diff --git a/.env.example b/.env.example index 7d34cef..48c5713 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,8 @@ ADMIN_WALLETS="" ORACLE_SECRET_KEY="" # ========== NETWORK CONFIGURATION ========== -# Network to use: testnet or public +# Network to use: testnet or public. Anything else (including empty) resolves +# to testnet -- the app never silently selects a network where funds are real. NEXT_PUBLIC_STELLAR_NETWORK=testnet # ========== STELLAR READ ADDRESS ========== @@ -46,13 +47,21 @@ NEXT_PUBLIC_STELLAR_NETWORK=testnet NEXT_PUBLIC_STELLAR_READ_ADDRESS= # ========== CONTRACT CONFIGURATION ========== -# The deployed CoreFlow contract address on Testnet or Mainnet. -# Update this after deploying the contract. +# The deployed CoreFlow contract address, on the network selected above. +# +# REQUIRED. There is no fallback: an unset value raises a clear error rather +# than defaulting to a hard-coded address. A previous default pointed at the +# MAINNET contract while the network defaulted to testnet, so an unconfigured +# deployment aimed a mainnet contract at testnet RPC and every call failed -- +# and a developer running locally was pointed at the live contract. NEXT_PUBLIC_STELLAR_CONTRACT_ID= # Settlement token: the Stellar Asset Contract (SAC) address used for custody. # On escrow creation the contract pulls this token from the manager and releases # it to workers on finalize. Use the USDC SAC for the configured network. +# The Stellar Asset Contract used for custody and settlement (e.g. the USDC +# SAC). Escrow creation refuses to build a transaction without it -- there is +# no default, because guessing the settlement asset is not recoverable. NEXT_PUBLIC_STELLAR_TOKEN_ID= # ========== WALLET CONFIGURATION ========== @@ -63,3 +72,40 @@ NEXT_PUBLIC_FREIGHTER_TIMEOUT=5000 # Selected automatically from NEXT_PUBLIC_STELLAR_NETWORK: # Testnet: https://soroban-testnet.stellar.org # Mainnet: https://mainnet.sorobanrpc.com + +# ========== VARIABLES WRITTEN BY `vercel env pull` ========== +# A `vercel env pull` overwrites .env / .env.local with the DEPLOYMENT's values, +# including these. On 2026-09-11 that silently repointed local development at the +# production database and Mainnet v1. If you pull, re-check the environment: +# +# npm run check:env +# +# These must be LOCAL in development. `npm run check:env` refuses otherwise, and +# it runs automatically before dev, db:migrate, db:deploy and db:seed. +PRISMA_DATABASE_URL="" +POSTGRES_URL="" +# Short-lived Vercel OIDC token. Never commit; never needed locally. +VERCEL_OIDC_TOKEN="" + +# ========== CRON ========== +# Bearer secret Vercel Cron presents to GET /api/indexer/run. Preferred over +# INDEXER_SECRET on Vercel. Generate with: +# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +CRON_SECRET="" + +# ========== BOOTSTRAP ========== +# One-time secret for POST /api/admin/bootstrap, which claims the first admin. +# Unset it again once the first administrator exists. +BOOTSTRAP_SECRET="" + +# ========== SETTLEMENT ASSET CODE ========== +# Display code for the asset NEXT_PUBLIC_STELLAR_TOKEN_ID issues, e.g. USDC. +# An escrow holds exactly ONE Stellar Asset Contract, so this is the only asset +# a payroll CSV may name here. CoreFlow never infers a SAC address from a symbol. +NEXT_PUBLIC_SETTLEMENT_ASSET_CODE=USDC + +# ========== PREFLIGHT OVERRIDES (never set these by default) ========== +# Each is a per-run, deliberate escape hatch for `scripts/check-env.mjs`: +# COREFLOW_ALLOW_MAINNET=1 act against Mainnet on purpose +# COREFLOW_ALLOW_REMOTE_DB=1 act against a non-local database on purpose +# COREFLOW_ALLOW_UNKNOWN_CONTRACT=1 use a contract absent from the registry diff --git a/.gitignore b/.gitignore index 82b9084..78881b6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,11 +20,18 @@ coverage node_modules/ # dotenv environment variables files +# +# Deny EVERY .env variant and re-admit only the example. The previous list named +# .env, .env.local and .env.*.local individually, which left .env.production, +# .env.development and anything else a tool decides to write unignored β€” and a +# `vercel env pull` writes real production secrets into files like those. .env -.env.local -.env.*.local +.env.* !.env.example +# Local TLS certificates for `next dev --experimental-https` +certs/ + # IDE & OS .vscode/ .idea/ @@ -60,3 +67,8 @@ contracts/*/target/ # Playwright /playwright-report/ /test-results/ + +# Exported environment dumps (e.g. `vercel env pull`) β€” these hold LIVE secrets +prodenv.txt +*env*.txt +.env*.txt diff --git a/README.md b/README.md index 9266509..78dc652 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,24 @@ ## πŸ”— Deployed Contract +> ## Two deployments β€” read this first +> +> CoreFlow has **two distinct on-chain deployments** with **different security properties**: +> +> | | Network | Contract | Status | +> |---|---|---|---| +> | **v2** (hardened) | **Testnet** | `CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4` | Active. Domain-separated attestations, admin-managed oracle registry, work/amount invariant, pinned admin. | +> | **v1** (historical) | Mainnet | `CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW` | Deployed earlier. Carries **none** of v2's hardening. | +> +> **v2's security improvements are NOT deployed on Mainnet.** Mainnet activity +> referenced below was produced against **v1**. Full detail: +> [`docs/DEPLOYMENTS.md`](docs/DEPLOYMENTS.md) Β· +> [`docs/evidence/REVIEWER_EVIDENCE.md`](docs/evidence/REVIEWER_EVIDENCE.md) +> +> Testnet activity is labelled **Testnet validated**; Mainnet contract calls are +> labelled **v1 Mainnet deployed**. Neither is revenue, pilot usage, or a +> commercial deployment. + > **Live on Stellar Mainnet** β€” View and verify the deployed CoreFlow smart contract on Stellar Expert: > > πŸ”— **[View Deployed Contract on Stellar Expert](https://stellar.expert/explorer/public/contract/CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW)** diff --git a/contracts/core-flow/src/lib.rs b/contracts/core-flow/src/lib.rs index a4ef3a3..29e4c8b 100644 --- a/contracts/core-flow/src/lib.rs +++ b/contracts/core-flow/src/lib.rs @@ -1,8 +1,9 @@ #![no_std] use soroban_sdk::token::TokenClient; +use soroban_sdk::xdr::ToXdr; use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, symbol_short, Address, Bytes, BytesN, Env, - Vec, + String, Vec, }; // ========== ENUMS & ERRORS ========== @@ -26,6 +27,20 @@ pub enum ContractError { ProofMissing = 13, NonceOverflow = 14, SignersNotDistinct = 15, + /// The oracle public key is not on the admin-managed registry. + OracleKeyNotRegistered = 16, + /// Attested hours x rate_per_hour does not equal the escrowed amount. + AmountHoursMismatch = 17, + /// end_date is not strictly after start_date. + InvalidPeriod = 18, + /// Batch exceeds MAX_BATCH_SIZE payments. + BatchTooLarge = 19, + /// This WASM pins an expected admin and the supplied address is not it. + AdminMismatch = 20, + /// No admin transfer is pending, or the caller is not the proposed admin. + NoPendingAdmin = 21, + /// `upgrade` requires the contract to be paused first. + NotPaused = 22, } #[contracttype] @@ -83,7 +98,11 @@ pub enum DataKey { Escrow(u32), Nonce(u32), Admin, + /// Proposed next admin, awaiting acceptance (two-step handover). + PendingAdmin, Paused, + /// Registered oracle signing keys. Presence => trusted by the platform admin. + OracleKey(BytesN<32>), } // Storage TTL constants (in ledgers) @@ -93,6 +112,45 @@ const INSTANCE_TTL_EXTEND: u32 = 17280 * 30; // Extend to 30 days const PERSISTENT_TTL_THRESHOLD: u32 = 17280; // Extend when below 1 day const PERSISTENT_TTL_EXTEND: u32 = 17280 * 90; // Extend to 90 days +/// Upper bound on payments per escrow. Every entry point loads and rewrites the +/// whole escrow, so an unbounded Vec is a denial-of-service vector: a batch big +/// enough to exceed the ledger resource limits would make its own escrow +/// permanently uncallable, stranding custody. 100 matches the API batch cap. +const MAX_BATCH_SIZE: u32 = 100; + +// ===== Oracle attestation domain separation (schema v2) ===== +// +// v1 signed only `escrow_id || payment_id || hours || nonce`. That message said +// nothing about WHICH chain, WHICH contract, WHICH worker or HOW MUCH, so one +// signature was valid on every deployment of this contract on every network for +// the same tuple -- a Testnet attestation replayed verbatim against Mainnet. +// +// v2 binds the attestation to its full context. Every field below is read from +// STORED escrow state rather than from caller arguments, so a caller cannot +// shift the message onto a payment the oracle never saw. +const PROOF_MAGIC: [u8; 4] = *b"CFWP"; // CoreFlow Work Proof +const PROOF_VERSION: u16 = 2; + +/// Build-time admin pin β€” the fix for `init_admin` front-running. +/// +/// THE PROBLEM: `init_admin` is first-caller-wins, and a Stellar transaction may +/// carry only ONE Soroban operation, so deploy and initialize cannot be bundled +/// atomically. That leaves a window in which anyone watching the ledger can call +/// `init_admin` first, become admin, and then `upgrade` the contract to +/// arbitrary code that drains every escrow's custody. +/// +/// THE FIX: a production build bakes the expected admin address into the WASM +/// (`COREFLOW_ADMIN=G... cargo build ...`). `init_admin` then refuses any other +/// address, so winning the race gains an attacker nothing β€” the window still +/// exists, but there is nothing to win. +/// +/// Builds without the pin (tests, local development) keep the old first-caller +/// behaviour, because test addresses are generated at runtime and cannot be +/// known at compile time. `scripts/deploy-testnet.sh` refuses to deploy an +/// unpinned WASM, and `expected_admin()` lets anyone verify a deployment's pin +/// on-chain after the fact. +const PINNED_ADMIN: Option<&str> = option_env!("COREFLOW_ADMIN"); + // ========== CONTRACT ========== #[contract] @@ -109,14 +167,79 @@ impl CoreFlowContract { if env.storage().instance().has(&DataKey::Admin) { return Err(ContractError::AdminAlreadySet); } + + // Front-running guard. When this WASM was built with COREFLOW_ADMIN set, + // only that address may claim the role β€” so losing the race to call + // `init_admin` first costs nothing. + if let Some(pinned) = PINNED_ADMIN { + let expected = Address::from_string(&String::from_str(&env, pinned)); + if admin != expected { + return Err(ContractError::AdminMismatch); + } + } + admin.require_auth(); env.storage().instance().set(&DataKey::Admin, &admin); env.storage() .instance() .extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND); + env.events() + .publish((symbol_short!("admin"), symbol_short!("init")), admin); + Ok(()) + } + + /// The admin address baked into this WASM at build time, if any. + /// + /// Read-only, so an operator (or an auditor) can confirm after deploy that + /// the running code is pinned to the key they expect, rather than trusting + /// that the deploy script was run correctly. + pub fn expected_admin(env: Env) -> Option
{ + PINNED_ADMIN.map(|p| Address::from_string(&String::from_str(&env, p))) + } + + /// Propose a new admin (current admin only). Step 1 of 2. + /// + /// Handover is two-step because a single-step transfer to a mistyped or + /// uncontrolled address permanently destroys the ability to pause, upgrade, + /// or manage the oracle registry. The proposed key must prove it can sign. + pub fn propose_admin(env: Env, new_admin: Address) -> Result<(), ContractError> { + Self::require_admin(&env)?; + env.storage() + .instance() + .set(&DataKey::PendingAdmin, &new_admin); + env.storage() + .instance() + .extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND); + env.events() + .publish((symbol_short!("admin"), symbol_short!("propose")), new_admin); + Ok(()) + } + + /// Accept a pending admin handover (proposed admin only). Step 2 of 2. + pub fn accept_admin(env: Env) -> Result<(), ContractError> { + let pending: Address = env + .storage() + .instance() + .get(&DataKey::PendingAdmin) + .ok_or(ContractError::NoPendingAdmin)?; + + pending.require_auth(); + + env.storage().instance().set(&DataKey::Admin, &pending); + env.storage().instance().remove(&DataKey::PendingAdmin); + env.storage() + .instance() + .extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND); + env.events() + .publish((symbol_short!("admin"), symbol_short!("accept")), pending); Ok(()) } + /// The currently configured admin, if one has been set. + pub fn get_admin(env: Env) -> Option
{ + env.storage().instance().get(&DataKey::Admin) + } + /// Pause or unpause state-changing operations (admin only). `cancel_escrow` /// stays available while paused so funds can always be refunded. pub fn set_paused(env: Env, paused: bool) -> Result<(), ContractError> { @@ -141,6 +264,27 @@ impl CoreFlowContract { /// the contract address or migrating escrow funds. pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), ContractError> { Self::require_admin(&env)?; + + // Upgrading is the one admin power that can drain every escrow at once: + // it replaces the code holding custody. Requiring the contract to be + // paused first makes that a deliberate two-transaction sequence with an + // observable `paused` event in between, rather than something that can + // happen silently while the system looks healthy. It does not stop a + // malicious admin -- nothing at this layer can -- but it removes the + // silent path and gives monitoring something to alert on. + if !env + .storage() + .instance() + .get(&DataKey::Paused) + .unwrap_or(false) + { + return Err(ContractError::NotPaused); + } + + env.events().publish( + (symbol_short!("admin"), symbol_short!("upgrade")), + new_wasm_hash.clone(), + ); env.deployer().update_current_contract_wasm(new_wasm_hash); Ok(()) } @@ -167,8 +311,134 @@ impl CoreFlowContract { Ok(()) } + // ===== Oracle key registry (admin-managed) ===== + + /// Register an oracle signing key as trusted by the platform (admin only). + /// + /// WHY A REGISTRY: previously the manager passed any `oracle_pubkey` they + /// liked into `initialize_multi_sig_escrow`, so a manager could install + /// their own key and sign their own "verified work" attestations. The + /// proof-of-work gate was therefore manager-attestable -- procedural, not + /// cryptographic. Escrows may now only name a key the admin has registered, + /// which makes the oracle an independent party by construction. + pub fn register_oracle_key(env: Env, pubkey: BytesN<32>) -> Result<(), ContractError> { + Self::require_admin(&env)?; + env.storage() + .persistent() + .set(&DataKey::OracleKey(pubkey.clone()), &true); + env.storage().persistent().extend_ttl( + &DataKey::OracleKey(pubkey.clone()), + PERSISTENT_TTL_THRESHOLD, + PERSISTENT_TTL_EXTEND, + ); + env.events() + .publish((symbol_short!("oracle"), symbol_short!("reg")), pubkey); + Ok(()) + } + + /// Revoke a previously registered oracle key (admin only). + /// + /// Existing escrows already naming this key keep functioning -- revoking is + /// not retroactive, because silently invalidating in-flight attestations + /// would strand funded escrows. It stops the key being named by NEW escrows + /// and NEW rotations. To retire a key from a live escrow, the manager calls + /// `rotate_oracle_key`, which revokes that escrow's verified proofs. + pub fn revoke_oracle_key(env: Env, pubkey: BytesN<32>) -> Result<(), ContractError> { + Self::require_admin(&env)?; + env.storage() + .persistent() + .remove(&DataKey::OracleKey(pubkey.clone())); + env.events() + .publish((symbol_short!("oracle"), symbol_short!("revoke")), pubkey); + Ok(()) + } + + /// True if `pubkey` is on the admin-managed registry. + pub fn is_oracle_key_registered(env: Env, pubkey: BytesN<32>) -> bool { + env.storage() + .persistent() + .get(&DataKey::OracleKey(pubkey)) + .unwrap_or(false) + } + + /// Bootstrap exception: if no admin was ever configured, the contract has no + /// registry authority and the registry check cannot be satisfied by anyone. + /// Rather than bricking such a deployment, an admin-less contract accepts any + /// key -- exactly the v1 trust model, and no weaker. Once `init_admin` runs, + /// the registry is enforced from that point on. + fn require_registered_oracle(env: &Env, pubkey: &BytesN<32>) -> Result<(), ContractError> { + if !env.storage().instance().has(&DataKey::Admin) { + return Ok(()); + } + if env + .storage() + .persistent() + .get(&DataKey::OracleKey(pubkey.clone())) + .unwrap_or(false) + { + Ok(()) + } else { + Err(ContractError::OracleKeyNotRegistered) + } + } + // ===== Oracle primitives ===== + /// SHA-256 of an address's XDR serialization. + /// + /// Addresses serialize to a variable number of bytes (an account ScAddress + /// and a contract ScAddress differ in length), so hashing each to a fixed 32 + /// bytes keeps the proof preimage fixed-width and trivially reproducible + /// off-chain. `scripts/oracle-cli.mjs` and `src/lib/oracle/index.ts` build + /// the identical digest; `test_cli_generated_signature_is_accepted_onchain` + /// fails if the two ever drift. + fn addr_digest(env: &Env, addr: &Address) -> BytesN<32> { + env.crypto().sha256(&addr.clone().to_xdr(env)) + } + + /// Build the domain-separated attestation preimage (schema v2, 198 bytes). + /// + /// magic "CFWP" 4 + /// version u16 BE 2 + /// network_id sha256(passphrase) 32 <- binds to Testnet vs Mainnet + /// contract sha256(addr xdr) 32 <- binds to THIS deployment + /// worker sha256(addr xdr) 32 <- binds to the payee + /// token sha256(addr xdr) 32 <- binds to the asset + /// escrow_id u32 BE 4 + /// payment_id u32 BE 4 + /// amount i128 BE 16 <- binds to how much moves + /// hours i128 BE 16 + /// start_date u64 BE 8 + /// end_date u64 BE 8 <- binds to the pay period + /// nonce u64 BE 8 + /// + /// `worker`, `token`, `amount` and the period come from the STORED payment + /// row, never from caller arguments. + fn build_proof_message( + env: &Env, + escrow_id: u32, + payment_id: u32, + payment: &PaymentSchedule, + hours: i128, + nonce: u64, + ) -> Bytes { + let mut m = Bytes::new(env); + m.extend_from_array(&PROOF_MAGIC); + m.extend_from_array(&PROOF_VERSION.to_be_bytes()); + m.extend_from_array(&env.ledger().network_id().to_array()); + m.extend_from_array(&Self::addr_digest(env, &env.current_contract_address()).to_array()); + m.extend_from_array(&Self::addr_digest(env, &payment.worker).to_array()); + m.extend_from_array(&Self::addr_digest(env, &payment.token).to_array()); + m.extend_from_array(&escrow_id.to_be_bytes()); + m.extend_from_array(&payment_id.to_be_bytes()); + m.extend_from_array(&payment.amount.to_be_bytes()); + m.extend_from_array(&hours.to_be_bytes()); + m.extend_from_array(&payment.start_date.to_be_bytes()); + m.extend_from_array(&payment.end_date.to_be_bytes()); + m.extend_from_array(&nonce.to_be_bytes()); + m + } + /// Verify an Ed25519 oracle attestation over `payload`. /// /// NOTE ON RETURN TYPE: this cannot return `bool`. `Env::crypto().ed25519_verify` @@ -223,6 +493,9 @@ impl CoreFlowContract { escrow.manager.require_auth(); + // Rotation cannot be used to escape the registry. + Self::require_registered_oracle(&env, &new_pubkey)?; + if escrow.cancelled { return Err(ContractError::EscrowCancelled); } @@ -284,6 +557,13 @@ impl CoreFlowContract { if payments.is_empty() { return Err(ContractError::InvalidAmount); } + if payments.len() > MAX_BATCH_SIZE { + return Err(ContractError::BatchTooLarge); + } + + // The oracle must be one the platform admin trusts, not one the manager + // chose. See `register_oracle_key`. + Self::require_registered_oracle(&env, &oracle_pubkey)?; // Guard amounts/rates. `total_amount` is for the event only β€” custody is // now funded per asset, since a batch may mix e.g. USDC and native XLM. @@ -293,6 +573,17 @@ impl CoreFlowContract { if p.amount <= 0 || p.rate_per_hour <= 0 { return Err(ContractError::InvalidAmount); } + // A zero-width or inverted period would make the attested pay period + // meaningless, and the period is a signed field of the proof. + if p.end_date <= p.start_date { + return Err(ContractError::InvalidPeriod); + } + // The amount must be reachable by whole attested hours at this rate, + // otherwise `submit_hours_proof`'s `hours x rate == amount` check can + // never be satisfied and the escrow is funded but unsettleable. + if p.amount % p.rate_per_hour != 0 { + return Err(ContractError::AmountHoursMismatch); + } total_amount += p.amount; } @@ -383,17 +674,45 @@ impl CoreFlowContract { (escrow_id, manager, total_amount), ); + // One event PER PAYMENT, carrying that payment's full financial identity. + // + // WHY THIS EXISTS: the escrow-level events above say only how much moved + // in aggregate. An indexer given just those cannot reconstruct who was + // paid what, so it would have to read `get_escrow` at index time β€” which + // returns CURRENT state, not the state at that ledger. That makes the + // projection non-deterministic and unreplayable: re-indexing from + // scratch after later activity would produce different rows. + // + // Emitting per-payment events makes the event stream self-sufficient, so + // the off-chain projection is a pure function of the log. `payment_index` + // is the zero-based Vec index, matching the `payment_id` argument that + // `submit_hours_proof` and `proof_preimage` take. + for i in 0..payments.len() { + let p = payments.get(i).unwrap(); + env.events().publish( + (symbol_short!("payment"), symbol_short!("add")), + ( + escrow_id, + i, + p.worker.clone(), + p.token.clone(), + p.amount, + p.rate_per_hour, + p.start_date, + p.end_date, + ), + ); + } + Ok(escrow_id) } - /// Submit hours proof verified by Ed25519 oracle signature. - /// - /// The oracle signs a 32-byte message: - /// escrow_id (4 bytes BE) || payment_id (4 bytes BE) || - /// hours_logged (16 bytes BE) || nonce (8 bytes BE) + /// Submit hours proof verified by an Ed25519 oracle signature. /// - /// The contract verifies the signature against the escrow's stored oracle public key - /// and checks the nonce matches the expected value to prevent replay attacks. + /// The oracle signs the 198-byte domain-separated preimage documented on + /// `build_proof_message` (schema v2). The contract rebuilds that preimage + /// from stored state, verifies it against the escrow's oracle public key, + /// enforces `hours x rate == amount`, and consumes the next expected nonce. pub fn submit_hours_proof( env: Env, escrow_id: u32, @@ -424,13 +743,25 @@ impl CoreFlowContract { return Err(ContractError::InvalidPaymentId); } - // Construct the 32-byte message the oracle should have signed - let mut msg_data = [0u8; 32]; - msg_data[0..4].copy_from_slice(&escrow_id.to_be_bytes()); - msg_data[4..8].copy_from_slice(&payment_id.to_be_bytes()); - msg_data[8..24].copy_from_slice(&hours_logged.to_be_bytes()); - msg_data[24..32].copy_from_slice(&nonce.to_be_bytes()); - let message = Bytes::from_slice(&env, &msg_data); + let mut payment = escrow.payments.get(payment_id).unwrap(); + + // The attested work must justify the escrowed amount exactly. Without + // this, `hours_logged` was decorative: the oracle could attest to any + // number of hours while `amount` -- fixed at creation and already funded + // into custody -- paid out regardless. Tying them makes "verified work + // determines payment" an on-chain invariant rather than a description. + let earned = hours_logged + .checked_mul(payment.rate_per_hour) + .ok_or(ContractError::InvalidAmount)?; + if earned != payment.amount { + return Err(ContractError::AmountHoursMismatch); + } + + // Domain-separated preimage (schema v2). Built from stored payment state, + // so a caller cannot retarget a signature onto a different payee, asset, + // amount, period, contract or network. + let message = + Self::build_proof_message(&env, escrow_id, payment_id, &payment, hours_logged, nonce); // Signature first, then nonce. Verification traps on a bad signature, so // consuming the nonce beforehand would let an attacker burn the escrow's @@ -438,9 +769,7 @@ impl CoreFlowContract { Self::verify_oracle_work(&env, &message, &signature, &escrow.oracle_pubkey); Self::track_nonce(&env, escrow_id, nonce)?; - // Update the payment schedule with hours logged and mark the payment as - // carrying a verified proof β€” `pay_batch` requires this flag. - let mut payment = escrow.payments.get(payment_id).unwrap(); + // Mark the payment as carrying a verified proof β€” `pay_batch` requires it. payment.hours_logged = hours_logged; payment.proof_verified = true; @@ -605,6 +934,22 @@ impl CoreFlowContract { p.status = PaymentStatus::Finalized; TokenClient::new(&env, &p.token).transfer(&contract_addr, &p.worker, &p.amount); total_amount += p.amount; + + // Per-payment settlement event, emitted AFTER the transfer for this + // payee. A trapping transfer reverts the whole batch, so an emitted + // `paid` event always corresponds to value that actually moved. + env.events().publish( + (symbol_short!("payment"), symbol_short!("paid")), + ( + escrow_id, + i, + p.worker.clone(), + p.token.clone(), + p.amount, + p.hours_logged, + ), + ); + finalized_payments.push_back(p); } @@ -695,6 +1040,12 @@ impl CoreFlowContract { for i in 0..escrow.payments.len() { let mut p = escrow.payments.get(i).unwrap(); p.status = PaymentStatus::Cancelled; + // Per-payment cancellation, so the off-chain projection can move + // each payment to a terminal state from the log alone. + env.events().publish( + (symbol_short!("payment"), symbol_short!("cancel")), + (escrow_id, i), + ); cancelled_payments.push_back(p); } escrow.payments = cancelled_payments; @@ -720,6 +1071,104 @@ impl CoreFlowContract { .ok_or(ContractError::InvalidPaymentId) } + /// Extend an escrow's storage lifetime. Anyone may call this. + /// + /// Persistent entries that run out of rent are archived to the Expired + /// State Stack and can be restored; they are not deleted. The failure this + /// avoids is a funded escrow becoming temporarily unusable until someone + /// pays to restore it. + // + // ── The Soroban storage lifecycle, precisely ──────────────────────────── + // Escrow state and its nonce watermark live in PERSISTENT storage. When a + // persistent entry runs out of rent it is removed from the live ledger and + // placed on the Expired State Stack, from which it can be restored with a + // Stellar Core `RestoreFootprint` operation. Persistent entries are NOT + // permanently deleted -- that is the behaviour of TEMPORARY storage, which + // this contract deliberately does not use for anything. + // + // So the failure mode is a funded escrow becoming temporarily *unusable* + // (every entry point loads the escrow first, so all of them fail) until + // someone pays to restore it. Recoverable, not fund loss. Still worth + // avoiding: an escrow needing an out-of-band restore before a worker can be + // paid is an operational incident. + // + // ── Why anyone may call this ──────────────────────────────────────────── + // Requiring the manager's authorization would tie an escrow's survival to + // one key remaining available and willing. The party with the strongest + // interest in keeping a funded escrow alive is often the WORKER awaiting + // payment, and they hold no authority over it. Keeping this open lets the + // worker, the platform, or a keeper bot pay the rent. There is nothing to + // abuse: the only effect is paying to keep someone else's data alive, and + // the caller funds the transaction. + // + // Both keys are extended together. Letting the nonce watermark and the + // escrow diverge in lifetime would mean restoring one without the other. + pub fn extend_escrow_ttl(env: Env, escrow_id: u32) -> Result<(), ContractError> { + // Confirm the escrow exists before charging anyone rent for a key that + // holds nothing. + if !env + .storage() + .persistent() + .has(&DataKey::Escrow(escrow_id)) + { + return Err(ContractError::InvalidPaymentId); + } + + // Extend to the network maximum: the caller has explicitly chosen to pay + // for longevity, so buying the least possible would be a strange default. + let max = env.storage().max_ttl(); + + env.storage() + .persistent() + .extend_ttl(&DataKey::Escrow(escrow_id), max, max); + env.storage() + .persistent() + .extend_ttl(&DataKey::Nonce(escrow_id), max, max); + env.storage().persistent().extend_ttl( + &DataKey::EscrowCount, + max, + max, + ); + // The contract instance carries Admin and Paused; if it lapses, nothing + // works regardless of how healthy an individual escrow is. + env.storage().instance().extend_ttl(max, max); + + env.events().publish( + (symbol_short!("escrow"), symbol_short!("ttl")), + (escrow_id, max), + ); + + Ok(()) + } + + /// Return the exact bytes the oracle must sign for this payment. + /// + /// Read-only. Exposing the preimage makes the CONTRACT the single source of + /// truth for the message format: an off-chain signer can simulate this call + /// and sign the returned bytes verbatim instead of reimplementing the layout + /// and hoping the two agree. Every historical mismatch between a signer and + /// a verifier is a bug this removes by construction. + pub fn proof_preimage( + env: Env, + escrow_id: u32, + payment_id: u32, + hours: i128, + nonce: u64, + ) -> Result { + let escrow: CoreFlowEscrow = env + .storage() + .persistent() + .get(&DataKey::Escrow(escrow_id)) + .ok_or(ContractError::InvalidPaymentId)?; + if payment_id >= escrow.payments.len() { + return Err(ContractError::InvalidPaymentId); + } + let payment = escrow.payments.get(payment_id).unwrap(); + Ok(Self::build_proof_message( + &env, escrow_id, payment_id, &payment, hours, nonce, + )) + } + /// Return the next expected oracle nonce for an escrow. /// The oracle must sign a proof using this exact value (replay protection). /// Returns 0 for an unknown/uninitialized escrow. diff --git a/contracts/core-flow/src/test.rs b/contracts/core-flow/src/test.rs index 95204d5..098b01a 100644 --- a/contracts/core-flow/src/test.rs +++ b/contracts/core-flow/src/test.rs @@ -2,8 +2,11 @@ mod tests { use crate::{ContractError, CoreFlowContract, CoreFlowContractClient, PaymentSchedule, PaymentStatus}; use ed25519_dalek::{Signer, SigningKey}; - use soroban_sdk::testutils::Address as _; + use soroban_sdk::testutils::{Address as _, Events as _, Ledger as _, LedgerInfo}; use soroban_sdk::token::{StellarAssetClient, TokenClient}; + use soroban_sdk::xdr::ToXdr; + use soroban_sdk::{symbol_short, IntoVal}; + use soroban_sdk::{Bytes, String as SorobanString}; use soroban_sdk::{Address, BytesN, Env, Vec}; // ========== HELPERS ========== @@ -11,6 +14,12 @@ mod tests { /// Amount minted to the manager so escrow funding transfers succeed. const MINT_AMOUNT: i128 = 1_000_000; + /// This crate's own compiled WASM, used to exercise `upgrade` with a hash the + /// host will actually accept. + const CURRENT_WASM: &[u8] = include_bytes!( + "../target/wasm32v1-none/release/core_flow.wasm" + ); + /// Generate a deterministic Ed25519 keypair for testing. /// Returns (signing_key, oracle_pubkey_bytes). fn generate_oracle_keypair(env: &Env) -> (SigningKey, BytesN<32>) { @@ -42,17 +51,26 @@ mod tests { } /// Submit a valid oracle proof for every payment so `pay_batch` will settle. - /// Nonce is sequential across rows, matching the contract's watermark. + /// + /// Hours are derived per row as `amount / rate_per_hour` because the contract + /// now enforces `hours x rate == amount`; a fixed 40 would fail any row whose + /// escrowed amount implies different hours. Nonce is read from the live + /// watermark rather than assumed. fn prove_all( env: &Env, client: &CoreFlowContractClient, + contract_id: &Address, signing_key: &SigningKey, escrow_id: u32, - count: u32, ) { - for i in 0..count { - let sig = sign_oracle_proof(env, signing_key, escrow_id, i, 40, i as u64); - client.submit_hours_proof(&escrow_id, &i, &40i128, &(i as u64), &sig); + let escrow = client.get_escrow(&escrow_id); + for i in 0..escrow.payments.len() { + let p = escrow.payments.get(i).unwrap(); + let hours = p.amount / p.rate_per_hour; + let nonce = client.get_nonce(&escrow_id); + let sig = + sign_oracle_proof(env, client, contract_id, signing_key, escrow_id, i, hours, nonce); + client.submit_hours_proof(&escrow_id, &i, &hours, &nonce, &sig); } } @@ -60,31 +78,63 @@ mod tests { TokenClient::new(env, token).balance(who) } - /// Construct the 32-byte message that the oracle should sign. + fn addr_digest(env: &Env, addr: &Address) -> [u8; 32] { + env.crypto().sha256(&addr.clone().to_xdr(env)).to_array() + } + + /// Rebuild the 198-byte domain-separated preimage (schema v2) independently + /// of the contract's own builder. + /// + /// This is deliberately a second implementation rather than a call into + /// `CoreFlowContract::build_proof_message`. Sharing the builder would make + /// every signature test tautological -- it would prove only that one function + /// agrees with itself, and a field silently dropped from the preimage would + /// still pass. Written out, the layout is pinned by an independent witness. fn build_oracle_message( + env: &Env, + contract_id: &Address, + payment: &PaymentSchedule, escrow_id: u32, payment_id: u32, hours_logged: i128, nonce: u64, - ) -> [u8; 32] { - let mut msg = [0u8; 32]; - msg[0..4].copy_from_slice(&escrow_id.to_be_bytes()); - msg[4..8].copy_from_slice(&payment_id.to_be_bytes()); - msg[8..24].copy_from_slice(&hours_logged.to_be_bytes()); - msg[24..32].copy_from_slice(&nonce.to_be_bytes()); - msg + ) -> [u8; 198] { + let mut m = [0u8; 198]; + m[0..4].copy_from_slice(b"CFWP"); + m[4..6].copy_from_slice(&2u16.to_be_bytes()); + m[6..38].copy_from_slice(&env.ledger().network_id().to_array()); + m[38..70].copy_from_slice(&addr_digest(env, contract_id)); + m[70..102].copy_from_slice(&addr_digest(env, &payment.worker)); + m[102..134].copy_from_slice(&addr_digest(env, &payment.token)); + m[134..138].copy_from_slice(&escrow_id.to_be_bytes()); + m[138..142].copy_from_slice(&payment_id.to_be_bytes()); + m[142..158].copy_from_slice(&payment.amount.to_be_bytes()); + m[158..174].copy_from_slice(&hours_logged.to_be_bytes()); + m[174..182].copy_from_slice(&payment.start_date.to_be_bytes()); + m[182..190].copy_from_slice(&payment.end_date.to_be_bytes()); + m[190..198].copy_from_slice(&nonce.to_be_bytes()); + m } /// Sign an oracle message and return BytesN<64> signature. + /// + /// The payment row is read back from the contract so the preimage carries the + /// same worker/token/amount/period the contract will reconstruct. fn sign_oracle_proof( env: &Env, + client: &CoreFlowContractClient, + contract_id: &Address, signing_key: &SigningKey, escrow_id: u32, payment_id: u32, hours_logged: i128, nonce: u64, ) -> BytesN<64> { - let msg = build_oracle_message(escrow_id, payment_id, hours_logged, nonce); + let escrow = client.get_escrow(&escrow_id); + let payment = escrow.payments.get(payment_id).unwrap(); + let msg = build_oracle_message( + env, contract_id, &payment, escrow_id, payment_id, hours_logged, nonce, + ); let signature = signing_key.sign(&msg); BytesN::from_array(env, &signature.to_bytes()) } @@ -145,8 +195,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -227,7 +277,7 @@ mod tests { ); assert_eq!(balance_of(&env, &token, &contract_id), 13000); - prove_all(&env, &client, &signing_key, escrow_id, 2); + prove_all(&env, &client, &contract_id, &signing_key, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); client.finalize_payment(&escrow_id); @@ -275,8 +325,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -295,7 +345,7 @@ mod tests { ); // Manager approval - prove_all(&env, &client, &signing_key, escrow_id, 1); + prove_all(&env, &client, &contract_id, &signing_key, escrow_id); client.manager_approve(&escrow_id); let mut escrow = client.get_escrow(&escrow_id); assert!(escrow.manager_approved); @@ -318,8 +368,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -337,15 +387,17 @@ mod tests { &payments, ); - // Sign with real Ed25519 key - let hours: i128 = 80; + // Sign with real Ed25519 key. 10000 units at 250/hour is exactly 40 + // hours -- the contract rejects any other figure with #17. + let hours: i128 = 40; let nonce: u64 = 0; - let sig = sign_oracle_proof(&env, &signing_key, escrow_id, 0, hours, nonce); + let sig = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, hours, nonce); client.submit_hours_proof(&escrow_id, &0, &hours, &nonce, &sig); let escrow = client.get_escrow(&escrow_id); - assert_eq!(escrow.payments.get(0).unwrap().hours_logged, 80); + assert_eq!(escrow.payments.get(0).unwrap().hours_logged, 40); + assert!(escrow.payments.get(0).unwrap().proof_verified); } #[test] @@ -353,8 +405,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -377,7 +429,7 @@ mod tests { assert_eq!(escrow.payments.get(1).unwrap().amount, 8000); // Full flow with multiple payments - prove_all(&env, &client, &signing_key, escrow_id, 2); + prove_all(&env, &client, &contract_id, &signing_key, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); let finalized = client.finalize_payment(&escrow_id); @@ -391,8 +443,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -434,8 +486,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -468,8 +520,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -499,8 +551,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -519,15 +571,19 @@ mod tests { ); // First submission with nonce 0 - let sig0 = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 40, 0); + let sig0 = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40, &0, &sig0); + assert_eq!(client.get_nonce(&escrow_id), 1); - // Second submission with nonce 1 (updated hours) - let sig1 = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 80, 1); - client.submit_hours_proof(&escrow_id, &0, &80, &1, &sig1); + // A second attestation at the next nonce is accepted, and the watermark + // advances again. The hours must match the escrowed amount both times -- + // re-attesting is a re-confirmation, not a way to revise the payout. + let sig1 = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 1); + client.submit_hours_proof(&escrow_id, &0, &40, &1, &sig1); + assert_eq!(client.get_nonce(&escrow_id), 2); let escrow = client.get_escrow(&escrow_id); - assert_eq!(escrow.payments.get(0).unwrap().hours_logged, 80); + assert_eq!(escrow.payments.get(0).unwrap().hours_logged, 40); } #[test] @@ -535,8 +591,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -557,7 +613,7 @@ mod tests { // Fresh escrow starts at nonce 0. assert_eq!(client.get_nonce(&escrow_id), 0); - let sig = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40, &0, &sig); // Nonce advances after a successful proof. @@ -570,8 +626,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -590,7 +646,7 @@ mod tests { ); // First submission succeeds - let sig = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40, &0, &sig); // Replay same nonce β€” should panic with InvalidNonce @@ -610,8 +666,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -631,7 +687,7 @@ mod tests { let wrong_secret: [u8; 32] = [99u8; 32]; let wrong_key = SigningKey::from_bytes(&wrong_secret); - let sig = sign_oracle_proof(&env, &wrong_key, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &wrong_key, escrow_id, 0, 40, 0); let result = client.try_submit_hours_proof(&escrow_id, &0, &40, &0, &sig); assert!( @@ -648,8 +704,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -680,8 +736,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -710,8 +766,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -729,7 +785,7 @@ mod tests { &payments, ); - prove_all(&env, &client, &signing_key, escrow_id, 1); + prove_all(&env, &client, &contract_id, &signing_key, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); client.finalize_payment(&escrow_id); @@ -744,8 +800,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -774,8 +830,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -793,7 +849,7 @@ mod tests { &payments, ); - prove_all(&env, &client, &signing_key, escrow_id, 1); + prove_all(&env, &client, &contract_id, &signing_key, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); client.finalize_payment(&escrow_id); @@ -808,8 +864,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); // Should panic with InvalidPaymentId client.get_escrow(&999); @@ -821,8 +877,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -841,8 +897,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -861,7 +917,7 @@ mod tests { ); client.cancel_escrow(&escrow_id); - let sig = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 0); // Should fail β€” escrow is cancelled client.submit_hours_proof(&escrow_id, &0, &40, &0, &sig); } @@ -872,8 +928,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -893,7 +949,7 @@ mod tests { client.manager_approve(&escrow_id); - let sig = sign_oracle_proof(&env, &signing_key, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 40, 0); // Should fail β€” manager already approved, cannot modify hours client.submit_hours_proof(&escrow_id, &0, &40, &0, &sig); } @@ -904,8 +960,8 @@ mod tests { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let manager = Address::generate(&env); let finance = Address::generate(&env); @@ -927,8 +983,8 @@ mod tests { fn test_admin_can_be_set_once() { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let admin = Address::generate(&env); client.init_admin(&admin); @@ -942,8 +998,8 @@ mod tests { fn test_set_paused_without_admin_fails() { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); // No admin configured -> NotAdmin (#10). client.set_paused(&true); } @@ -953,8 +1009,8 @@ mod tests { fn test_pause_blocks_new_escrow() { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let admin = Address::generate(&env); let manager = Address::generate(&env); @@ -964,6 +1020,9 @@ mod tests { let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); client.init_admin(&admin); + // Once an admin exists the oracle registry is enforced, so the key this + // escrow names has to be one the admin trusts. + client.register_oracle_key(&oracle_pubkey); client.set_paused(&true); assert!(client.is_paused()); @@ -977,8 +1036,8 @@ mod tests { fn test_unpause_restores_operations() { let env = Env::default(); env.mock_all_auths(); - let client = - CoreFlowContractClient::new(&env, &env.register_contract(None, CoreFlowContract)); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); let admin = Address::generate(&env); let manager = Address::generate(&env); @@ -988,6 +1047,9 @@ mod tests { let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); client.init_admin(&admin); + // Once an admin exists the oracle registry is enforced, so the key this + // escrow names has to be one the admin trusts. + client.register_oracle_key(&oracle_pubkey); client.set_paused(&true); client.set_paused(&false); @@ -1017,6 +1079,9 @@ mod tests { let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); client.init_admin(&admin); + // Once an admin exists the oracle registry is enforced, so the key this + // escrow names has to be one the admin trusts. + client.register_oracle_key(&oracle_pubkey); let mut payments = Vec::new(&env); payments.push_back(create_test_payment(&env, &worker, &token)); // 10000 let id = client.initialize_multi_sig_escrow( @@ -1083,7 +1148,7 @@ mod tests { assert_eq!(balance_of(&env, &usdc, &contract_id), 5000); assert_eq!(balance_of(&env, &xlm, &contract_id), 8000); - prove_all(&env, &client, &sk, escrow_id, 2); + prove_all(&env, &client, &contract_id, &sk, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); client.pay_batch(&escrow_id); @@ -1188,7 +1253,7 @@ mod tests { let escrow_id = client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); - let sig = sign_oracle_proof(&env, &sk, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &sk, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40i128, &0u64, &sig); assert_eq!(client.get_nonce(&escrow_id), 1); @@ -1235,7 +1300,7 @@ mod tests { // catch β€” it takes the whole test process down with SIGABRT. That path // is covered by the Testnet validation run instead; see the oracle CLI // rotation walkthrough in the developer guide. - let fresh_sig = sign_oracle_proof(&env, &new_sk, escrow_id, 0, 40, 0); + let fresh_sig = sign_oracle_proof(&env, &client, &contract_id, &new_sk, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40i128, &0u64, &fresh_sig); assert!(client.get_escrow(&escrow_id).payments.get(0).unwrap().proof_verified); } @@ -1258,7 +1323,7 @@ mod tests { let escrow_id = client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); - let sig = sign_oracle_proof(&env, &sk, escrow_id, 0, 40, 0); + let sig = sign_oracle_proof(&env, &client, &contract_id, &sk, escrow_id, 0, 40, 0); client.submit_hours_proof(&escrow_id, &0, &40i128, &0u64, &sig); assert!(client.get_escrow(&escrow_id).payments.get(0).unwrap().proof_verified); @@ -1296,7 +1361,7 @@ mod tests { let escrow_id = client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); - prove_all(&env, &client, &sk, escrow_id, 1); + prove_all(&env, &client, &contract_id, &sk, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); client.pay_batch(&escrow_id); @@ -1326,7 +1391,7 @@ mod tests { let escrow_id = client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); - prove_all(&env, &client, &sk, escrow_id, 1); + prove_all(&env, &client, &contract_id, &sk, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); @@ -1364,29 +1429,112 @@ mod tests { // ========== CROSS-LANGUAGE CONFORMANCE (CLI <-> CONTRACT) ========== + /// Shared cross-language vector (docs/evidence/proof-vector-v2.json). + /// + /// The same constants are asserted by the TypeScript signer in + /// src/lib/oracle/__tests__/sign.test.ts. Two independent implementations + /// pinned to one vector is what makes "the signer and the verifier agree" a + /// tested claim rather than an assumption; a field added, reordered or + /// dropped on either side breaks one of the two suites. + const VECTOR_CONTRACT: &str = "CCQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2CNSG"; + const VECTOR_WORKER: &str = "GB43KVROR7TFJ6KAPCYRF2FJROTZAH4FHLTJLPWX4DRZCC5NASLGITR6"; + const VECTOR_TOKEN: &str = "CCZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLEB3K"; + /// sha256("Test SDF Network ; September 2015") + const VECTOR_NETWORK_ID: [u8; 32] = [ + 206, 224, 48, 45, 89, 132, 77, 50, 189, 202, 145, 92, + 130, 3, 221, 68, 179, 63, 187, 126, 220, 25, 5, 30, + 163, 122, 190, 223, 40, 236, 212, 114, + ]; + /// The 198-byte preimage the CLI produced for escrow 1 / payment 0 / + /// 10000 units / 40 hours / period 1000..2000 / nonce 0. + const VECTOR_MESSAGE: [u8; 198] = [ + 67, 70, 87, 80, 0, 2, 206, 224, 48, 45, 89, 132, + 77, 50, 189, 202, 145, 92, 130, 3, 221, 68, 179, 63, + 187, 126, 220, 25, 5, 30, 163, 122, 190, 223, 40, 236, + 212, 114, 91, 12, 99, 36, 38, 131, 234, 88, 177, 74, + 255, 60, 106, 69, 95, 166, 219, 243, 87, 61, 222, 220, + 30, 79, 162, 24, 224, 64, 103, 17, 186, 66, 44, 187, + 208, 6, 4, 30, 234, 113, 96, 61, 172, 242, 46, 138, + 241, 168, 203, 242, 243, 176, 8, 60, 170, 139, 139, 243, + 51, 171, 86, 92, 226, 224, 81, 25, 87, 64, 74, 123, + 96, 183, 34, 169, 57, 133, 142, 71, 250, 146, 5, 197, + 247, 199, 29, 231, 148, 31, 21, 210, 244, 137, 200, 197, + 60, 235, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 39, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, + 3, 232, 0, 0, 0, 0, 0, 0, 7, 208, 0, 0, + 0, 0, 0, 0, 0, 0, + ]; + /// The CLI's Ed25519 signature over VECTOR_MESSAGE for the test oracle seed. + const VECTOR_SIGNATURE: [u8; 64] = [ + 77, 210, 135, 45, 238, 118, 184, 14, 143, 74, 232, 251, + 208, 185, 36, 146, 138, 93, 230, 4, 224, 155, 50, 226, + 115, 102, 83, 220, 121, 142, 103, 167, 184, 83, 160, 232, + 41, 169, 200, 153, 188, 163, 81, 62, 52, 108, 15, 46, + 71, 28, 214, 133, 177, 104, 240, 9, 95, 151, 97, 216, + 124, 171, 62, 10, + ]; + + fn vector_env() -> Env { + let env = Env::default(); + // Pin the network so the preimage's network_id field is Testnet's, + // matching what the CLI hashed from the passphrase. + let mut info = env.ledger().get(); + info.network_id = VECTOR_NETWORK_ID; + env.ledger().set(info); + env + } + + fn vector_payment(env: &Env) -> PaymentSchedule { + PaymentSchedule { + id: 1, + worker: Address::from_string(&SorobanString::from_str(env, VECTOR_WORKER)), + token: Address::from_string(&SorobanString::from_str(env, VECTOR_TOKEN)), + amount: 10000, + start_date: 1000, + end_date: 2000, + hours_logged: 0, + rate_per_hour: 250, + proof_verified: false, + status: PaymentStatus::Pending, + } + } + + /// Link 1 of 2: the Rust preimage layout equals the TypeScript one. #[test] - fn test_cli_generated_signature_is_accepted_onchain() { - // Byte-for-byte output of: - // ORACLE_SECRET_KEY=0102...20 node scripts/oracle-cli.mjs sign batch.json - // for { escrowId: 1, payees: [{ paymentId: 0, hours: 40 }], startNonce: 0 }. - // - // This is the guard against the two systems drifting apart: if either the - // CLI's 32-byte encoding or the contract's reconstruction changes, this - // test fails rather than the mismatch surfacing as an opaque Testnet - // signature rejection during the validation run. - const CLI_SIGNATURE: [u8; 64] = [ - 24, 178, 216, 233, 110, 113, 128, 147, 172, 148, 23, 160, 156, 230, 81, 41, 111, 33, - 50, 78, 143, 140, 222, 254, 242, 193, 212, 137, 148, 225, 47, 85, 160, 136, 252, 244, - 43, 115, 153, 52, 235, 138, 29, 215, 137, 174, 89, 78, 118, 214, 140, 215, 132, 182, - 12, 151, 158, 16, 236, 90, 98, 255, 107, 12, - ]; - // Public key the CLI printed for that same seed. - const CLI_PUBKEY: [u8; 32] = [ - 0x79, 0xb5, 0x56, 0x2e, 0x8f, 0xe6, 0x54, 0xf9, 0x40, 0x78, 0xb1, 0x12, 0xe8, 0xa9, - 0x8b, 0xa7, 0x90, 0x1f, 0x85, 0x3a, 0xe6, 0x95, 0xbe, 0xd7, 0xe0, 0xe3, 0x91, 0x0b, - 0xad, 0x04, 0x96, 0x64, - ]; + fn test_proof_preimage_matches_cross_language_vector() { + let env = vector_env(); + let contract = Address::from_string(&SorobanString::from_str(&env, VECTOR_CONTRACT)); + let payment = vector_payment(&env); + let built = build_oracle_message(&env, &contract, &payment, 1, 0, 40, 0); + + assert_eq!( + built.as_slice(), + VECTOR_MESSAGE.as_slice(), + "Rust preimage diverged from the shared CFWP-v2 vector" + ); + } + + /// The signature in the vector actually verifies against that preimage, so + /// the vector is self-consistent and not merely two copies of one mistake. + #[test] + fn test_vector_signature_verifies_against_vector_message() { + use ed25519_dalek::{Signature, Verifier, VerifyingKey}; + let (signing_key, _) = generate_oracle_keypair(&Env::default()); + let vk: VerifyingKey = signing_key.verifying_key(); + let sig = Signature::from_bytes(&VECTOR_SIGNATURE); + assert!(vk.verify(&VECTOR_MESSAGE, &sig).is_ok()); + } + + /// Link 2 of 2: the CONTRACT's own builder equals the independent Rust one. + /// + /// Combined with link 1, this is what closes the loop: contract == test + /// reimplementation == TypeScript signer. `proof_preimage` is the contract + /// answering "what exactly must be signed", read from stored escrow state. + #[test] + fn test_contract_preimage_matches_independent_implementation() { let env = Env::default(); env.mock_all_auths(); let contract_id = env.register_contract(None, CoreFlowContract); @@ -1396,26 +1544,824 @@ mod tests { let finance = Address::generate(&env); let worker = Address::generate(&env); let token = setup_token(&env, &manager); - - // The Rust helper derives the same key from the same seed β€” assert the - // two languages agree before relying on the signature itself. - let (_sk, derived_pubkey) = generate_oracle_keypair(&env); - assert_eq!(derived_pubkey, BytesN::from_array(&env, &CLI_PUBKEY)); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); let mut payments = Vec::new(&env); payments.push_back(create_test_payment(&env, &worker, &token)); let escrow_id = - client.initialize_multi_sig_escrow(&manager, &finance, &derived_pubkey, &payments); - assert_eq!(escrow_id, 1); // CLI signed escrowId=1 + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); - // The contract accepts the CLI's bytes verbatim. - let cli_sig = BytesN::from_array(&env, &CLI_SIGNATURE); - client.submit_hours_proof(&escrow_id, &0, &40i128, &0u64, &cli_sig); + let from_contract = client.proof_preimage(&escrow_id, &0, &40i128, &0u64); - let escrow = client.get_escrow(&escrow_id); - assert!(escrow.payments.get(0).unwrap().proof_verified); - assert_eq!(escrow.payments.get(0).unwrap().hours_logged, 40); - assert_eq!(client.get_nonce(&escrow_id), 1); + let stored = client.get_escrow(&escrow_id).payments.get(0).unwrap(); + let expected = + build_oracle_message(&env, &contract_id, &stored, escrow_id, 0, 40, 0); + + assert_eq!(from_contract.len(), 198); + assert_eq!( + from_contract, + Bytes::from_slice(&env, &expected), + "contract preimage diverged from the independent implementation" + ); + } + + /// A signature is bound to its network: the identical attestation built for + /// Mainnet does not verify against the Testnet preimage. This is the + /// property v1 lacked, and the reason a Testnet proof could be replayed + /// against a Mainnet deployment. + #[test] + fn test_preimage_is_bound_to_network() { + let env = vector_env(); + let contract = Address::from_string(&SorobanString::from_str(&env, VECTOR_CONTRACT)); + let payment = vector_payment(&env); + let testnet = build_oracle_message(&env, &contract, &payment, 1, 0, 40, 0); + + let mainnet_env = Env::default(); + let mut info = mainnet_env.ledger().get(); + // sha256("Public Global Stellar Network ; September 2015") + info.network_id = [ + 0x7a, 0xc3, 0x39, 0x97, 0x54, 0x4e, 0x31, 0x75, 0xd2, 0x66, 0xbd, 0x02, 0x24, 0x39, + 0xb2, 0x2c, 0xdb, 0x16, 0x50, 0x8c, 0x01, 0x16, 0x3f, 0x26, 0xe5, 0xcb, 0x2a, 0x3e, + 0x10, 0x45, 0xa9, 0x79, + ]; + mainnet_env.ledger().set(info); + let contract_m = + Address::from_string(&SorobanString::from_str(&mainnet_env, VECTOR_CONTRACT)); + let payment_m = vector_payment(&mainnet_env); + let mainnet = build_oracle_message(&mainnet_env, &contract_m, &payment_m, 1, 0, 40, 0); + + assert_ne!( + testnet.as_slice(), + mainnet.as_slice(), + "preimage must differ across networks or Testnet proofs replay on Mainnet" + ); + } + + /// A signature is bound to its payee: retargeting the attestation to a + /// different worker changes the preimage, so the old signature cannot pay + /// someone the oracle never attested to. + #[test] + fn test_preimage_is_bound_to_worker_and_amount() { + let env = vector_env(); + let contract = Address::from_string(&SorobanString::from_str(&env, VECTOR_CONTRACT)); + let base = vector_payment(&env); + let original = build_oracle_message(&env, &contract, &base, 1, 0, 40, 0); + + let mut other_worker = base.clone(); + other_worker.worker = Address::generate(&env); + assert_ne!( + original.as_slice(), + build_oracle_message(&env, &contract, &other_worker, 1, 0, 40, 0).as_slice(), + "preimage must bind the payee" + ); + + let mut other_amount = base.clone(); + other_amount.amount = 20000; + assert_ne!( + original.as_slice(), + build_oracle_message(&env, &contract, &other_amount, 1, 0, 40, 0).as_slice(), + "preimage must bind the amount" + ); + } + + + /// Count published events whose (topic0, topic1) match the given symbols. + /// + /// Written as an explicit loop over the soroban `Vec` rather than iterator + /// chains, because topics are `Val` and comparison needs the env. + fn count_events(env: &Env, t0: &str, t1: &str) -> u32 { + let want0: soroban_sdk::Val = soroban_sdk::Symbol::new(env, t0).into_val(env); + let want1: soroban_sdk::Val = soroban_sdk::Symbol::new(env, t1).into_val(env); + let mut n = 0u32; + let all = env.events().all(); + for i in 0..all.len() { + let (_, topics, _) = all.get(i).unwrap(); + if topics.len() < 2 { + continue; + } + let a = topics.get(0).unwrap(); + let b = topics.get(1).unwrap(); + if a.shallow_eq(&want0) && b.shallow_eq(&want1) { + n += 1; + } + } + n + } + + // ========== PER-PAYMENT EVENTS (indexer determinism) ========== + + /// The event log must be sufficient on its own to reconstruct who was paid + /// what. Reading `get_escrow` at index time returns CURRENT state, not state + /// at that ledger, which makes an off-chain projection unreplayable. + #[test] + fn test_settlement_emits_one_event_per_payment() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker1 = Address::generate(&env); + let worker2 = Address::generate(&env); + let token = setup_token(&env, &manager); + let (sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let payments = create_multi_payments(&env, &worker1, &worker2, &token); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + // Creation emits one `payment/add` per row, carrying that row's identity. + let adds = count_events(&env, "payment", "add"); + assert_eq!(adds, 2, "one payment/add event per payment at creation"); + + prove_all(&env, &client, &contract_id, &sk, escrow_id); + client.manager_approve(&escrow_id); + client.finance_approve(&escrow_id); + client.pay_batch(&escrow_id); + + let paid = count_events(&env, "payment", "paid"); + assert_eq!(paid, 2, "one payment/paid event per settled payment"); + } + + /// Cancellation must also be per-payment, so each payment can reach a + /// terminal state off-chain from the log alone. + #[test] + fn test_cancellation_emits_one_event_per_payment() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker1 = Address::generate(&env); + let worker2 = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let payments = create_multi_payments(&env, &worker1, &worker2, &token); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + client.cancel_escrow(&escrow_id); + + let cancels = count_events(&env, "payment", "cancel"); + assert_eq!(cancels, 2, "one payment/cancel event per payment"); + } + + // ========== ADMIN LIFECYCLE / FRONT-RUNNING (F-9) ========== + + /// This test build carries no COREFLOW_ADMIN pin, so `expected_admin` is + /// None and `init_admin` keeps first-caller behaviour. Asserting it here + /// documents WHY the front-running tests below look the way they do, and + /// fails loudly if someone bakes a pin into the test profile. + #[test] + fn test_test_builds_are_unpinned() { + let env = Env::default(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + assert_eq!(client.expected_admin(), None); + } + + /// An attacker who wins the race to `init_admin` locks out the real + /// operator, so the deploy sequence must be treated as adversarial. This + /// pins the exact consequence the build-time pin exists to remove. + #[test] + fn test_init_admin_is_first_caller_wins_without_a_pin() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let attacker = Address::generate(&env); + let real_operator = Address::generate(&env); + + client.init_admin(&attacker); + + // The intended operator is now permanently locked out of an unpinned + // deployment. A production build sets COREFLOW_ADMIN so that the + // attacker's call fails with AdminMismatch instead. + assert_eq!( + client.try_init_admin(&real_operator), + Err(Ok(ContractError::AdminAlreadySet)) + ); + assert_eq!(client.get_admin(), Some(attacker)); + } + + /// Admin handover is two-step: a single-step transfer to a mistyped or + /// uncontrolled address would permanently destroy pause, upgrade and + /// registry control. + #[test] + fn test_admin_handover_requires_acceptance() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let next = Address::generate(&env); + client.init_admin(&admin); + + client.propose_admin(&next); + // Proposing alone changes nothing β€” the old admin still holds the role. + assert_eq!(client.get_admin(), Some(admin)); + + client.accept_admin(); + assert_eq!(client.get_admin(), Some(next)); + + // The handover is consumed; it cannot be replayed to seize the role back. + assert_eq!(client.try_accept_admin(), Err(Ok(ContractError::NoPendingAdmin))); + } + + #[test] + fn test_accept_admin_without_a_proposal_fails() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + + assert_eq!(client.try_accept_admin(), Err(Ok(ContractError::NoPendingAdmin))); + } + + #[test] + fn test_accept_admin_requires_the_proposed_address_to_sign() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let next = Address::generate(&env); + client.init_admin(&admin); + client.propose_admin(&next); + client.accept_admin(); + + // Authorization is asserted via env.auths() rather than by calling + // unauthorized: a failed require_auth is a non-unwinding host trap that + // #[should_panic] cannot catch in native cargo test. + let auths = env.auths(); + assert_eq!(auths.len(), 1); + assert_eq!(auths.get(0).unwrap().0, next, "only the proposed admin may accept"); + } + + /// Upgrading replaces the code holding every escrow's custody. Requiring a + /// pause first makes it a deliberate two-transaction sequence with an + /// observable event in between, rather than a silent single call. + #[test] + fn test_upgrade_requires_pause_first() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + + let wasm_hash = BytesN::from_array(&env, &[7u8; 32]); + assert_eq!( + client.try_upgrade(&wasm_hash), + Err(Ok(ContractError::NotPaused)) + ); + } + + // ========== UPGRADE AUTHORITY ========== + // + // `upgrade` replaces the code that holds every escrow's custody, so it is the + // single most consequential entry point in the contract. These tests pin the + // exact conditions, because "we tested it once by hand" is not evidence for a + // mechanism that controls funds. + + /// Upgrading demands the admin's signature, and no one else's. + /// + /// Asserted via `env.auths()` rather than by calling unauthorized: a failed + /// `require_auth` is a non-unwinding host trap that `#[should_panic]` cannot + /// catch in native `cargo test`. + #[test] + fn test_upgrade_requires_admin_authorization() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + client.init_admin(&admin); + client.set_paused(&true); + + // A hash of the currently-installed WASM keeps the call valid while we + // inspect who had to authorize it. + // Uploading a real WASM blob costs far more than a normal contract call, + // so the default test budget has to be lifted to exercise `upgrade` at all. + env.budget().reset_unlimited(); + let wasm_hash = env.deployer().upload_contract_wasm(CURRENT_WASM); + client.upgrade(&wasm_hash); + + let auths = env.auths(); + assert_eq!(auths.len(), 1, "upgrade must require exactly one signer"); + assert_eq!(auths.get(0).unwrap().0, admin, "that signer must be the admin"); + assert_ne!(auths.get(0).unwrap().0, manager, "a manager must not authorize an upgrade"); + } + + /// An admin-less contract cannot be upgraded at all β€” there is no authority + /// to satisfy. A deployment that never calls `init_admin` is immutable. + #[test] + fn test_upgrade_impossible_without_an_admin() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let wasm_hash = BytesN::from_array(&env, &[1u8; 32]); + assert_eq!(client.try_upgrade(&wasm_hash), Err(Ok(ContractError::NotAdmin))); + } + + /// Pause is mandatory, and it is checked BEFORE anything is replaced. + #[test] + fn test_upgrade_pause_is_mandatory_and_checked_first() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + + // Uploading a real WASM blob costs far more than a normal contract call, + // so the default test budget has to be lifted to exercise `upgrade` at all. + env.budget().reset_unlimited(); + let wasm_hash = env.deployer().upload_contract_wasm(CURRENT_WASM); + + assert_eq!(client.try_upgrade(&wasm_hash), Err(Ok(ContractError::NotPaused))); + // And unpausing again does not leave a latent permission behind. + client.set_paused(&true); + client.set_paused(&false); + assert_eq!(client.try_upgrade(&wasm_hash), Err(Ok(ContractError::NotPaused))); + } + + /// An upgrade is observable. A silent replacement of the code holding custody + /// would leave monitoring nothing to alert on. + #[test] + fn test_upgrade_emits_an_event_carrying_the_wasm_hash() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + client.set_paused(&true); + + // Uploading a real WASM blob costs far more than a normal contract call, + // so the default test budget has to be lifted to exercise `upgrade` at all. + env.budget().reset_unlimited(); + let wasm_hash = env.deployer().upload_contract_wasm(CURRENT_WASM); + client.upgrade(&wasm_hash); + + assert_eq!(count_events(&env, "admin", "upgrade"), 1); + // The pause that necessarily preceded it is observable too, so the whole + // sequence is reconstructable from the log. + assert!(count_events(&env, "admin", "paused") >= 1); + } + + /// Escrow state survives an upgrade: custody, approvals and payment rows are + /// all untouched. An upgrade that silently reset them would be catastrophic. + #[test] + fn test_upgrade_preserves_escrow_state_and_custody() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (sk, oracle_pubkey) = generate_oracle_keypair(&env); + + client.init_admin(&admin); + client.register_oracle_key(&oracle_pubkey); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + prove_all(&env, &client, &contract_id, &sk, escrow_id); + client.manager_approve(&escrow_id); + + let custody_before = balance_of(&env, &token, &contract_id); + let nonce_before = client.get_nonce(&escrow_id); + assert_eq!(custody_before, 10000); + + client.set_paused(&true); + // Uploading a real WASM blob costs far more than a normal contract call, + // so the default test budget has to be lifted to exercise `upgrade` at all. + env.budget().reset_unlimited(); + let wasm_hash = env.deployer().upload_contract_wasm(CURRENT_WASM); + client.upgrade(&wasm_hash); + client.set_paused(&false); + + // Everything that matters is still there. + assert_eq!(balance_of(&env, &token, &contract_id), custody_before); + assert_eq!(client.get_nonce(&escrow_id), nonce_before); + let escrow = client.get_escrow(&escrow_id); + assert!(escrow.manager_approved); + assert!(!escrow.finance_approved); + assert!(escrow.payments.get(0).unwrap().proof_verified); + assert_eq!(client.get_admin(), Some(admin)); + assert!(client.is_oracle_key_registered(&oracle_pubkey)); + + // And the escrow still settles afterwards. + client.finance_approve(&escrow_id); + client.pay_batch(&escrow_id); + assert_eq!(balance_of(&env, &token, &worker), 10000); + assert_eq!(balance_of(&env, &token, &contract_id), 0); + } + + /// `cancel_escrow` remains callable while paused, so an upgrade window can + /// never trap a manager's funds: if an operator pauses and walks away, refunds + /// are still available. + #[test] + fn test_pause_for_upgrade_does_not_trap_funds() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + client.init_admin(&admin); + client.register_oracle_key(&oracle_pubkey); + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + client.set_paused(&true); + client.cancel_escrow(&escrow_id); + + assert_eq!(balance_of(&env, &token, &manager), MINT_AMOUNT); + assert_eq!(balance_of(&env, &token, &contract_id), 0); + } + + /// An admin cannot install a WASM hash that was never uploaded: the host + /// refuses it. So naming a wrong hash fails the transaction rather than + /// bricking the contract β€” the upgrade simply does not happen. + /// + /// The refusal comes from BELOW the contract (the host, not a ContractError), + /// which is why this is a `should_panic` rather than a `try_` assertion. + #[test] + #[ignore = "host rejects an unuploaded WASM hash with a non-unwinding trap, which \ +#[should_panic] cannot catch in native cargo test (same limitation as \ +test_wrong_oracle_key_rejected). Verified against live Testnet instead \ +β€” see docs/evidence/REVIEWER_EVIDENCE.md, upgrade authority."] + fn test_upgrade_to_an_unknown_wasm_hash_is_refused() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + client.set_paused(&true); + + let bogus = BytesN::from_array(&env, &[0xABu8; 32]); + client.upgrade(&bogus); + } + + // ========== STORAGE LIFETIME (F-13) ========== + + /// A funded escrow must not depend on the manager staying reachable to keep + /// its storage alive β€” the worker awaiting payment has the strongest + /// interest and no authority, so the keep-alive is permissionless. + #[test] + fn test_anyone_can_extend_escrow_ttl() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + // No auth mock consumed, and no authorization demanded: a keeper bot + // holding no role can pay the rent. + env.set_auths(&[]); + client.extend_escrow_ttl(&escrow_id); + + assert!(env.auths().is_empty(), "keep-alive must require no signer"); + // The escrow is untouched β€” this only buys storage lifetime. + assert_eq!(client.get_escrow(&escrow_id).manager, manager); + } + + #[test] + fn test_extend_ttl_on_unknown_escrow_fails() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + // Nobody should be charged rent for a key that holds nothing. + assert_eq!( + client.try_extend_escrow_ttl(&999), + Err(Ok(ContractError::InvalidPaymentId)) + ); + } + + // ========== ORACLE KEY REGISTRY (F-7) ========== + + /// A manager may no longer name an arbitrary oracle. Before the registry, + /// they could install their own key and sign their own "verified work", + /// which made the proof-of-work gate manager-attestable. + #[test] + fn test_escrow_rejects_unregistered_oracle_key() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, rogue_key) = generate_oracle_keypair(&env); + + client.init_admin(&admin); + // Deliberately NOT registered. + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let res = client.try_initialize_multi_sig_escrow(&manager, &finance, &rogue_key, &payments); + + assert_eq!(res, Err(Ok(ContractError::OracleKeyNotRegistered))); + // And no custody was pulled for a rejected escrow. + assert_eq!(balance_of(&env, &token, &contract_id), 0); + assert_eq!(balance_of(&env, &token, &manager), MINT_AMOUNT); + } + + /// Rotation cannot be used as a back door around the registry. + #[test] + fn test_rotation_to_unregistered_key_rejected() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + client.init_admin(&admin); + client.register_oracle_key(&oracle_pubkey); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + let rogue = BytesN::from_array(&env, &[9u8; 32]); + let res = client.try_rotate_oracle_key(&escrow_id, &rogue); + + assert_eq!(res, Err(Ok(ContractError::OracleKeyNotRegistered))); + assert_eq!(client.get_escrow(&escrow_id).oracle_pubkey, oracle_pubkey); + } + + /// Revocation stops a key being named by NEW escrows. It is deliberately not + /// retroactive -- invalidating in-flight attestations would strand escrows + /// that are already funded. + #[test] + fn test_revoked_key_cannot_back_a_new_escrow() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + client.init_admin(&admin); + client.register_oracle_key(&oracle_pubkey); + assert!(client.is_oracle_key_registered(&oracle_pubkey)); + + client.revoke_oracle_key(&oracle_pubkey); + assert!(!client.is_oracle_key_registered(&oracle_pubkey)); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let res = + client.try_initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + assert_eq!(res, Err(Ok(ContractError::OracleKeyNotRegistered))); + } + + /// Registration is admin-only -- otherwise the registry would be decorative. + /// + /// Asserted by inspecting the authorizations the contract DEMANDED rather + /// than by calling unauthorized and catching a panic: a failed + /// `require_auth` is a non-unwinding host trap, which `#[should_panic]` + /// cannot catch in native `cargo test` (the same limitation documented on + /// `test_wrong_oracle_key_rejected`). Checking `env.auths()` proves the + /// admin address had to sign, which is the property that matters. + #[test] + fn test_register_oracle_key_requires_admin_authorization() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let manager = Address::generate(&env); + client.init_admin(&admin); + + let (_sk, key) = generate_oracle_keypair(&env); + client.register_oracle_key(&key); + + let auths = env.auths(); + assert_eq!(auths.len(), 1, "register_oracle_key must require exactly one signer"); + assert_eq!(auths.get(0).unwrap().0, admin, "that signer must be the admin"); + assert_ne!( + auths.get(0).unwrap().0, + manager, + "a manager must not be able to authorize registry changes" + ); + } + + /// Revocation is likewise admin-gated. + #[test] + fn test_revoke_oracle_key_requires_admin_authorization() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.init_admin(&admin); + let (_sk, key) = generate_oracle_keypair(&env); + client.register_oracle_key(&key); + + client.revoke_oracle_key(&key); + + let auths = env.auths(); + assert_eq!(auths.len(), 1); + assert_eq!(auths.get(0).unwrap().0, admin); + } + + /// An admin-less deployment keeps the v1 trust model rather than bricking: + /// with no registry authority, no key could ever satisfy the check. + #[test] + fn test_admin_less_contract_accepts_any_oracle_key() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + assert_eq!(escrow_id, 1); + } + + // ========== WORK / AMOUNT INVARIANT (F-8) ========== + + /// Attested hours must justify the escrowed amount exactly. Previously + /// `hours_logged` was decorative: the oracle could attest to any figure + /// while the pre-funded `amount` paid out regardless. + #[test] + fn test_hours_must_match_escrowed_amount() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (signing_key, oracle_pubkey) = generate_oracle_keypair(&env); + + let mut payments = Vec::new(&env); + payments.push_back(create_test_payment(&env, &worker, &token)); // 10000 @ 250 = 40h + let escrow_id = + client.initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + + // A perfectly valid signature over 80 hours -- the oracle really did sign + // this. It is refused because 80 x 250 != 10000. + let sig = + sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, 80, 0); + let res = client.try_submit_hours_proof(&escrow_id, &0, &80i128, &0u64, &sig); + + assert_eq!(res, Err(Ok(ContractError::AmountHoursMismatch))); + assert!(!client.get_escrow(&escrow_id).payments.get(0).unwrap().proof_verified); + } + + /// An amount that no whole number of hours can reach is refused at creation, + /// rather than funding custody into an escrow that can never settle. + #[test] + fn test_amount_not_divisible_by_rate_rejected_at_creation() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let mut p = create_test_payment(&env, &worker, &token); + p.amount = 10_001; // 10001 / 250 is not a whole number of hours + let mut payments = Vec::new(&env); + payments.push_back(p); + + let res = + client.try_initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + assert_eq!(res, Err(Ok(ContractError::AmountHoursMismatch))); + assert_eq!(balance_of(&env, &token, &contract_id), 0); + } + + // ========== BATCH / PERIOD BOUNDS ========== + + #[test] + fn test_batch_over_cap_rejected() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + // 101 rows: one past MAX_BATCH_SIZE. An unbounded Vec would eventually + // exceed the ledger resource limits and strand the escrow's custody. + let mut payments = Vec::new(&env); + for _ in 0..101 { + let worker = Address::generate(&env); + let mut p = create_test_payment(&env, &worker, &token); + p.amount = 250; // 1 hour, keeps the funding total small + payments.push_back(p); + } + + let res = + client.try_initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + assert_eq!(res, Err(Ok(ContractError::BatchTooLarge))); + } + + #[test] + fn test_inverted_pay_period_rejected() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, CoreFlowContract); + let client = CoreFlowContractClient::new(&env, &contract_id); + + let manager = Address::generate(&env); + let finance = Address::generate(&env); + let worker = Address::generate(&env); + let token = setup_token(&env, &manager); + let (_sk, oracle_pubkey) = generate_oracle_keypair(&env); + + let mut p = create_test_payment(&env, &worker, &token); + p.start_date = 2000; + p.end_date = 1000; // inverted + let mut payments = Vec::new(&env); + payments.push_back(p); + + let res = + client.try_initialize_multi_sig_escrow(&manager, &finance, &oracle_pubkey, &payments); + assert_eq!(res, Err(Ok(ContractError::InvalidPeriod))); } // ========== PROPERTY / FUZZ ========== @@ -1450,6 +2396,9 @@ mod tests { let mut payments = Vec::new(&env); let (mut total_a, mut total_b) = (0i128, 0i128); for i in 0..n { + // rate_per_hour is 1 below, so `hours == amount` and the + // contract's `hours x rate == amount` invariant holds for any + // positive integer drawn here. let amount = ((next() % 100_000) + 1) as i128; // 1..=100000, always positive let use_a = next() % 2 == 0; if use_a { @@ -1485,7 +2434,7 @@ mod tests { assert_eq!(balance_of(&env, &token_a, &manager), MINT_AMOUNT - total_a); assert_eq!(balance_of(&env, &token_b, &manager), MINT_AMOUNT - total_b); - prove_all(&env, &client, &sk, escrow_id, n as u32); + prove_all(&env, &client, &contract_id, &sk, escrow_id); client.manager_approve(&escrow_id); client.finance_approve(&escrow_id); let finalized = client.pay_batch(&escrow_id); @@ -1527,8 +2476,11 @@ mod tests { for offset in 0..10u32 { let user_index = batch * 10 + offset; let worker = Address::generate(&env); - let amount = 1_000 + user_index as i128; - let hours = 40 + user_index as i128; + // Whole hours x rate: the contract enforces + // `hours x rate_per_hour == amount`, so a payroll row is + // defined by hours worked, not by an arbitrary sum. + let hours = 40 + (user_index as i128 % 8); // 40..47 hours + let amount = hours * 25; let mut payments = Vec::new(&env); payments.push_back(PaymentSchedule { id: 1, @@ -1549,7 +2501,7 @@ mod tests { &oracle_pubkey, &payments, ); - let signature = sign_oracle_proof(&env, &signing_key, escrow_id, 0, hours, 0); + let signature = sign_oracle_proof(&env, &client, &contract_id, &signing_key, escrow_id, 0, hours, 0); client.submit_hours_proof(&escrow_id, &0, &hours, &0, &signature); client.manager_approve(&escrow_id); @@ -1576,6 +2528,10 @@ mod tests { total_funded += batch_funded; } - assert_eq!(total_funded, 50 * 1_000 + (50 * 49) / 2); + // 50 workers, hours cycling 40..47 at 25/hour: six full 40..47 cycles + // (48 workers) plus indices 48,49 at 40 and 41 hours. + let expected_hours: i128 = (0..50i128).map(|i| 40 + i % 8).sum(); + assert_eq!(expected_hours, 2_169); + assert_eq!(total_funded, expected_hours * 25); } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_requires_the_proposed_address_to_sign.1.json b/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_requires_the_proposed_address_to_sign.1.json new file mode 100644 index 0000000..1df72b7 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_requires_the_proposed_address_to_sign.1.json @@ -0,0 +1,448 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "accept_admin", + "args": [] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "propose_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "propose" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "propose_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "accept" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_without_a_proposal_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_without_a_proposal_fails.1.json new file mode 100644 index 0000000..1f4a89d --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_accept_admin_without_a_proposal_fails.1.json @@ -0,0 +1,320 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_admin" + } + ], + "data": { + "error": { + "contract": 21 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 21 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 21 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "accept_admin" + }, + { + "vec": [] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_admin_can_be_set_once.1.json b/contracts/core-flow/test_snapshots/test/tests/test_admin_can_be_set_once.1.json index b013e7a..12cd62c 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_admin_can_be_set_once.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_admin_can_be_set_once.1.json @@ -163,6 +163,29 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_admin_handover_requires_acceptance.1.json b/contracts/core-flow/test_snapshots/test/tests/test_admin_handover_requires_acceptance.1.json new file mode 100644 index 0000000..b8f4f87 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_admin_handover_requires_acceptance.1.json @@ -0,0 +1,654 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "accept_admin", + "args": [] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "propose_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "propose" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "propose_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "accept" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_admin" + } + ], + "data": { + "error": { + "contract": 21 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 21 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 21 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "accept_admin" + }, + { + "vec": [] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_admin_less_contract_accepts_any_oracle_key.1.json b/contracts/core-flow/test_snapshots/test/tests/test_admin_less_contract_accepts_any_oracle_key.1.json new file mode 100644 index 0000000..52a9128 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_admin_less_contract_accepts_any_oracle_key.1.json @@ -0,0 +1,1485 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_amount_not_divisible_by_rate_rejected_at_creation.1.json b/contracts/core-flow/test_snapshots/test/tests/test_amount_not_divisible_by_rate_rejected_at_creation.1.json new file mode 100644 index 0000000..b86918e --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_amount_not_divisible_by_rate_rejected_at_creation.1.json @@ -0,0 +1,986 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10001 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "error": { + "contract": 17 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 17 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 17 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "initialize_multi_sig_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10001 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_anyone_can_extend_escrow_ttl.1.json b/contracts/core-flow/test_snapshots/test/tests/test_anyone_can_extend_escrow_ttl.1.json new file mode 100644 index 0000000..3d195af --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_anyone_can_extend_escrow_ttl.1.json @@ -0,0 +1,1772 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 6311999 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "extend_escrow_ttl" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "ttl" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 6312000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "extend_escrow_ttl" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_approve_cancelled_escrow_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_approve_cancelled_escrow_fails.1.json index 16ac9fc..9433b63 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_approve_cancelled_escrow_fails.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_approve_cancelled_escrow_fails.1.json @@ -1457,6 +1457,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1598,6 +1652,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_batch_over_cap_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_batch_over_cap_rejected.1.json new file mode 100644 index 0000000..933d462 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_batch_over_cap_rejected.1.json @@ -0,0 +1,19533 @@ +{ + "generators": { + "address": 106, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000005" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB6KO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDWC6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFO3O" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHGT6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABI7IO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKXA6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMPZO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOHR6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR4OP" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABTUG7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVM7P" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABXEX7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABY5MP" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2VE7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4N5P" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6FV7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBKTY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDC3I" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACF2CY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHSKI" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACILRY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKDZI" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACM3AY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOTII" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRIXZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACTA7J" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVYGZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXQOJ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYJVZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2B5J" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4ZEZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6RMJ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBG3K" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDOT2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADFWKK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADH6C2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIHZK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKPR2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMXIK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADO7A2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRE7L" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADTMX3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVUOL" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX4G3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYF5L" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2NV3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4VML" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD65E3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEADAU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECLIE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEETRU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEG3ZE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJCCU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAELKKE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENSTU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEP23E" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQBEV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAESJMF" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEURVV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWZ5F" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEZAGV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE3IOF" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5QXV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE7Y7F" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAPIG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCHAW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFE7ZG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFGXRW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFJOKG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLGCW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFN63G" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFPWTW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQNMH" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSFEX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFU55H" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFWVVX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFZMOH" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF3EGX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF547H" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF7UXX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA3RQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCTZA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGELAQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGDIA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGJ2TQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGLS3A" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNKCQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGPCKA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQZVR" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGSR5B" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGUJER" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "error": { + "contract": 19 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 19 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 19 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "initialize_multi_sig_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB6KO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDWC6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFO3O" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHGT6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABI7IO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKXA6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMPZO" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOHR6" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR4OP" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABTUG7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVM7P" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABXEX7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABY5MP" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2VE7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4N5P" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6FV7" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBKTY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDC3I" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACF2CY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHSKI" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACILRY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKDZI" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACM3AY" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOTII" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRIXZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACTA7J" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVYGZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXQOJ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYJVZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2B5J" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4ZEZ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6RMJ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBG3K" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDOT2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADFWKK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADH6C2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIHZK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKPR2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMXIK" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADO7A2" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADRE7L" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADTMX3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVUOL" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX4G3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYF5L" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2NV3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4VML" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD65E3" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEADAU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECLIE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEETRU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEG3ZE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJCCU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAELKKE" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENSTU" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEP23E" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQBEV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAESJMF" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEURVV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWZ5F" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEZAGV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE3IOF" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5QXV" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE7Y7F" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAPIG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCHAW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFE7ZG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFGXRW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFJOKG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFLGCW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFN63G" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFPWTW" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQNMH" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSFEX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFU55H" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFWVVX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFZMOH" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF3EGX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF547H" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF7UXX" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA3RQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCTZA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGELAQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGDIA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGJ2TQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGLS3A" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNKCQ" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGPCKA" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQZVR" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGSR5B" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGUJER" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancel_allowed_while_paused.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancel_allowed_while_paused.1.json index 052e922..9d9cd11 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_cancel_allowed_while_paused.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancel_allowed_while_paused.1.json @@ -67,6 +67,25 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", @@ -613,6 +632,51 @@ 1555200 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], [ { "contract_data": { @@ -709,7 +773,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", "key": { "ledger_key_nonce": { - "nonce": 2032731177588607455 + "nonce": 4270020994084947596 } }, "durability": "temporary" @@ -724,7 +788,40 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", "key": { "ledger_key_nonce": { - "nonce": 2032731177588607455 + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 } }, "durability": "temporary", @@ -742,7 +839,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 2032731177588607455 } }, "durability": "temporary" @@ -757,7 +854,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 2032731177588607455 } }, "durability": "temporary", @@ -775,7 +872,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 8370022561469687789 } }, "durability": "temporary" @@ -790,7 +887,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 8370022561469687789 } }, "durability": "temporary", @@ -1352,6 +1449,29 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1373,6 +1493,76 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1634,6 +1824,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1845,6 +2089,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancel_escrow.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancel_escrow.1.json index 54e1e50..eadc135 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_cancel_escrow.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancel_escrow.1.json @@ -1457,6 +1457,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1598,6 +1652,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancel_finalized_escrow_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancel_finalized_escrow_fails.1.json index 10a57be..cced16c 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_cancel_finalized_escrow_fails.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancel_finalized_escrow_fails.1.json @@ -195,6 +195,9 @@ ] ], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1635,6 +1638,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1658,6 +1715,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1694,7 +2216,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -2018,6 +2540,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_each_asset_separately.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_each_asset_separately.1.json index 4b985a3..b42b9c2 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_each_asset_separately.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_each_asset_separately.1.json @@ -2459,6 +2459,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 3000 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 4000 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2692,6 +2800,66 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_manager.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_manager.1.json index d1007a8..e849000 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_manager.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancel_refunds_manager.1.json @@ -1459,6 +1459,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1652,6 +1706,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_cancellation_emits_one_event_per_payment.1.json b/contracts/core-flow/test_snapshots/test/tests/test_cancellation_emits_one_event_per_payment.1.json new file mode 100644 index 0000000..bddd15d --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_cancellation_emits_one_event_per_payment.1.json @@ -0,0 +1,2092 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "cancel_escrow", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 13000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "cancel_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 13000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "cancel" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "cancel_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_contract_preimage_matches_independent_implementation.1.json b/contracts/core-flow/test_snapshots/test/tests/test_contract_preimage_matches_independent_implementation.1.json new file mode 100644 index 0000000..6d0b042 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_contract_preimage_matches_independent_implementation.1.json @@ -0,0 +1,1760 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "proof_preimage" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "proof_preimage" + } + ], + "data": { + "bytes": "434657500002000000000000000000000000000000000000000000000000000000000000000028415d19553c5d590306a4429847180a867cfcea9393db6b5a0e0635075ea93603b07df31ca4bcd5754b472170dd2e99570c11b0f73d632d4ad64170ddff48e3c99fe05630194fe88ba3573dffbdb9cd8e97642345dbd68095cb884b659d4aae0000000100000000000000000000000000000000000027100000000000000000000000000000002800000000000003e800000000000007d00000000000000000" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.1.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.1.json index 18214d3..ff5b6ff 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.1.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 72679 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 72679 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 72679 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 72679 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 72679 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "dfddccaf2cf3d806865b7af32b1e3c0cc717afd96959e254d1047755f6348c50efc74e2c2d642c50cf49d59dbab735ac0088bcfc55aeefa601ecd1800651d70f" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 72679 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 72679 + } + }, + { + "i128": { + "hi": 0, + "lo": 72679 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 72679 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.10.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.10.json index 2c6abcc..e5455dd 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.10.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.10.json @@ -454,6 +454,13 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -779,7 +786,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 37860 } } }, @@ -872,7 +879,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 13664 } } }, @@ -965,7 +972,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56318 } } }, @@ -3076,19 +3083,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 37860 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3098,23 +3136,105 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "payment" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 13664 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56318 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3124,7 +3244,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -3133,14 +3253,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 94178 - } + "u32": 1 } } } @@ -3159,7 +3276,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3176,7 +3293,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3191,7 +3308,7 @@ "data": { "i128": { "hi": 0, - "lo": 13664 + "lo": 94178 } } } @@ -3211,14 +3328,14 @@ "symbol": "fn_call" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -3228,7 +3345,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -3243,7 +3360,7 @@ "data": { "i128": { "hi": 0, - "lo": 905822 + "lo": 13664 } } } @@ -3263,7 +3380,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3280,7 +3397,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3295,7 +3412,7 @@ "data": { "i128": { "hi": 0, - "lo": 986336 + "lo": 905822 } } } @@ -3315,33 +3432,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -3351,33 +3449,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 986336 + } } } } @@ -3387,19 +3475,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -3408,56 +3501,1412 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13664 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56318 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13664 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56318 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 37860 + } + }, + { + "u64": 0 + }, + { + "bytes": "d088070da7e81df0a215f933293a1be54c6ebbc5691f2064d72d2be6fd227560a5b3ad22329b4b2cdf0d5aec07d7ba816dfbe312867718f0b0f0de846f205703" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 37860 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13664 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56318 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 13664 + } + }, + { + "u64": 1 + }, + { + "bytes": "3b4882005f3d1ec785cc44caa1f9797710d83b7b51a2416855bfa3ef59c48506bd68bfa91617413b2a8095557ba8f4a32feb36998c04c63737147527909d1007" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ { "symbol": "hours" }, @@ -3476,7 +4925,471 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 13664 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37860 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13664 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13664 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56318 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -3486,27 +5399,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3536,14 +5428,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 56318 } }, { "u64": 2 }, { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" + "bytes": "815540b32c25c154926e71a1a66f8d34eeedc214a757ae1b7b02cce9098c82a22c89b5c58853d4eb282800ea67ba654b79fd176f49eea71fbbb02446d7c0c604" } ] } @@ -3578,7 +5470,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 56318 } } ] @@ -3867,6 +5759,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 37860 + } + }, + { + "i128": { + "hi": 0, + "lo": 37860 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3959,6 +5899,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 13664 + } + }, + { + "i128": { + "hi": 0, + "lo": 13664 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4051,6 +6039,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56318 + } + }, + { + "i128": { + "hi": 0, + "lo": 56318 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4132,7 +6168,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 37860 } } }, @@ -4225,7 +6261,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 13664 } } }, @@ -4318,7 +6354,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56318 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.11.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.11.json index 7ec3f5d..0238a2a 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.11.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.11.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 9045 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 9045 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9045 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9045 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 9045 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "cb0741653ad6c8d64957dcc2047500f230e2bb5aff9ee25383432195e23585418e15eb06d1aa771dae81b327b7904242e6154e6116d9ed541b39b0d667735609" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 9045 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 9045 + } + }, + { + "i128": { + "hi": 0, + "lo": 9045 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 9045 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.12.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.12.json index a8ca0cf..5caa86d 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.12.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.12.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 49565 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 49565 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49565 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49565 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 49565 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "4195eb2493b3d4b5e0b26ae0d33054c88470df579e7dfb6ce87c45b1605714be62bde9f0ec27532e6a0657082bd8d42021329a2a38055f51743245c104a5560a" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 49565 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 49565 + } + }, + { + "i128": { + "hi": 0, + "lo": 49565 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 49565 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.13.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.13.json index 0b6725c..a5d09fb 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.13.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.13.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 14923 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 14923 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 14923 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 14923 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 14923 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "463c6dff5a8a12051b206db9440d46cd84f0fae0020e0b6e5921f2206370975b51dd0b0c71dec0c0f06831a9fb6fc160867c34964bacab297dea40975e3b1002" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 14923 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 14923 + } + }, + { + "i128": { + "hi": 0, + "lo": 14923 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 14923 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.14.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.14.json index af22fb5..40e9eb7 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.14.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.14.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 16941 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 93165 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56587 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 81166 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 16941 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 93165 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56587 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 81166 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 81166 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 81166 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 16941 + } + }, + { + "u64": 0 + }, + { + "bytes": "b1638832a099102548e49acb18eb3122d19fa27829b88d558ec7c58f603760206efd4948e5b864e8f69099809dfe4096f393d8d79977ab3a8c495131c8b17709" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 16941 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 81166 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 93165 + } + }, + { + "u64": 1 + }, + { + "bytes": "dcd1b00d63112ae4c5f8d3412c48bba190478166a5f1d3e4c6ecaa189579f817665d93081dae89cd49826e06607b7d79cfd5828b15bb705b4fe449f5951aa80f" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 93165 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 81166 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 56587 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "027b9f822fb4c01d9e0e95a7a1225da80cca3ed425fb2116e10fddd56e35c7f7a019db0fc5deb5a9ba454069d8c879c6df9010d890829fd03263e3998871b00c" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 56587 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 16941 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 93165 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56587 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 81166 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 81166 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "c57aec85ff846103bd701a2ed5f1d9cb4ac16daabdc87801fd75e11881db8ecf2d93af94140068185f5b8478fc29c4762d5f90d4461e18cd6fd291a044a7b601" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 81166 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 16941 + } + }, + { + "i128": { + "hi": 0, + "lo": 16941 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 93165 + } + }, + { + "i128": { + "hi": 0, + "lo": 93165 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56587 + } + }, + { + "i128": { + "hi": 0, + "lo": 56587 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 81166 + } + }, + { + "i128": { + "hi": 0, + "lo": 81166 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 16941 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 93165 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56587 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 81166 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.15.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.15.json index 57e463e..6334248 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.15.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.15.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 39328 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 89370 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 99881 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 36398 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 39328 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 89370 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 99881 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 36398 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 36398 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 36398 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 39328 + } + }, + { + "u64": 0 + }, + { + "bytes": "f698349f081093d4d7bf0ed2883d23b7d4f9a8071f8deeaab48a2cd286027a5285af28cb32ce38fd4fa4e1bba90427f6a44b331312168c63d7bbe34f9cfe0f08" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 39328 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 36398 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 89370 + } + }, + { + "u64": 1 + }, + { + "bytes": "1d38c85cc113fa93716335e07d107cafbdeb488602f6617cca868530ec9e9acf1cc4d518411c81bcb5cb24c054837c740ad03e9ef24efc794a8fb6aa318f180b" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 89370 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 36398 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 99881 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "f58499f73b35c48813fdae930467b9a7b9265910777aa92a5121bf1bef67322685c7ed4055beea9d938ba2707d50f8e13b3ea928e8341623ef57f27182416007" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 99881 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 39328 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 89370 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 99881 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 36398 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 36398 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "a57d5ee533785fd30d62ed5c99de4270ba66b2fe6664f9d4709e6f091a2fe711f38fc1408214f26ed5553532b2c076c494b45bca59d41f93dbbc64478ed46300" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 36398 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 39328 + } + }, + { + "i128": { + "hi": 0, + "lo": 39328 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 89370 + } + }, + { + "i128": { + "hi": 0, + "lo": 89370 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 99881 + } + }, + { + "i128": { + "hi": 0, + "lo": 99881 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 36398 + } + }, + { + "i128": { + "hi": 0, + "lo": 36398 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 39328 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 89370 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 99881 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 36398 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.16.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.16.json index 4449803..45ce6a6 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.16.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.16.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 61280 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 95674 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 41026 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 8213 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 61280 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 95674 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 41026 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8213 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8213 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8213 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 61280 + } + }, + { + "u64": 0 + }, + { + "bytes": "e6e8232f60a67b1ad223839d410847fa84bcfc07a6797c1b3ee8459d6999b9157c258c973a1c83ae0c396a1721b8eb0805abb9bde9f35551a6a7289633e61200" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 61280 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8213 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 95674 + } + }, + { + "u64": 1 + }, + { + "bytes": "0c0a361954313f4cf5532ce47bc161b621e736a7558f97ecce269e0e73a19d64959c6e8747573f64214481850d3ec11a980ae2905cd142c312b38f6b2150d00a" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 95674 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8213 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 41026 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "8578f5191eb278bb42df2492065b5084ceac69b85b4f189915b3d687ac4dda7f6aec9ac3bfab73cd2dda7a7ca73418338d5d9a07a6c97c10f4273d9077669e06" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 41026 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61280 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 95674 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41026 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8213 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 8213 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "d3444a7b0a3cd355bb5aa4815aa4569c19e2d34d9a5304dd57ae8bcd0539975554a365bd0e87932f8e20b063a185e08b3788344f54d77686417e43b58d5e8a06" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 8213 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 61280 + } + }, + { + "i128": { + "hi": 0, + "lo": 61280 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 95674 + } + }, + { + "i128": { + "hi": 0, + "lo": 95674 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 41026 + } + }, + { + "i128": { + "hi": 0, + "lo": 41026 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8213 + } + }, + { + "i128": { + "hi": 0, + "lo": 8213 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 61280 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 95674 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 41026 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 8213 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.17.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.17.json index 7a8dc47..41620ce 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.17.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.17.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 80568 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 80568 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 80568 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 80568 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 80568 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "177553fd5d7c1a7411a0d405c2884c9c2e2ff069e0af2bf0e3f12badceb5499da788b96772900691030ebcdd6aba5da6b8332442bb0b883474f3eb1db84cc607" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 80568 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 80568 + } + }, + { + "i128": { + "hi": 0, + "lo": 80568 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 80568 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.18.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.18.json index 2597117..196cad9 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.18.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.18.json @@ -360,6 +360,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -683,7 +688,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 1992 } } }, @@ -776,7 +781,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 33350 } } }, @@ -2717,6 +2722,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 1992 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 33350 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2963,30 +3076,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2997,29 +3091,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1992 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 33350 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -3032,24 +3365,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1992 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 33350 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 1992 + } + }, + { + "u64": 0 + }, + { + "bytes": "88cc9c5ca34145d1ed64520bf96771d317f8a23c2e6860ae0928df1dd7f2d20bb7c93697d7f72cc3ca243ca1426756ab5a83257c543520d4399e9d21265d9300" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 1992 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1992 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1992 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 33350 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3079,14 +4193,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 33350 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "16a30782a554e62b9137619e6b5292308092314bc9fb44c85c6027c86ebad993b69ec8a8fa430bb196f56739bee9411f0f0be5e4ad70ed48552601cd2835b001" } ] } @@ -3121,7 +4235,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 33350 } } ] @@ -3410,6 +4524,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 1992 + } + }, + { + "i128": { + "hi": 0, + "lo": 1992 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3502,6 +4664,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 33350 + } + }, + { + "i128": { + "hi": 0, + "lo": 33350 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3583,7 +4793,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 1992 } } }, @@ -3676,7 +4886,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 33350 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.19.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.19.json index ec8def4..33847d1 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.19.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.19.json @@ -337,6 +337,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -660,7 +665,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 73905 } } }, @@ -753,7 +758,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 706 } } }, @@ -2529,6 +2534,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 73905 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 706 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2775,30 +2888,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2809,29 +2903,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 73905 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 706 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -2844,24 +3177,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 73905 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 706 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 73905 + } + }, + { + "u64": 0 + }, + { + "bytes": "44af810f7d29a7d8457e79e234eb646de92dc0d9882759aafce88bb7e4a3dc1083af26792f1aed7b5d5781623c46cb7171ed9ae1ed85e2aef2ba690e92981c03" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 73905 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 73905 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 73905 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 706 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2891,14 +4005,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 706 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "943f6fc202adc1a8d808264f5f96971e161ec71ab34a881fbf07541a47883aa4ed54ecda8c5438552709cf41ce32a2750b9e11ada597e438afb603ec05195601" } ] } @@ -2933,7 +4047,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 706 } } ] @@ -3222,6 +4336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 73905 + } + }, + { + "i128": { + "hi": 0, + "lo": 73905 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3314,6 +4476,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 706 + } + }, + { + "i128": { + "hi": 0, + "lo": 706 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3395,7 +4605,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 73905 } } }, @@ -3488,7 +4698,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 706 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.2.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.2.json index 371d03e..e6e47d3 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.2.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.2.json @@ -360,6 +360,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -683,7 +688,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 10027 } } }, @@ -776,7 +781,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 4103 } } }, @@ -2717,6 +2722,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10027 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 4103 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2963,30 +3076,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2997,29 +3091,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10027 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4103 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -3032,24 +3365,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10027 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4103 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 10027 + } + }, + { + "u64": 0 + }, + { + "bytes": "1d5158f1d7c7982da230acb1403a1c251feeb3212bf7826b71d2827c462efdd2fa4e10e244d50d9bfc2ad6f63a54a396eaed4580352d0bb432fb071327821701" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 10027 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10027 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10027 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4103 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3079,14 +4193,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 4103 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "aeed8390f67c1bda1589968734acbf22ee677689f5ae96bf47e6d160ebe5816e235e28cf1b64f2f79378c2be8b1a7669b4e6a05bb9019efc2c32102c026d0d0d" } ] } @@ -3121,7 +4235,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 4103 } } ] @@ -3410,6 +4524,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10027 + } + }, + { + "i128": { + "hi": 0, + "lo": 10027 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3502,6 +4664,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 4103 + } + }, + { + "i128": { + "hi": 0, + "lo": 4103 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3583,7 +4793,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 10027 } } }, @@ -3676,7 +4886,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 4103 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.20.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.20.json index bda81a7..8f5b81e 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.20.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.20.json @@ -454,6 +454,13 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -779,7 +786,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 76145 } } }, @@ -872,7 +879,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 88142 } } }, @@ -965,7 +972,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 15028 } } }, @@ -3076,19 +3083,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 76145 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3098,23 +3136,105 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "payment" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 88142 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 15028 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3124,7 +3244,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -3133,14 +3253,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 164287 - } + "u32": 1 } } } @@ -3159,7 +3276,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3176,7 +3293,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3191,7 +3308,7 @@ "data": { "i128": { "hi": 0, - "lo": 15028 + "lo": 164287 } } } @@ -3211,14 +3328,14 @@ "symbol": "fn_call" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -3228,7 +3345,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -3243,7 +3360,7 @@ "data": { "i128": { "hi": 0, - "lo": 835713 + "lo": 15028 } } } @@ -3263,7 +3380,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3280,7 +3397,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3295,7 +3412,7 @@ "data": { "i128": { "hi": 0, - "lo": 984972 + "lo": 835713 } } } @@ -3315,33 +3432,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -3351,33 +3449,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 984972 + } } } } @@ -3387,19 +3475,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -3408,56 +3501,1412 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 88142 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15028 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 88142 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15028 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 76145 + } + }, + { + "u64": 0 + }, + { + "bytes": "93f2f48c87b05bf81aaf768a1651bafd86c147c136628bb2e4c74960a804112bd5d9f0930d6ab581bbd245e9fe8f24977966d5172ac61a8b8b85a1b30924900d" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 76145 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 88142 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15028 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 88142 + } + }, + { + "u64": 1 + }, + { + "bytes": "8e3465730f70f4f31ca7c2f4ac36fbb35ac3c1aa9e9ef1f9d73b02776754b0d101ae970bb67fae7266cb77fc8dd1f55d193902988e2c7597033f9820ec072a02" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ { "symbol": "hours" }, @@ -3476,7 +4925,471 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 88142 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 76145 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 88142 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 88142 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15028 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -3486,27 +5399,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3536,14 +5428,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 15028 } }, { "u64": 2 }, { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" + "bytes": "8e9f55c835c351cf6c78e5e0a1756576d1d5d6719c1bc41079c69548ac2216a6e532cdfa0b6f7a363632a49d2a921443194b3e645ce6ede49275e61aec2a1906" } ] } @@ -3578,7 +5470,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 15028 } } ] @@ -3867,6 +5759,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 76145 + } + }, + { + "i128": { + "hi": 0, + "lo": 76145 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3959,6 +5899,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 88142 + } + }, + { + "i128": { + "hi": 0, + "lo": 88142 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4051,6 +6039,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 15028 + } + }, + { + "i128": { + "hi": 0, + "lo": 15028 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4132,7 +6168,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 76145 } } }, @@ -4225,7 +6261,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 88142 } } }, @@ -4318,7 +6354,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 15028 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.21.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.21.json index 2571e07..252a116 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.21.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.21.json @@ -454,6 +454,13 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -779,7 +786,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 98218 } } }, @@ -872,7 +879,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 47798 } } }, @@ -965,7 +972,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 12642 } } }, @@ -3076,19 +3083,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 98218 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3098,23 +3136,105 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "payment" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 47798 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 12642 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3124,7 +3244,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -3133,14 +3253,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 47798 - } + "u32": 1 } } } @@ -3159,7 +3276,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3176,7 +3293,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3191,7 +3308,7 @@ "data": { "i128": { "hi": 0, - "lo": 110860 + "lo": 47798 } } } @@ -3211,14 +3328,14 @@ "symbol": "fn_call" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -3228,7 +3345,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -3243,7 +3360,7 @@ "data": { "i128": { "hi": 0, - "lo": 952202 + "lo": 110860 } } } @@ -3263,7 +3380,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3280,7 +3397,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3295,7 +3412,7 @@ "data": { "i128": { "hi": 0, - "lo": 889140 + "lo": 952202 } } } @@ -3315,33 +3432,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -3351,33 +3449,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 889140 + } } } } @@ -3387,19 +3475,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -3408,56 +3501,1412 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47798 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 12642 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47798 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 12642 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 98218 + } + }, + { + "u64": 0 + }, + { + "bytes": "5590d9dd45ca88736d5cd8ff74bd1b8778dc61914ffb2e255b74ba95adca99c2e05b9a3b85fc17f3f68c5f9a8de36dc260b66efcb848d31a997a0380ddf3f109" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 98218 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47798 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 12642 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 47798 + } + }, + { + "u64": 1 + }, + { + "bytes": "73170ac73e2f1d267c6835e692dd83b3b73187d1cbbb1f5bf2c9488db51337fb4a7e44b5f603187c59bc12073b7a361c894ce38404f6627601ac94f652550003" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ { "symbol": "hours" }, @@ -3476,7 +4925,471 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 47798 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 98218 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47798 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47798 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 12642 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -3486,27 +5399,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3536,14 +5428,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 12642 } }, { "u64": 2 }, { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" + "bytes": "0d09eec747314f49c5f33dbb632928b9bb2881425b227c273309b2167da42c57313649128214addf2a756978978e50390c0a373af8277b0db398c766f5031e0c" } ] } @@ -3578,7 +5470,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 12642 } } ] @@ -3867,6 +5759,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 98218 + } + }, + { + "i128": { + "hi": 0, + "lo": 98218 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3959,6 +5899,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 47798 + } + }, + { + "i128": { + "hi": 0, + "lo": 47798 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4051,6 +6039,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 12642 + } + }, + { + "i128": { + "hi": 0, + "lo": 12642 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4132,7 +6168,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 98218 } } }, @@ -4225,7 +6261,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 47798 } } }, @@ -4318,7 +6354,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 12642 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.22.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.22.json index 466573f..dac5e7c 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.22.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.22.json @@ -431,6 +431,13 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -756,7 +763,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 68141 } } }, @@ -849,7 +856,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 55495 } } }, @@ -942,7 +949,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 45771 } } }, @@ -2888,19 +2895,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 68141 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -2910,23 +2948,105 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "payment" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 55495 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 45771 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -2936,7 +3056,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -2945,14 +3065,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 0 - } + "u32": 1 } } } @@ -2971,7 +3088,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -2988,7 +3105,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3003,7 +3120,7 @@ "data": { "i128": { "hi": 0, - "lo": 169407 + "lo": 0 } } } @@ -3023,14 +3140,14 @@ "symbol": "fn_call" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -3040,7 +3157,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -3055,7 +3172,7 @@ "data": { "i128": { "hi": 0, - "lo": 1000000 + "lo": 169407 } } } @@ -3075,7 +3192,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3092,7 +3209,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3107,7 +3224,7 @@ "data": { "i128": { "hi": 0, - "lo": 830593 + "lo": 1000000 } } } @@ -3127,33 +3244,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -3163,33 +3261,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 830593 + } } } } @@ -3199,19 +3287,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -3220,56 +3313,1412 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 55495 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 45771 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 55495 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 45771 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 68141 + } + }, + { + "u64": 0 + }, + { + "bytes": "2a7c5ff8ce73e61b4c65203bbe8df41fcac5d7ec8824e4dfb37cbabd47e6c5b0e0c39bfc21142c8040da8dfa75171e72592e16879b2fac75450b80cd0fd47703" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 68141 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 55495 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 45771 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 55495 + } + }, + { + "u64": 1 + }, + { + "bytes": "ed65811218d7dd4125b89c9b3f2c1b01123423d5a0c40e63663bcde554af318f2ac2411748e2d1a6cd3156acff2c93315868f66ca2bd28e941746f5a16c84a09" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ { "symbol": "hours" }, @@ -3288,7 +4737,471 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 55495 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68141 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 55495 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 55495 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 45771 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -3298,27 +5211,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3348,14 +5240,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 45771 } }, { "u64": 2 }, { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" + "bytes": "f4acb4acf756f9ae4500bfde42d6025976903aeb0d76222c2b2ba5c72dcf80148adc71197efaa556faa6cb7af3e3b24c7c07de4d22fd7022977af96fe3b48005" } ] } @@ -3390,7 +5282,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 45771 } } ] @@ -3679,6 +5571,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 68141 + } + }, + { + "i128": { + "hi": 0, + "lo": 68141 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3771,6 +5711,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 55495 + } + }, + { + "i128": { + "hi": 0, + "lo": 55495 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3863,6 +5851,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 45771 + } + }, + { + "i128": { + "hi": 0, + "lo": 45771 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3944,7 +5980,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 68141 } } }, @@ -4037,7 +6073,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 55495 } } }, @@ -4130,7 +6166,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 45771 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.23.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.23.json index 539e50c..cf15847 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.23.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.23.json @@ -360,6 +360,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -683,7 +688,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 13294 } } }, @@ -776,7 +781,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 84436 } } }, @@ -2717,6 +2722,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 13294 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 84436 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2963,30 +3076,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2997,29 +3091,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13294 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 84436 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -3032,24 +3365,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13294 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 84436 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 13294 + } + }, + { + "u64": 0 + }, + { + "bytes": "abb9f44200765a7c5c0b9a6a03bcf25d9aa35baa89f1a3f0a776c1a84bd08e51f37c487f7010a242e2021ea0fcc72d448999d2b98b0c0af392a0dba890787b0c" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 13294 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13294 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 13294 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 84436 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3079,14 +4193,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 84436 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "469e6e630e5f9d8c71fec7eeb061a97905d46854d309b485b348360d8535d17e17896b78ba00f861e607bb3461bf456e8edf54bbff64d78556c9492f6ac5300b" } ] } @@ -3121,7 +4235,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 84436 } } ] @@ -3410,6 +4524,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 13294 + } + }, + { + "i128": { + "hi": 0, + "lo": 13294 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3502,6 +4664,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 84436 + } + }, + { + "i128": { + "hi": 0, + "lo": 84436 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3583,7 +4793,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 13294 } } }, @@ -3676,7 +4886,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 84436 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.24.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.24.json index be2a234..9b739cb 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.24.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.24.json @@ -454,6 +454,13 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -779,7 +786,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 54982 } } }, @@ -872,7 +879,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 61257 } } }, @@ -965,7 +972,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32416 } } }, @@ -3076,19 +3083,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 54982 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3098,23 +3136,105 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" + "symbol": "payment" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 61257 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 32416 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -3124,7 +3244,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -3133,14 +3253,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 61257 - } + "u32": 1 } } } @@ -3159,7 +3276,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3176,7 +3293,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3191,7 +3308,7 @@ "data": { "i128": { "hi": 0, - "lo": 87398 + "lo": 61257 } } } @@ -3211,14 +3328,14 @@ "symbol": "fn_call" }, { - "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -3228,7 +3345,7 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -3243,7 +3360,7 @@ "data": { "i128": { "hi": 0, - "lo": 938743 + "lo": 87398 } } } @@ -3263,7 +3380,7 @@ "symbol": "fn_call" }, { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" }, { "symbol": "balance" @@ -3280,7 +3397,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", "type_": "diagnostic", "body": { "v0": { @@ -3295,7 +3412,7 @@ "data": { "i128": { "hi": 0, - "lo": 912602 + "lo": 938743 } } } @@ -3315,33 +3432,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -3351,33 +3449,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 912602 + } } } } @@ -3387,19 +3475,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -3408,56 +3501,1412 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61257 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32416 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61257 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32416 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 54982 + } + }, + { + "u64": 0 + }, + { + "bytes": "fb10e174f16906cab1ffc506c2d9a6377f226f11f3f361463658016a7be3d02ae51ad879e1845916fc7993a1bc0fc63b843a83fa2d1f42376325a6bdad12c50b" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 54982 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61257 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32416 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 61257 + } + }, + { + "u64": 1 + }, + { + "bytes": "adfab390ea2460d1ed6483bc68a846a5002b5ed4ba59ed086984656bb8db52a7095bf1187cf25e3a5b96bd15af518666a5ab89fd98c087de5be51c0602c69609" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ { "symbol": "hours" }, @@ -3476,7 +4925,471 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 61257 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 54982 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61257 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 61257 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32416 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -3486,27 +5399,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3536,14 +5428,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 32416 } }, { "u64": 2 }, { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" + "bytes": "6418d68097dc90b1f19fba5c0e681d0eeede5a60372a441b4d8e59879a9ba2ba9f6e93271316b6a3f94b37ff212da1260e26bfec11e7b64275a89dbf42dff007" } ] } @@ -3578,7 +5470,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 32416 } } ] @@ -3867,6 +5759,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 54982 + } + }, + { + "i128": { + "hi": 0, + "lo": 54982 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3959,6 +5899,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 61257 + } + }, + { + "i128": { + "hi": 0, + "lo": 61257 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4051,6 +6039,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 32416 + } + }, + { + "i128": { + "hi": 0, + "lo": 32416 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4132,7 +6168,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 54982 } } }, @@ -4225,7 +6261,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 61257 } } }, @@ -4318,7 +6354,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32416 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.25.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.25.json index 221a06e..51d7d9a 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.25.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.25.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 24226 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 24226 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24226 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24226 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 24226 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "180ff6856c7b255b439e34aff7d6946c3c405c5a0a39de71d0876b5c14d6b6156a8ca7d6b9eb47bed71b21d56f8a795af9ca44bc061c16f1573bf81d8087440a" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 24226 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 24226 + } + }, + { + "i128": { + "hi": 0, + "lo": 24226 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 24226 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.26.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.26.json index 2566aac..b67d69e 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.26.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.26.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 41048 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 29221 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 31470 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56904 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 41048 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 29221 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 31470 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56904 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56904 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56904 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 41048 + } + }, + { + "u64": 0 + }, + { + "bytes": "cb6df83322cc6e8ebce745b3a4836759f606e37d4bcc08ea40ca90034ccce2aafc9e47e707c94e541ba5acde90e284bd1916b02a671895b6112b687022de7c0a" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 41048 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56904 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 29221 + } + }, + { + "u64": 1 + }, + { + "bytes": "a2a8127fb2d0e7f14f4664272139ef9d6467ab95d2cab9ee22a1a571a5a2458c7cb766cc6c5ae9843bc2de87c23271f87823237fe71d598c71c02dd533ffd206" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 29221 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56904 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 31470 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "a6d4fe08697f08bb492bceb34952d78d5a908fc0e699a2f8d632f8b9964efad1e55388d952079e0f97535c7354f03551ee4fb4f41a64f6994b9cf0b26c446408" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 31470 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 41048 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29221 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 31470 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 56904 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 56904 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "665d2e17c40162f3e11e44249da9fe5ace7e936dd9bc456be5b9ea22688ecba39d103ac447b91f0f44890e3d05b0413c49c50ebec9c6cec6d186178287336704" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 56904 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 41048 + } + }, + { + "i128": { + "hi": 0, + "lo": 41048 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 29221 + } + }, + { + "i128": { + "hi": 0, + "lo": 29221 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 31470 + } + }, + { + "i128": { + "hi": 0, + "lo": 31470 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 56904 + } + }, + { + "i128": { + "hi": 0, + "lo": 56904 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 41048 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 29221 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 31470 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 56904 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.27.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.27.json index 50e944a..b213bb7 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.27.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.27.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 52199 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 52199 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 52199 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 52199 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 52199 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "6f08fdc0e978eff38b6c7df6d280599f3dc84cd6a6741df473f04e2463b7eeebfe04f612694971a5501546cea5140d4c7c7a6d0f04c0240191d1a15a1d859805" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 52199 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 52199 + } + }, + { + "i128": { + "hi": 0, + "lo": 52199 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 52199 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.28.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.28.json index 33a59c8..bee7393 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.28.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.28.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 6291 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 6291 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 6291 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 6291 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 6291 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "877061ee2535837ea994adedfab2e79458b7e80f53a232387f2cdb52f1cfda938dd425a1939d7be8829036ace4ee50b0a708176a48b64b50c972ff65c34c7206" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 6291 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 6291 + } + }, + { + "i128": { + "hi": 0, + "lo": 6291 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 6291 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.29.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.29.json index 03a1fdb..befc626 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.29.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.29.json @@ -337,6 +337,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -660,7 +665,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 92443 } } }, @@ -753,7 +758,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 10118 } } }, @@ -2529,6 +2534,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 92443 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 10118 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2775,30 +2888,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2809,29 +2903,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 92443 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10118 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -2844,24 +3177,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 92443 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10118 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 92443 + } + }, + { + "u64": 0 + }, + { + "bytes": "31839e8647b53138c60099013f9ad535e30a8d09115f43ec32cae7cd263fbd5a1f3334c15365be0243b06ca7195bf681735cd3b87abd355048296aa28ce05001" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 92443 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 92443 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 92443 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10118 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2891,14 +4005,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 10118 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "3e008f92ffe34b8a14e30e1e3ebc65c39cf85206090f75d3c740c1084a662253ff60f58d2d6a927b592cef94bff21eff6909f5c938061a1abbeae7039ff58701" } ] } @@ -2933,7 +4047,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 10118 } } ] @@ -3222,6 +4336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 92443 + } + }, + { + "i128": { + "hi": 0, + "lo": 92443 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3314,6 +4476,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 10118 + } + }, + { + "i128": { + "hi": 0, + "lo": 10118 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3395,7 +4605,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 92443 } } }, @@ -3488,7 +4698,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 10118 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.3.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.3.json index eb35cc3..2f3e3d8 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.3.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.3.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 37368 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 37368 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37368 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 37368 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 37368 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "4cfc5bb1a812ef0c5ea6d239a4779739c49166f34d64e813e963be82e68044a5c17dcdc76785465bf8452f34c7f43aab91131973abd19af690f47ac1310fcd03" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 37368 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 37368 + } + }, + { + "i128": { + "hi": 0, + "lo": 37368 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 37368 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.30.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.30.json index feed3f8..f3ecd26 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.30.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.30.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 29454 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 60460 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 96430 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 2267 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 29454 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 60460 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 96430 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 2267 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2267 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2267 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 29454 + } + }, + { + "u64": 0 + }, + { + "bytes": "23c02dfe55b33a6ad90425cf00ca4b071e45066eb99770a24ef5463dc741cc40cf9cfdfee7870a45928c01dbd0ddc6ada65bec216cc85054b6a36495b57a0b0a" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 29454 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2267 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 60460 + } + }, + { + "u64": 1 + }, + { + "bytes": "783065ab899c9cae7ee80b77d1c717b013fe8132e867446fe1b93e7d954615fcdcf90d7ea2d72f6c3848f21bf0302113438fc1d52ae755ef8a23fab8c6f12a07" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 60460 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2267 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 96430 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "09ee80e93b0c37a5e17d3e60c46dccaa8fc85875ad706ae3bd665f8ed69aa5bc8a38c9c651ff754c364828bab9370d541c48fa863cbcf61faa58c0a0a64b530d" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 96430 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 29454 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 60460 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 96430 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2267 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 2267 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "e95a5645580002f0388113e0c6375b17266d338af2b225dde6b21a441d3049861003be10d981945441a74ad37f20571d5ead034d24e5580f08f7a86aa58ff909" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 2267 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 29454 + } + }, + { + "i128": { + "hi": 0, + "lo": 29454 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 60460 + } + }, + { + "i128": { + "hi": 0, + "lo": 60460 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 96430 + } + }, + { + "i128": { + "hi": 0, + "lo": 96430 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 2267 + } + }, + { + "i128": { + "hi": 0, + "lo": 2267 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 29454 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 60460 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 96430 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 2267 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.4.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.4.json index 978b79b..545b7e1 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.4.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.4.json @@ -243,6 +243,9 @@ [], [], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -564,7 +567,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 15243 } } }, @@ -2174,6 +2177,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 15243 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2405,6 +2462,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15243 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 15243 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2434,14 +2956,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 15243 } }, { "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "22afd2c33b73fafd9d362c2f9c59281a0562c71f5d64dfeb16a8584b64ab24682fa4c3e4e47d87268840bd013580e868965908c07b771f424ed0401af6b2ab0d" } ] } @@ -2476,7 +2998,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 15243 } } ] @@ -2765,6 +3287,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 15243 + } + }, + { + "i128": { + "hi": 0, + "lo": 15243 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2846,7 +3416,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 15243 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.5.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.5.json index d41cc04..cb42d26 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.5.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.5.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 82586 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 27845 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 49824 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 24655 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 82586 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 27845 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 49824 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 24655 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24655 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24655 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 82586 + } + }, + { + "u64": 0 + }, + { + "bytes": "306364ce9fe9a1ba5dc3b3877ce28ada4bd786708046b846b9307a4db3dd8f0119d0e2688cc57373c5f1c91ad4753f36c5f51dedf65ef32209955b502ff07706" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 82586 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24655 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 27845 + } + }, + { + "u64": 1 + }, + { + "bytes": "28cb91051201f8feb8947e53f6f3449ee53b0203ed9dd0e54e70a610b8c030ea825ce133081dd19162764db89ae76ec66f1c8c0622198b0d2c45b21afddafa0a" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 27845 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24655 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 49824 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "ce565fee8ae7639ccfd4b97aff80bb7b41c4b3e3b9faf1b942dd2837021e063a0528b85846d1640f0cb02d632de49913e5e0aebaf7c324e49484a3988e3dcd03" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 49824 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 82586 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27845 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 49824 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 24655 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 24655 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "c53f94c0ef6024a616516de2d51179af688e2f9ce4e3e4aaf65574d06482efb2b64fee839dc4bc0070dc6206451cc86a7c9558058d1680347e63b4a49d91c000" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 24655 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 82586 + } + }, + { + "i128": { + "hi": 0, + "lo": 82586 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 27845 + } + }, + { + "i128": { + "hi": 0, + "lo": 27845 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 49824 + } + }, + { + "i128": { + "hi": 0, + "lo": 49824 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 24655 + } + }, + { + "i128": { + "hi": 0, + "lo": 24655 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 82586 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 27845 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 49824 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 24655 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.6.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.6.json index 9276284..2197997 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.6.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.6.json @@ -548,6 +548,15 @@ [], [], [], + [], + [], + [], + [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -875,7 +884,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 47144 } } }, @@ -968,7 +977,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 59934 } } }, @@ -1061,7 +1070,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 87517 } } }, @@ -1154,7 +1163,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 26601 } } }, @@ -3427,6 +3436,222 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 47144 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 59934 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 87517 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 26601 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3673,30 +3898,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -3707,37 +3913,1764 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } - } - ] - } - } - } - }, - "failed_call": false + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 26601 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 26601 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 47144 + } + }, + { + "u64": 0 + }, + { + "bytes": "699391c6dc11cefa4a9c32ad5c7125f76bcef778ea63d581afde8b3d0788a8e31929cd4bb00261d11f1a80ff03b54d490b7d8cfa08cd0c952463cf84627f6208" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 47144 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 26601 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 59934 + } + }, + { + "u64": 1 + }, + { + "bytes": "07ba26c9f25cc9753f7d3c0a20a6def61f77e60b698e3502785f37a9d05be02adb8e6384d8c9fe90e3907af150e65a419cc515e38a087cf089a9fba07776c00d" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 59934 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false }, { "event": { @@ -3751,10 +5684,499 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 26601 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } } } }, @@ -3784,19 +6206,19 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 87517 } }, { - "u64": 1 + "u64": 2 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "bb155c9e652a2d5a49d184d46e0158915e76e1788b9d24810ec521993c59dff025c46f69c6b185121b42a3957f4ee675262d77894a2450074d53c9cf6c790103" } ] } @@ -3826,12 +6248,12 @@ "u32": 1 }, { - "u32": 1 + "u32": 2 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 87517 } } ] @@ -3877,30 +6299,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_nonce" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 2 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 2 - }, - { - "bytes": "42a8a6e2f333f122243e8032db35a15f3768a4c4d8d1ac04225d2dec4bfd7a854557e5f31ccc384f19bfc1b67df3520008c1b2990160ecc6e3861d0a3967a303" - } - ] + "u32": 1 } } } @@ -3911,29 +6314,503 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_nonce" } ], "data": { - "vec": [ + "u64": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 2 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 47144 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 59934 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 87517 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 26601 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -3943,27 +6820,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -3993,14 +6849,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 26601 } }, { "u64": 3 }, { - "bytes": "795b279eabbed5a31288680b0eaece08d4954d8075346e0fb285c119274de9f6114c9775682b73b49eae78d2e91942e0078ab29f08d09226aa0f23f3f23e3d0d" + "bytes": "3af9870e63bcbfe7c3aa8ce52183129966286364e6fc2e66dd5a72887a49f55df0fdb3541d5d31b9d1a4f87183f7016bd864c40d4115a981c05d279062a04805" } ] } @@ -4035,7 +6891,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 26601 } } ] @@ -4324,6 +7180,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 47144 + } + }, + { + "i128": { + "hi": 0, + "lo": 47144 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4416,6 +7320,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 59934 + } + }, + { + "i128": { + "hi": 0, + "lo": 59934 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4508,6 +7460,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 87517 + } + }, + { + "i128": { + "hi": 0, + "lo": 87517 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4600,6 +7600,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 26601 + } + }, + { + "i128": { + "hi": 0, + "lo": 26601 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -4681,7 +7729,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 47144 } } }, @@ -4774,7 +7822,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 59934 } } }, @@ -4867,7 +7915,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 87517 } } }, @@ -4960,7 +8008,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 26601 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.7.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.7.json index c6b3a86..1f2f730 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.7.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.7.json @@ -360,6 +360,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -683,7 +688,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32618 } } }, @@ -776,7 +781,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 14558 } } }, @@ -2717,6 +2722,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 32618 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 14558 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2963,30 +3076,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2997,29 +3091,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32618 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 14558 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -3032,24 +3365,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32618 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 14558 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 32618 + } + }, + { + "u64": 0 + }, + { + "bytes": "b08a36aa1cbfecbf2263773f9832db3b434efc82a66ea06fbb266b954078dea48f6bd169fac8a832078b13df8293aea10bdb84de70713b4a5c61d1f10722570f" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 32618 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32618 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32618 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 14558 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3079,14 +4193,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 14558 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "3f8be013842982b91d5a2524b3d5671dc61c41089bc5f5addacc4f976ab150d0c70e178307ce503aa1023ec0373c0595825d04f31a0560ec644b75576d0da605" } ] } @@ -3121,7 +4235,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 14558 } } ] @@ -3410,6 +4524,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 32618 + } + }, + { + "i128": { + "hi": 0, + "lo": 32618 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3502,6 +4664,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 14558 + } + }, + { + "i128": { + "hi": 0, + "lo": 14558 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3583,7 +4793,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32618 } } }, @@ -3676,7 +4886,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 14558 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.8.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.8.json index 1e46935..b6d025f 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.8.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.8.json @@ -360,6 +360,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -683,7 +688,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 40066 } } }, @@ -776,7 +781,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 68669 } } }, @@ -2717,6 +2722,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 40066 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 68669 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2963,30 +3076,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2997,29 +3091,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40066 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68669 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -3032,24 +3365,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40066 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68669 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40066 + } + }, + { + "u64": 0 + }, + { + "bytes": "5041cfacaaee67be90f68b5642a15b0ef69b7444faeb69496e69b48a4f69a6097c97f31c0da7969ad6f19d28a778257e9a5c6ad619f2afeff1e4d9076af02005" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40066 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40066 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40066 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 68669 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3079,14 +4193,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 68669 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "62eaa0149aad38bbc0927524407ba648ae2b61b2285b255acb76df15412adbedda52633ee860d07834d626f482cdb3aa8259b093c7f98423cef6d135bedadb0f" } ] } @@ -3121,7 +4235,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 68669 } } ] @@ -3410,6 +4524,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 40066 + } + }, + { + "i128": { + "hi": 0, + "lo": 40066 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3502,6 +4664,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 68669 + } + }, + { + "i128": { + "hi": 0, + "lo": 68669 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3583,7 +4793,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 40066 } } }, @@ -3676,7 +4886,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 68669 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.9.json b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.9.json index efc63c1..f03c578 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.9.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_custody_sum_invariant_fuzz.9.json @@ -337,6 +337,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -660,7 +665,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 27185 } } }, @@ -753,7 +758,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 67837 } } }, @@ -2529,6 +2534,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 27185 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 67837 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2775,30 +2888,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "u32": 1 } } } @@ -2809,29 +2903,268 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27185 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 67837 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -2844,24 +3177,805 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" - } - ], + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27185 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 67837 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 27185 + } + }, + { + "u64": 0 + }, + { + "bytes": "6cb0bf905a30ed3c1f98d89dfb11dfee0b3f4b1d9d7efe3011922fc0787d6ef19dbaaa75eea2e9c93ff12166d232c6df9d6ab54b2641d20ded6d137cd5972001" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 27185 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], "data": "void" } } }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27185 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 27185 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 67837 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2891,14 +4005,14 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 67837 } }, { "u64": 1 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "bytes": "5042b2f888922cc4396d20935ac12ad68bb2b2eb68f4fde93773481af54b3d2c6a296838c23f5f77b66a60df04923a6c935f5a77869043b3f558f0f607dd670d" } ] } @@ -2933,7 +4047,7 @@ { "i128": { "hi": 0, - "lo": 40 + "lo": 67837 } } ] @@ -3222,6 +4336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 27185 + } + }, + { + "i128": { + "hi": 0, + "lo": 27185 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3314,6 +4476,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 67837 + } + }, + { + "i128": { + "hi": 0, + "lo": 67837 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3395,7 +4605,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 27185 } } }, @@ -3488,7 +4698,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 67837 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_double_approval_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_double_approval_rejected.1.json index 4bcf723..8ffe42e 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_double_approval_rejected.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_double_approval_rejected.1.json @@ -1457,6 +1457,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_double_cancel_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_double_cancel_rejected.1.json index 6af2ad8..ad7e200 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_double_cancel_rejected.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_double_cancel_rejected.1.json @@ -1457,6 +1457,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1598,6 +1652,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_double_finalize_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_double_finalize_rejected.1.json index 411c865..a49867d 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_double_finalize_rejected.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_double_finalize_rejected.1.json @@ -195,6 +195,9 @@ ] ], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1635,6 +1638,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1658,6 +1715,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1694,7 +2216,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -2018,6 +2540,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_escrow_rejects_unregistered_oracle_key.1.json b/contracts/core-flow/test_snapshots/test/tests/test_escrow_rejects_unregistered_oracle_key.1.json new file mode 100644 index 0000000..75820fb --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_escrow_rejects_unregistered_oracle_key.1.json @@ -0,0 +1,1174 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "error": { + "contract": 16 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "initialize_multi_sig_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_extend_ttl_on_unknown_escrow_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_extend_ttl_on_unknown_escrow_fails.1.json new file mode 100644 index 0000000..6962e83 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_extend_ttl_on_unknown_escrow_fails.1.json @@ -0,0 +1,191 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "extend_escrow_ttl" + } + ], + "data": { + "u32": 999 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "extend_escrow_ttl" + } + ], + "data": { + "error": { + "contract": 4 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 4 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 4 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "extend_escrow_ttl" + }, + { + "vec": [ + { + "u32": 999 + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.1.json b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.1.json index 85fd308..3977844 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.1.json @@ -195,6 +195,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -282,7 +283,7 @@ "val": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } }, @@ -387,7 +388,7 @@ { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } ] @@ -400,6 +401,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -487,7 +489,7 @@ "val": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } }, @@ -592,7 +594,7 @@ { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } ] @@ -605,6 +607,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -692,7 +695,7 @@ "val": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } }, @@ -797,7 +800,7 @@ { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } ] @@ -810,6 +813,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -897,7 +901,7 @@ "val": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } }, @@ -1002,7 +1006,7 @@ { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } ] @@ -1015,6 +1019,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1102,7 +1107,7 @@ "val": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } }, @@ -1207,7 +1212,7 @@ { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } ] @@ -1220,6 +1225,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1307,7 +1313,7 @@ "val": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } }, @@ -1412,7 +1418,7 @@ { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } ] @@ -1425,6 +1431,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1512,7 +1519,7 @@ "val": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } }, @@ -1617,7 +1624,7 @@ { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } ] @@ -1630,6 +1637,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1717,7 +1725,7 @@ "val": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } }, @@ -1822,7 +1830,7 @@ { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } ] @@ -1835,6 +1843,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1922,7 +1931,7 @@ "val": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } }, @@ -2027,7 +2036,7 @@ { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } ] @@ -2040,6 +2049,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -2484,7 +2494,7 @@ "val": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } }, @@ -2688,7 +2698,7 @@ "val": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } }, @@ -2892,7 +2902,7 @@ "val": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } }, @@ -3096,7 +3106,7 @@ "val": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } }, @@ -3300,7 +3310,7 @@ "val": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } }, @@ -3504,7 +3514,7 @@ "val": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } }, @@ -3708,7 +3718,7 @@ "val": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } }, @@ -3912,7 +3922,7 @@ "val": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } }, @@ -3931,7 +3941,7 @@ "val": { "i128": { "hi": 0, - "lo": 48 + "lo": 40 } } }, @@ -4116,7 +4126,7 @@ "val": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } }, @@ -4135,7 +4145,7 @@ "val": { "i128": { "hi": 0, - "lo": 49 + "lo": 41 } } }, @@ -6203,7 +6213,7 @@ "val": { "i128": { "hi": 0, - "lo": 989955 + "lo": 989275 } } }, @@ -6349,7 +6359,7 @@ "val": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } }, @@ -6422,7 +6432,7 @@ "val": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } }, @@ -6495,7 +6505,7 @@ "val": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } }, @@ -6568,7 +6578,7 @@ "val": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } }, @@ -6641,7 +6651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } }, @@ -6714,7 +6724,7 @@ "val": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } }, @@ -6787,7 +6797,7 @@ "val": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } }, @@ -6860,7 +6870,7 @@ "val": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } }, @@ -6933,7 +6943,7 @@ "val": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } }, @@ -7568,6 +7578,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7591,6 +7655,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7627,7 +7899,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "f3bdf6eb753157650d1687999d22798556297ff817141b43381969e7a981c98f73d17aa603cb38614a91578a2b58c0ad1f16643ee4d0613d2b19b5ca3712dc05" } ] } @@ -7951,6 +8223,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8196,7 +8516,7 @@ "val": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } }, @@ -8318,7 +8638,7 @@ { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } ] @@ -8352,7 +8672,7 @@ "data": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } } @@ -8407,7 +8727,7 @@ { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } ] @@ -8421,39 +8741,301 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 2 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, { "symbol": "submit_hours_proof" } @@ -8476,7 +9058,7 @@ "u64": 0 }, { - "bytes": "4fd7e0520cedc7e296837af5e262f55057d29e51c634e1799a00f09bfbbe406ff50709d9b9f07cf73f04ecb1096425d7ff7f1c9ce4bc20a3d47be9ee679e0b0d" + "bytes": "b0c4a348b2142ce3d68ecf85aa173005d75bf5dd1f736fe95f07862e505da9c9fdb63f13924aa5ea93883df60ed73a2a4984592361cf135dc3a35075ab28a905" } ] } @@ -8737,7 +9319,7 @@ { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } ] @@ -8771,7 +9353,7 @@ "data": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } } @@ -8800,6 +9382,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8823,7 +9453,7 @@ { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } }, { @@ -8862,7 +9492,7 @@ "val": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } }, @@ -8997,7 +9627,7 @@ "data": { "i128": { "hi": 0, - "lo": 1001 + "lo": 1025 } } } @@ -9045,7 +9675,7 @@ "val": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } }, @@ -9167,7 +9797,7 @@ { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } ] @@ -9201,7 +9831,7 @@ "data": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } } @@ -9256,7 +9886,292 @@ { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] } } ] @@ -9266,29 +10181,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 3 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -9325,7 +10217,7 @@ "u64": 0 }, { - "bytes": "4a405d8cd217247f0c9dc7f7c07b0dd0ed509e958891877aabe388214c6b288cdbf98a62acaa840b36cea28f4dbdccae6c98d792beef2557516b2973b7959e06" + "bytes": "58203a1c6196802ee6dd1506d260d164fa4fff95bf83641bb1f2e7d06c98d17fdd99897719feeeb6f12878bd09594bd86d8fea0726bf4c283a5d547a24497b0b" } ] } @@ -9586,7 +10478,7 @@ { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } ] @@ -9620,7 +10512,7 @@ "data": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } } @@ -9649,6 +10541,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -9672,7 +10612,7 @@ { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } }, { @@ -9711,7 +10651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } }, @@ -9846,7 +10786,7 @@ "data": { "i128": { "hi": 0, - "lo": 1002 + "lo": 1050 } } } @@ -9894,7 +10834,7 @@ "val": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } }, @@ -10016,7 +10956,7 @@ { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } ] @@ -10050,7 +10990,7 @@ "data": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } } @@ -10105,7 +11045,292 @@ { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -10115,29 +11340,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 4 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -10174,7 +11376,7 @@ "u64": 0 }, { - "bytes": "96e3a45b28240716232209b6a9500ddbb6e54f435761ac404f4aef55a8a0612e844d743f2b8294ee3ee73f43f6a64bc02edcfe71af2bb3315afca512a2e6fa08" + "bytes": "ce92b45540af8346ab71c2323647054722445dcae4ddd77c0ca81664d72532bee6130e61a9da5ce71f87a4c29d06c4319a973bfa81d42c076c47afd534f7ef07" } ] } @@ -10435,7 +11637,7 @@ { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } ] @@ -10469,7 +11671,7 @@ "data": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } } @@ -10498,6 +11700,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -10521,7 +11771,7 @@ { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } }, { @@ -10560,7 +11810,7 @@ "val": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } }, @@ -10695,7 +11945,7 @@ "data": { "i128": { "hi": 0, - "lo": 1003 + "lo": 1075 } } } @@ -10743,7 +11993,7 @@ "val": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } }, @@ -10865,7 +12115,7 @@ { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } ] @@ -10899,7 +12149,7 @@ "data": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } } @@ -10954,7 +12204,292 @@ { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -10964,29 +12499,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 5 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11023,7 +12535,7 @@ "u64": 0 }, { - "bytes": "9a6541295dd4655b2d7e0327f02a754e39313526982a4029ad796559adeaba87062b57f7aa7d6d16fe739334f3b8207ce6a579b557a03c53706349e1accaf30b" + "bytes": "86df43a17e7b99e9b4ac8c3288c9b2427c135f9156b1067eec8bb704aa12d19fe9605505e9e63c13348f37ca9332066a4fecb7b52d99a9424ff9fab4f01a2507" } ] } @@ -11284,7 +12796,7 @@ { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } ] @@ -11318,7 +12830,7 @@ "data": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } } @@ -11347,6 +12859,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11370,7 +12930,7 @@ { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } }, { @@ -11409,7 +12969,7 @@ "val": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } }, @@ -11544,7 +13104,7 @@ "data": { "i128": { "hi": 0, - "lo": 1004 + "lo": 1100 } } } @@ -11592,7 +13152,7 @@ "val": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } }, @@ -11714,7 +13274,7 @@ { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } ] @@ -11748,7 +13308,7 @@ "data": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } } @@ -11803,7 +13363,292 @@ { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -11813,29 +13658,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 6 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11872,7 +13694,7 @@ "u64": 0 }, { - "bytes": "457aa20d374504b7c28425cf16bf372b3600437ab8d0d179a518197eaa54f8b92efd7cdfa1d1e6186a18129192cc40974e7fb98ff0d40935728cc19f21952509" + "bytes": "93db3d860077a2b9de2d750e66d22966fb2a983a0abd22344576e4ce758db78145e3ae1a13468cae55ac42158bab13528b13837de933d0692d8f623124fc2506" } ] } @@ -12133,7 +13955,7 @@ { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } ] @@ -12167,7 +13989,7 @@ "data": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } } @@ -12196,6 +14018,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -12219,7 +14089,7 @@ { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } }, { @@ -12258,7 +14128,7 @@ "val": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } }, @@ -12393,7 +14263,7 @@ "data": { "i128": { "hi": 0, - "lo": 1005 + "lo": 1125 } } } @@ -12441,7 +14311,7 @@ "val": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } }, @@ -12563,7 +14433,7 @@ { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } ] @@ -12597,7 +14467,7 @@ "data": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } } @@ -12652,7 +14522,292 @@ { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + } + ] } } ] @@ -12662,29 +14817,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 7 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -12721,7 +14853,7 @@ "u64": 0 }, { - "bytes": "5109c8b858121e373e339fb0460ac50c42ee970a8f71531b264915d92d1ad99f22b85dcf88ca82510e5561fa8a57a7ec2dcc754e4d20c17d1b288fb6313b3a0c" + "bytes": "78de14750f19ddd60f832717c51cdaebb03269672380e312d2a52f14124acacf2fd1d76793d52b901fd9d5924af9e7846eb7285613512f80b677dddafe79cf0c" } ] } @@ -12982,7 +15114,7 @@ { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } ] @@ -13016,7 +15148,7 @@ "data": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } } @@ -13045,6 +15177,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13068,7 +15248,7 @@ { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } }, { @@ -13107,7 +15287,7 @@ "val": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } }, @@ -13242,7 +15422,7 @@ "data": { "i128": { "hi": 0, - "lo": 1006 + "lo": 1150 } } } @@ -13290,7 +15470,7 @@ "val": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } }, @@ -13412,7 +15592,7 @@ { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } ] @@ -13446,7 +15626,7 @@ "data": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } } @@ -13501,8 +15681,62 @@ { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -13534,6 +15768,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13570,7 +16012,7 @@ "u64": 0 }, { - "bytes": "934ee6312f83e268ed46997c327da7b599a6924aafd12ed17de3d3b349f94789cc1ba89c62e07dc775f8e895890a281b61598e65128011e75d7b5f5e64ca3902" + "bytes": "21d4c6568c4c893fc4c0af018bd4d0ed57effc83e1fad9ddf600a74ee7f2a9a2161f38819e538a6e0e0dc181fb3675662308f82c3fa07b58cb3b62aa8f7bf407" } ] } @@ -13831,7 +16273,7 @@ { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } ] @@ -13865,7 +16307,7 @@ "data": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } } @@ -13894,6 +16336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13917,7 +16407,7 @@ { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } }, { @@ -13956,7 +16446,7 @@ "val": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } }, @@ -14091,7 +16581,7 @@ "data": { "i128": { "hi": 0, - "lo": 1007 + "lo": 1175 } } } @@ -14139,7 +16629,7 @@ "val": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } }, @@ -14261,7 +16751,7 @@ { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } ] @@ -14295,7 +16785,7 @@ "data": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } } @@ -14350,8 +16840,62 @@ { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -14383,6 +16927,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 9 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14412,14 +17164,14 @@ { "i128": { "hi": 0, - "lo": 48 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "ab7b644a45b721b869e7930ae8e4b2f60a24f07057474a671e6efb0dd1da79d6b361aa2bd9c228b2814169bfa5c1b37ab92338ffe3d75ae7f5a935ecc3516d03" + "bytes": "d4648fd46183ca8c9f003f3259fb7b7bf958d251b12f5e89c49bd6751b6fd42c665652a2f062db9be7c17fe7b92293e3c7fec3155edfdb906dc2914298af780d" } ] } @@ -14454,7 +17206,7 @@ { "i128": { "hi": 0, - "lo": 48 + "lo": 40 } } ] @@ -14680,7 +17432,7 @@ { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } ] @@ -14714,7 +17466,7 @@ "data": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } } @@ -14725,19 +17477,67 @@ { "event": { "ext": "v0", - "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", - "type_": "diagnostic", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "transfer" + "symbol": "paid" } ], - "data": "void" + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } } } }, @@ -14766,7 +17566,7 @@ { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } }, { @@ -14805,7 +17605,7 @@ "val": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } }, @@ -14824,7 +17624,7 @@ "val": { "i128": { "hi": 0, - "lo": 48 + "lo": 40 } } }, @@ -14940,7 +17740,7 @@ "data": { "i128": { "hi": 0, - "lo": 1008 + "lo": 1000 } } } @@ -14988,7 +17788,7 @@ "val": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } }, @@ -15110,7 +17910,7 @@ { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } ] @@ -15144,7 +17944,7 @@ "data": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } } @@ -15199,8 +17999,62 @@ { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -15232,6 +18086,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15261,14 +18323,14 @@ { "i128": { "hi": 0, - "lo": 49 + "lo": 41 } }, { "u64": 0 }, { - "bytes": "9fec7531fce60790b499876f9c7d0c2f773dd10f57e55e82f3106223859ed77c14bffbd2fddfa11bf29c206a17b04d4270a8a6142c9e580bf94b171494187f00" + "bytes": "fcc334487ed0de43025a2cd64a471dd18bd7aa9999f5843b4e3820b640616df48dd1771190860bb7c67960c7dbe62e7b957d35e0cfbefc2fc0ebf2387a72a600" } ] } @@ -15303,7 +18365,7 @@ { "i128": { "hi": 0, - "lo": 49 + "lo": 41 } } ] @@ -15529,7 +18591,7 @@ { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } ] @@ -15563,7 +18625,7 @@ "data": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } } @@ -15592,6 +18654,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15615,7 +18725,7 @@ { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } }, { @@ -15654,7 +18764,7 @@ "val": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } }, @@ -15673,7 +18783,7 @@ "val": { "i128": { "hi": 0, - "lo": 49 + "lo": 41 } } }, @@ -15789,7 +18899,7 @@ "data": { "i128": { "hi": 0, - "lo": 1009 + "lo": 1025 } } } @@ -15893,7 +19003,7 @@ "data": { "i128": { "hi": 0, - "lo": 989955 + "lo": 989275 } } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.2.json b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.2.json index c1aea09..aea659d 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.2.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.2.json @@ -77,7 +77,7 @@ "val": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } }, @@ -182,7 +182,7 @@ { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } ] @@ -195,6 +195,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -282,7 +283,7 @@ "val": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } }, @@ -387,7 +388,7 @@ { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } ] @@ -400,6 +401,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -487,7 +489,7 @@ "val": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } }, @@ -592,7 +594,7 @@ { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } ] @@ -605,6 +607,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -692,7 +695,7 @@ "val": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } }, @@ -797,7 +800,7 @@ { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } ] @@ -810,6 +813,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -897,7 +901,7 @@ "val": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } }, @@ -1002,7 +1006,7 @@ { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } ] @@ -1015,6 +1019,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1102,7 +1107,7 @@ "val": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } }, @@ -1207,7 +1212,7 @@ { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } ] @@ -1220,6 +1225,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1307,7 +1313,7 @@ "val": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } }, @@ -1412,7 +1418,7 @@ { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } ] @@ -1425,6 +1431,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1512,7 +1519,7 @@ "val": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } }, @@ -1617,7 +1624,7 @@ { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } ] @@ -1630,6 +1637,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1717,7 +1725,7 @@ "val": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } }, @@ -1822,7 +1830,7 @@ { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } ] @@ -1835,6 +1843,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1922,7 +1931,7 @@ "val": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } }, @@ -2027,7 +2036,7 @@ { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } ] @@ -2040,6 +2049,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -2280,7 +2290,7 @@ "val": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } }, @@ -2299,7 +2309,7 @@ "val": { "i128": { "hi": 0, - "lo": 50 + "lo": 42 } } }, @@ -2484,7 +2494,7 @@ "val": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } }, @@ -2503,7 +2513,7 @@ "val": { "i128": { "hi": 0, - "lo": 51 + "lo": 43 } } }, @@ -2688,7 +2698,7 @@ "val": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } }, @@ -2707,7 +2717,7 @@ "val": { "i128": { "hi": 0, - "lo": 52 + "lo": 44 } } }, @@ -2892,7 +2902,7 @@ "val": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } }, @@ -2911,7 +2921,7 @@ "val": { "i128": { "hi": 0, - "lo": 53 + "lo": 45 } } }, @@ -3096,7 +3106,7 @@ "val": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } }, @@ -3115,7 +3125,7 @@ "val": { "i128": { "hi": 0, - "lo": 54 + "lo": 46 } } }, @@ -3300,7 +3310,7 @@ "val": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } }, @@ -3319,7 +3329,7 @@ "val": { "i128": { "hi": 0, - "lo": 55 + "lo": 47 } } }, @@ -3504,7 +3514,7 @@ "val": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } }, @@ -3523,7 +3533,7 @@ "val": { "i128": { "hi": 0, - "lo": 56 + "lo": 40 } } }, @@ -3708,7 +3718,7 @@ "val": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } }, @@ -3727,7 +3737,7 @@ "val": { "i128": { "hi": 0, - "lo": 57 + "lo": 41 } } }, @@ -3912,7 +3922,7 @@ "val": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } }, @@ -3931,7 +3941,7 @@ "val": { "i128": { "hi": 0, - "lo": 58 + "lo": 42 } } }, @@ -4116,7 +4126,7 @@ "val": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } }, @@ -4135,7 +4145,7 @@ "val": { "i128": { "hi": 0, - "lo": 59 + "lo": 43 } } }, @@ -6203,7 +6213,7 @@ "val": { "i128": { "hi": 0, - "lo": 989855 + "lo": 989175 } } }, @@ -6276,7 +6286,7 @@ "val": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } }, @@ -6349,7 +6359,7 @@ "val": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } }, @@ -6422,7 +6432,7 @@ "val": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } }, @@ -6495,7 +6505,7 @@ "val": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } }, @@ -6568,7 +6578,7 @@ "val": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } }, @@ -6641,7 +6651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } }, @@ -6714,7 +6724,7 @@ "val": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } }, @@ -6787,7 +6797,7 @@ "val": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } }, @@ -6860,7 +6870,7 @@ "val": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } }, @@ -6933,7 +6943,7 @@ "val": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } }, @@ -7347,7 +7357,7 @@ "val": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } }, @@ -7469,7 +7479,7 @@ { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } ] @@ -7503,7 +7513,7 @@ "data": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } } @@ -7558,8 +7568,62 @@ { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -7591,6 +7655,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7620,14 +7892,14 @@ { "i128": { "hi": 0, - "lo": 50 + "lo": 42 } }, { "u64": 0 }, { - "bytes": "7e0d8bceaece11d56b262f470c8ba2fd9c21516d631cd89e87271d8a64fec6cc10cf90de772f7ca104d7419effa7fa6da908d3946186384ff4ccc6759cd1ee0f" + "bytes": "8946adfbb98b808216f39748c4032a859db049757712c8f01b089f91fd3d012cf62d85abc2275452f5bb430d1d3e52f1871ae52de30b13aff66cb6c774decc01" } ] } @@ -7662,7 +7934,7 @@ { "i128": { "hi": 0, - "lo": 50 + "lo": 42 } } ] @@ -7888,7 +8160,7 @@ { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } ] @@ -7922,7 +8194,7 @@ "data": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } } @@ -7951,6 +8223,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7974,7 +8294,7 @@ { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } }, { @@ -8013,7 +8333,7 @@ "val": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } }, @@ -8032,7 +8352,7 @@ "val": { "i128": { "hi": 0, - "lo": 50 + "lo": 42 } } }, @@ -8148,7 +8468,7 @@ "data": { "i128": { "hi": 0, - "lo": 1010 + "lo": 1050 } } } @@ -8196,7 +8516,7 @@ "val": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } }, @@ -8318,7 +8638,7 @@ { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } ] @@ -8352,7 +8672,7 @@ "data": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } } @@ -8407,7 +8727,7 @@ { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } ] @@ -8421,41 +8741,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 2 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -8467,16 +8761,304 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 51 - } + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 43 + } }, { "u64": 0 }, { - "bytes": "ef4b6b3c52005414fe8ab41900235123fffaa29b036106a35b1a5919e0a81af2dbb813af8e11723f8c37b78e95e9f2f2c5c58fc1fee727b397a0f5f595991405" + "bytes": "36f1703ed6943326de57044e396af29517a8adb1b2c326c263a3cf3ec258284ca5e69afa7b61f123836ef41680966ae1ce80dbb6538247b485f3538ed52a1603" } ] } @@ -8511,7 +9093,7 @@ { "i128": { "hi": 0, - "lo": 51 + "lo": 43 } } ] @@ -8737,7 +9319,7 @@ { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } ] @@ -8771,7 +9353,7 @@ "data": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } } @@ -8800,6 +9382,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8823,7 +9453,7 @@ { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } }, { @@ -8862,7 +9492,7 @@ "val": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } }, @@ -8881,7 +9511,7 @@ "val": { "i128": { "hi": 0, - "lo": 51 + "lo": 43 } } }, @@ -8997,7 +9627,7 @@ "data": { "i128": { "hi": 0, - "lo": 1011 + "lo": 1075 } } } @@ -9045,7 +9675,7 @@ "val": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } }, @@ -9167,7 +9797,7 @@ { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } ] @@ -9201,7 +9831,7 @@ "data": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } } @@ -9256,7 +9886,292 @@ { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] } } ] @@ -9266,29 +10181,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 3 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -9318,14 +10210,14 @@ { "i128": { "hi": 0, - "lo": 52 + "lo": 44 } }, { "u64": 0 }, { - "bytes": "b3dbf47d6ec2bb53fa28d1068469dce3524bda5547e0df644adb7b0380486f8f9332db4e8ef6d60622b06144bde67cbf5fd924689b38626183fbfd388534dd01" + "bytes": "2ec5c5e91d38790211a5b60c908973082de26033a4954b634e7fcdcf4f60e0ee0267812477b338224402c244772f7f2d8e4031afe0e5c8a2e46232750f22ca05" } ] } @@ -9360,7 +10252,7 @@ { "i128": { "hi": 0, - "lo": 52 + "lo": 44 } } ] @@ -9586,7 +10478,7 @@ { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } ] @@ -9620,7 +10512,7 @@ "data": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } } @@ -9649,6 +10541,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -9672,7 +10612,7 @@ { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } }, { @@ -9711,7 +10651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } }, @@ -9730,7 +10670,7 @@ "val": { "i128": { "hi": 0, - "lo": 52 + "lo": 44 } } }, @@ -9846,7 +10786,7 @@ "data": { "i128": { "hi": 0, - "lo": 1012 + "lo": 1100 } } } @@ -9894,7 +10834,7 @@ "val": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } }, @@ -10016,7 +10956,7 @@ { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } ] @@ -10050,7 +10990,7 @@ "data": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } } @@ -10105,7 +11045,292 @@ { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -10115,29 +11340,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 4 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -10167,14 +11369,14 @@ { "i128": { "hi": 0, - "lo": 53 + "lo": 45 } }, { "u64": 0 }, { - "bytes": "ae244c3e6a03c0188b70aa3d2f528120fddc890c5f3871a70ed07eda904faac01a4e18268686646e3a928f6d9c799674c52bac97ea0f551e595640b488116808" + "bytes": "6a8abed0892305be95f440eb3c677e14f8a3c925dff7f01980a8cffb4549793f4f9dea65649f2bbd4abfd9bb0968e451e269817007584d360c03189c60a3f10c" } ] } @@ -10209,7 +11411,7 @@ { "i128": { "hi": 0, - "lo": 53 + "lo": 45 } } ] @@ -10435,7 +11637,7 @@ { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } ] @@ -10469,7 +11671,7 @@ "data": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } } @@ -10498,6 +11700,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -10521,7 +11771,7 @@ { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } }, { @@ -10560,7 +11810,7 @@ "val": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } }, @@ -10579,7 +11829,7 @@ "val": { "i128": { "hi": 0, - "lo": 53 + "lo": 45 } } }, @@ -10695,7 +11945,7 @@ "data": { "i128": { "hi": 0, - "lo": 1013 + "lo": 1125 } } } @@ -10743,7 +11993,7 @@ "val": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } }, @@ -10865,7 +12115,7 @@ { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } ] @@ -10899,7 +12149,7 @@ "data": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } } @@ -10954,7 +12204,292 @@ { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -10964,29 +12499,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 5 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11016,14 +12528,14 @@ { "i128": { "hi": 0, - "lo": 54 + "lo": 46 } }, { "u64": 0 }, { - "bytes": "b51a55c6721b84d769a03d0a974a224432a68103183cca8b4d3b89a98c051513cb1af78e9a6323378cc0a34d26dcf621525c57d3f2904706709bfe9d88a3e208" + "bytes": "bba3603ddbeaff7da7725a0441c61333095ec24cd210af17467bca1d6de1db170d40a4f63350fa837549270544d4bc8409bd595690096274d9bb5170e2e23609" } ] } @@ -11058,7 +12570,7 @@ { "i128": { "hi": 0, - "lo": 54 + "lo": 46 } } ] @@ -11284,7 +12796,7 @@ { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } ] @@ -11318,7 +12830,7 @@ "data": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } } @@ -11347,6 +12859,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11370,7 +12930,7 @@ { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } }, { @@ -11409,7 +12969,7 @@ "val": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } }, @@ -11428,7 +12988,7 @@ "val": { "i128": { "hi": 0, - "lo": 54 + "lo": 46 } } }, @@ -11544,7 +13104,7 @@ "data": { "i128": { "hi": 0, - "lo": 1014 + "lo": 1150 } } } @@ -11592,7 +13152,7 @@ "val": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } }, @@ -11714,7 +13274,7 @@ { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } ] @@ -11748,7 +13308,7 @@ "data": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } } @@ -11803,7 +13363,292 @@ { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -11813,29 +13658,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 6 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11865,14 +13687,14 @@ { "i128": { "hi": 0, - "lo": 55 + "lo": 47 } }, { "u64": 0 }, { - "bytes": "07e630f0e448a4cf5eea9f89692a65b223f420ec752ae40b3e832a9bf36a1872c95c90530538389ac2ef7fc7ecaf04049fb06428cc5b0146963dce677968390c" + "bytes": "51c4d1943de94c0a9c7faf657d22f3119a1ccdf0e7f17fd9ddf3a8dce9f6c51c4b7f3cde5d07192ae32a0c82fe38c5b6adcd910b5daf08c2a088422b7794150d" } ] } @@ -11907,7 +13729,7 @@ { "i128": { "hi": 0, - "lo": 55 + "lo": 47 } } ] @@ -12133,7 +13955,7 @@ { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } ] @@ -12167,7 +13989,7 @@ "data": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } } @@ -12196,6 +14018,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -12219,7 +14089,7 @@ { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } }, { @@ -12258,7 +14128,7 @@ "val": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } }, @@ -12277,7 +14147,7 @@ "val": { "i128": { "hi": 0, - "lo": 55 + "lo": 47 } } }, @@ -12393,7 +14263,7 @@ "data": { "i128": { "hi": 0, - "lo": 1015 + "lo": 1175 } } } @@ -12441,7 +14311,7 @@ "val": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } }, @@ -12563,7 +14433,7 @@ { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } ] @@ -12597,7 +14467,7 @@ "data": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } } @@ -12652,7 +14522,292 @@ { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + } + ] } } ] @@ -12662,29 +14817,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 7 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -12714,14 +14846,14 @@ { "i128": { "hi": 0, - "lo": 56 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "660aec17de53aa28e1fe44943ffe37fb826208dfa675443100bf83dfca64ef178e2a12ebc1072589e1529b7bd3f9a5c61590ceed07546787dbd6458df515e408" + "bytes": "d05f4637e65927c3b0795327c26361b2ed291bf71a994a4c8c529c0fd597108df93b80e5ef5868c19d6898958e3ddf1cd3b1bbd185701db6895715a322af8f09" } ] } @@ -12756,7 +14888,7 @@ { "i128": { "hi": 0, - "lo": 56 + "lo": 40 } } ] @@ -12982,7 +15114,7 @@ { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } ] @@ -13016,7 +15148,7 @@ "data": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } } @@ -13045,6 +15177,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13068,7 +15248,7 @@ { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } }, { @@ -13107,7 +15287,7 @@ "val": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } }, @@ -13126,7 +15306,7 @@ "val": { "i128": { "hi": 0, - "lo": 56 + "lo": 40 } } }, @@ -13242,7 +15422,7 @@ "data": { "i128": { "hi": 0, - "lo": 1016 + "lo": 1000 } } } @@ -13290,7 +15470,7 @@ "val": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } }, @@ -13412,7 +15592,7 @@ { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } ] @@ -13446,7 +15626,7 @@ "data": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } } @@ -13501,7 +15681,292 @@ { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] } } ] @@ -13511,29 +15976,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 8 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -13563,14 +16005,14 @@ { "i128": { "hi": 0, - "lo": 57 + "lo": 41 } }, { "u64": 0 }, { - "bytes": "fa8436b69fb7e9b151855c7e3757017ee05a8d55d7b57c5b66e82156ebbb7569fd4b612e517c375d7c364a980b0f3467cc3c7004163b776a60ec148897bafb0e" + "bytes": "7683d168d6f6d6ccc0a6f379da9631528435318edf7a187604567113e66dbae1b8c7edd2dc25d1c632c2682be58f720ce270a85b19399cb1bcc4cc21e760cb04" } ] } @@ -13605,7 +16047,7 @@ { "i128": { "hi": 0, - "lo": 57 + "lo": 41 } } ] @@ -13831,7 +16273,7 @@ { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } ] @@ -13865,7 +16307,7 @@ "data": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } } @@ -13894,6 +16336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13917,7 +16407,7 @@ { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } }, { @@ -13956,7 +16446,7 @@ "val": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } }, @@ -13975,7 +16465,7 @@ "val": { "i128": { "hi": 0, - "lo": 57 + "lo": 41 } } }, @@ -14091,7 +16581,7 @@ "data": { "i128": { "hi": 0, - "lo": 1017 + "lo": 1025 } } } @@ -14139,7 +16629,7 @@ "val": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } }, @@ -14261,7 +16751,7 @@ { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } ] @@ -14295,7 +16785,7 @@ "data": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } } @@ -14350,8 +16840,62 @@ { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -14383,6 +16927,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 9 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14412,14 +17164,14 @@ { "i128": { "hi": 0, - "lo": 58 + "lo": 42 } }, { "u64": 0 }, { - "bytes": "51f9f0bcbaba1fc6990506211d28cfbe1e2fadc714d4594cd336da853eb5ad2002f3bb4f83cc298e4f02430f1bccc74ec03a246fbbcefe03a06210cf05f8990c" + "bytes": "6d35cc2f5339d66cb8f78762b8e26440010f47b27c365d66741ae2e0064077bad638cc980c0cdfb6bdd43a55635361e0dbf3b8a1a2de0a47bcc59003b14bb30e" } ] } @@ -14454,7 +17206,7 @@ { "i128": { "hi": 0, - "lo": 58 + "lo": 42 } } ] @@ -14680,7 +17432,7 @@ { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } ] @@ -14714,7 +17466,7 @@ "data": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } } @@ -14743,6 +17495,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14766,7 +17566,7 @@ { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } }, { @@ -14805,7 +17605,7 @@ "val": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } }, @@ -14824,7 +17624,7 @@ "val": { "i128": { "hi": 0, - "lo": 58 + "lo": 42 } } }, @@ -14940,7 +17740,7 @@ "data": { "i128": { "hi": 0, - "lo": 1018 + "lo": 1050 } } } @@ -14988,7 +17788,7 @@ "val": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } }, @@ -15110,7 +17910,7 @@ { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } ] @@ -15144,7 +17944,7 @@ "data": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } } @@ -15199,8 +17999,62 @@ { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -15232,6 +18086,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15261,14 +18323,14 @@ { "i128": { "hi": 0, - "lo": 59 + "lo": 43 } }, { "u64": 0 }, { - "bytes": "8b7ea526dea787ee3ba311bf7dca6e10b424626e380908e03e3f7a46285941b6c0f234d9427ffa745a17c3d6ce73df8aee694f8790ddb05a62069c6ff2475905" + "bytes": "4e7dc0761d72f9f9262af1bf9b005a082c78d18a09c26f7d09dab930d1995bb8c5a956b672089f0769f3bc4462fe916ba8b360b1c2b8d0d8662a6a2fc5359205" } ] } @@ -15303,7 +18365,7 @@ { "i128": { "hi": 0, - "lo": 59 + "lo": 43 } } ] @@ -15529,7 +18591,7 @@ { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } ] @@ -15563,7 +18625,7 @@ "data": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } } @@ -15592,6 +18654,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15615,7 +18725,7 @@ { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } }, { @@ -15654,7 +18764,7 @@ "val": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } }, @@ -15673,7 +18783,7 @@ "val": { "i128": { "hi": 0, - "lo": 59 + "lo": 43 } } }, @@ -15789,7 +18899,7 @@ "data": { "i128": { "hi": 0, - "lo": 1019 + "lo": 1075 } } } @@ -15893,7 +19003,7 @@ "data": { "i128": { "hi": 0, - "lo": 989855 + "lo": 989175 } } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.3.json b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.3.json index 72f4a2f..0629346 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.3.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.3.json @@ -77,7 +77,7 @@ "val": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } }, @@ -182,7 +182,7 @@ { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } ] @@ -195,6 +195,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -282,7 +283,7 @@ "val": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } }, @@ -387,7 +388,7 @@ { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } ] @@ -400,6 +401,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -487,7 +489,7 @@ "val": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } }, @@ -592,7 +594,7 @@ { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } ] @@ -605,6 +607,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -692,7 +695,7 @@ "val": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } }, @@ -797,7 +800,7 @@ { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } ] @@ -810,6 +813,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -897,7 +901,7 @@ "val": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } }, @@ -1002,7 +1006,7 @@ { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } ] @@ -1015,6 +1019,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1220,6 +1225,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1307,7 +1313,7 @@ "val": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } }, @@ -1412,7 +1418,7 @@ { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } ] @@ -1425,6 +1431,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1512,7 +1519,7 @@ "val": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } }, @@ -1617,7 +1624,7 @@ { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } ] @@ -1630,6 +1637,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1717,7 +1725,7 @@ "val": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } }, @@ -1822,7 +1830,7 @@ { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } ] @@ -1835,6 +1843,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1922,7 +1931,7 @@ "val": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } }, @@ -2027,7 +2036,7 @@ { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } ] @@ -2040,6 +2049,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -2280,7 +2290,7 @@ "val": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } }, @@ -2299,7 +2309,7 @@ "val": { "i128": { "hi": 0, - "lo": 60 + "lo": 44 } } }, @@ -2484,7 +2494,7 @@ "val": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } }, @@ -2503,7 +2513,7 @@ "val": { "i128": { "hi": 0, - "lo": 61 + "lo": 45 } } }, @@ -2688,7 +2698,7 @@ "val": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } }, @@ -2707,7 +2717,7 @@ "val": { "i128": { "hi": 0, - "lo": 62 + "lo": 46 } } }, @@ -2892,7 +2902,7 @@ "val": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } }, @@ -2911,7 +2921,7 @@ "val": { "i128": { "hi": 0, - "lo": 63 + "lo": 47 } } }, @@ -3096,7 +3106,7 @@ "val": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } }, @@ -3115,7 +3125,7 @@ "val": { "i128": { "hi": 0, - "lo": 64 + "lo": 40 } } }, @@ -3319,7 +3329,7 @@ "val": { "i128": { "hi": 0, - "lo": 65 + "lo": 41 } } }, @@ -3504,7 +3514,7 @@ "val": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } }, @@ -3523,7 +3533,7 @@ "val": { "i128": { "hi": 0, - "lo": 66 + "lo": 42 } } }, @@ -3708,7 +3718,7 @@ "val": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } }, @@ -3727,7 +3737,7 @@ "val": { "i128": { "hi": 0, - "lo": 67 + "lo": 43 } } }, @@ -3912,7 +3922,7 @@ "val": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } }, @@ -3931,7 +3941,7 @@ "val": { "i128": { "hi": 0, - "lo": 68 + "lo": 44 } } }, @@ -4116,7 +4126,7 @@ "val": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } }, @@ -4135,7 +4145,7 @@ "val": { "i128": { "hi": 0, - "lo": 69 + "lo": 45 } } }, @@ -6203,7 +6213,7 @@ "val": { "i128": { "hi": 0, - "lo": 989755 + "lo": 989075 } } }, @@ -6276,7 +6286,7 @@ "val": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } }, @@ -6349,7 +6359,7 @@ "val": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } }, @@ -6422,7 +6432,7 @@ "val": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } }, @@ -6495,7 +6505,7 @@ "val": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } }, @@ -6568,7 +6578,7 @@ "val": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } }, @@ -6714,7 +6724,7 @@ "val": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } }, @@ -6787,7 +6797,7 @@ "val": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } }, @@ -6860,7 +6870,7 @@ "val": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } }, @@ -6933,7 +6943,7 @@ "val": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } }, @@ -7347,7 +7357,7 @@ "val": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } }, @@ -7469,7 +7479,7 @@ { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } ] @@ -7503,7 +7513,7 @@ "data": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } } @@ -7558,8 +7568,62 @@ { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -7591,6 +7655,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7620,14 +7892,14 @@ { "i128": { "hi": 0, - "lo": 60 + "lo": 44 } }, { "u64": 0 }, { - "bytes": "28c5e436b91ea686d33438503eb28eef162b4cc7fb6c3926e1ab245cfc1d9be477062f9326f088d9ffba3c5d7af20cc18f546ff48df2c4dd30743a76dfe07006" + "bytes": "2c3e5421e070670c1afb5c45a4b91dd140a8b6ea6fa0f7b973abc2f114029f19a434c1a77a969586d955f1c3e6e86d96c64f6745c2255d424118d4b3eafc0a01" } ] } @@ -7662,7 +7934,7 @@ { "i128": { "hi": 0, - "lo": 60 + "lo": 44 } } ] @@ -7888,7 +8160,7 @@ { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } ] @@ -7922,7 +8194,7 @@ "data": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } } @@ -7951,6 +8223,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7974,7 +8294,7 @@ { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } }, { @@ -8013,7 +8333,7 @@ "val": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } }, @@ -8032,7 +8352,7 @@ "val": { "i128": { "hi": 0, - "lo": 60 + "lo": 44 } } }, @@ -8148,7 +8468,7 @@ "data": { "i128": { "hi": 0, - "lo": 1020 + "lo": 1100 } } } @@ -8196,7 +8516,7 @@ "val": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } }, @@ -8318,7 +8638,7 @@ { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } ] @@ -8352,7 +8672,7 @@ "data": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } } @@ -8407,7 +8727,7 @@ { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } ] @@ -8421,41 +8741,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 2 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -8467,16 +8761,304 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 61 - } + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 45 + } }, { "u64": 0 }, { - "bytes": "2bf97e6f7e3248ef87acd9406a0c0e89912ec6ba5e33183fbf9054911debf7416e249534ba03d7926332fee9d06c2554a1cea7508dd42c6f43948df51b51290d" + "bytes": "4ef0cb60637c8694795757de734d48731eb0c25ddea6e537cfb1da110423712308995fff17fa188332cd2f2c42ad2b252f3b3b00eeeafc6667b38e0a50342901" } ] } @@ -8511,7 +9093,7 @@ { "i128": { "hi": 0, - "lo": 61 + "lo": 45 } } ] @@ -8737,7 +9319,7 @@ { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } ] @@ -8771,7 +9353,7 @@ "data": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } } @@ -8800,6 +9382,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8823,7 +9453,7 @@ { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } }, { @@ -8862,7 +9492,7 @@ "val": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } }, @@ -8881,7 +9511,7 @@ "val": { "i128": { "hi": 0, - "lo": 61 + "lo": 45 } } }, @@ -8997,7 +9627,7 @@ "data": { "i128": { "hi": 0, - "lo": 1021 + "lo": 1125 } } } @@ -9045,7 +9675,7 @@ "val": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } }, @@ -9167,7 +9797,7 @@ { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } ] @@ -9201,7 +9831,7 @@ "data": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } } @@ -9256,7 +9886,292 @@ { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] } } ] @@ -9266,29 +10181,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 3 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -9318,14 +10210,14 @@ { "i128": { "hi": 0, - "lo": 62 + "lo": 46 } }, { "u64": 0 }, { - "bytes": "7840751be0255989e8ffb0102370cac59ad868f37b3d61e7a503fa8e4ba7c2dd41fb6ab7d7c68e10343d4a70d4e2536d6e001acdf991bc46022b3451b084210d" + "bytes": "6da1ab1eaba553b79d98842f2b71bf8e708c07f52ed31f7eec7c06a46b8f7e5f316976e4e3f901dd5c0474bfc98dfda5e407e0114beb91a207b61e5862f0e501" } ] } @@ -9360,7 +10252,7 @@ { "i128": { "hi": 0, - "lo": 62 + "lo": 46 } } ] @@ -9586,7 +10478,7 @@ { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } ] @@ -9620,7 +10512,7 @@ "data": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } } @@ -9649,6 +10541,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -9672,7 +10612,7 @@ { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } }, { @@ -9711,7 +10651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } }, @@ -9730,7 +10670,7 @@ "val": { "i128": { "hi": 0, - "lo": 62 + "lo": 46 } } }, @@ -9846,7 +10786,7 @@ "data": { "i128": { "hi": 0, - "lo": 1022 + "lo": 1150 } } } @@ -9894,7 +10834,7 @@ "val": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } }, @@ -10016,7 +10956,7 @@ { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } ] @@ -10050,7 +10990,7 @@ "data": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } } @@ -10105,7 +11045,292 @@ { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -10115,29 +11340,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 4 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -10167,14 +11369,14 @@ { "i128": { "hi": 0, - "lo": 63 + "lo": 47 } }, { "u64": 0 }, { - "bytes": "3c6b61579f53247b5c157f67ad81206bb1a99822a20689e329622900a486701211863d52c0dbb4d6b1e180bf4fa5aefed8b3c4b9819b701d40657272d404ca01" + "bytes": "78edf280f2afe21e2cc68ea3dcb57532e23f2798bca34c01c2cd1de3fd20f9f54415fadd13e362843e694ced675a1427605bb96078e6ae116f8268cb26878903" } ] } @@ -10209,7 +11411,7 @@ { "i128": { "hi": 0, - "lo": 63 + "lo": 47 } } ] @@ -10435,7 +11637,7 @@ { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } ] @@ -10469,7 +11671,7 @@ "data": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } } @@ -10498,6 +11700,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -10521,7 +11771,7 @@ { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } }, { @@ -10560,7 +11810,7 @@ "val": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } }, @@ -10579,7 +11829,7 @@ "val": { "i128": { "hi": 0, - "lo": 63 + "lo": 47 } } }, @@ -10695,7 +11945,7 @@ "data": { "i128": { "hi": 0, - "lo": 1023 + "lo": 1175 } } } @@ -10743,7 +11993,7 @@ "val": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } }, @@ -10865,7 +12115,7 @@ { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } ] @@ -10899,7 +12149,7 @@ "data": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } } @@ -10954,7 +12204,292 @@ { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -10964,29 +12499,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 5 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11016,14 +12528,14 @@ { "i128": { "hi": 0, - "lo": 64 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "46b4cfe3f1d9c2e0f39db865cd511c28e8a7ad23073bfa12cfaf6dfb26236ce001cdc7410c0a745f3bee14e33fae37261f3b5f6de7a7565a3cdbb333d8f87607" + "bytes": "fe6bff9baf9580f64d1941fd292a9b7f0a3544520bd6de91d51f43a6f3b5693ae55189ba26363291b12f770931ed6817d46a73bc24cbf23318dbff5d66237401" } ] } @@ -11058,7 +12570,7 @@ { "i128": { "hi": 0, - "lo": 64 + "lo": 40 } } ] @@ -11284,7 +12796,7 @@ { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } ] @@ -11318,7 +12830,7 @@ "data": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } } @@ -11347,6 +12859,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11370,7 +12930,7 @@ { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } }, { @@ -11409,7 +12969,7 @@ "val": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } }, @@ -11428,7 +12988,7 @@ "val": { "i128": { "hi": 0, - "lo": 64 + "lo": 40 } } }, @@ -11544,7 +13104,7 @@ "data": { "i128": { "hi": 0, - "lo": 1024 + "lo": 1000 } } } @@ -11813,29 +13373,291 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 6 - } - } - } - }, - "failed_call": false - }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11865,14 +13687,14 @@ { "i128": { "hi": 0, - "lo": 65 + "lo": 41 } }, { "u64": 0 }, { - "bytes": "b825bcee15dae8ed24b756f967222e8bcbf289ea4e537401686730c7e728f2de4abda1fc2df95bef1d179675119ba539436e42f0bb501a1316fbf5774c868b00" + "bytes": "2ce06cd0b18b3ee528cfcf65a6a95cafed08c815eb06e8ee04aefd48cbe174ed3a739ce0c9d3012938b93bba2e18960892280abbeb44ceafdf8b9985964a740a" } ] } @@ -11907,7 +13729,7 @@ { "i128": { "hi": 0, - "lo": 65 + "lo": 41 } } ] @@ -12196,6 +14018,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -12277,7 +14147,7 @@ "val": { "i128": { "hi": 0, - "lo": 65 + "lo": 41 } } }, @@ -12441,7 +14311,7 @@ "val": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } }, @@ -12563,7 +14433,7 @@ { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } ] @@ -12597,7 +14467,7 @@ "data": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } } @@ -12652,7 +14522,292 @@ { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + } + ] } } ] @@ -12662,29 +14817,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 7 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -12714,14 +14846,14 @@ { "i128": { "hi": 0, - "lo": 66 + "lo": 42 } }, { "u64": 0 }, { - "bytes": "10c197095f621f40937cb0de74279c290e66f967b3a38db21bb22cbf04c0262fe22c73514c34d7c9d4893bc4ea913523c7a75412d931233cea6f8747225b3305" + "bytes": "c66e038cfe123551e0742837f594bb5fcf0d6d830fdca43be881d9ec5c2504aff8be18a3810e1f040cdf315b406227671d75f5d3962279df6c9b219b47886607" } ] } @@ -12756,7 +14888,7 @@ { "i128": { "hi": 0, - "lo": 66 + "lo": 42 } } ] @@ -12982,7 +15114,7 @@ { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } ] @@ -13016,7 +15148,7 @@ "data": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } } @@ -13045,6 +15177,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13068,7 +15248,7 @@ { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } }, { @@ -13107,7 +15287,7 @@ "val": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } }, @@ -13126,7 +15306,7 @@ "val": { "i128": { "hi": 0, - "lo": 66 + "lo": 42 } } }, @@ -13242,7 +15422,7 @@ "data": { "i128": { "hi": 0, - "lo": 1026 + "lo": 1050 } } } @@ -13290,7 +15470,7 @@ "val": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } }, @@ -13412,7 +15592,7 @@ { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } ] @@ -13446,7 +15626,7 @@ "data": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } } @@ -13501,7 +15681,292 @@ { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] } } ] @@ -13511,29 +15976,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 8 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -13563,14 +16005,14 @@ { "i128": { "hi": 0, - "lo": 67 + "lo": 43 } }, { "u64": 0 }, { - "bytes": "e0389cda265e610230087341d21c0e9dd3d073aab8049d9f717ba39bd932b51fd1a3ea4ab031c0416e797867060961cc3655095903cded1576b7f4804233280a" + "bytes": "512bbaaa69266eb2a9fed423d3603210286e995723e53fc7f14b392ffcdeeb9eec050a08a5e9eb43d2614335b0eb50145dc42ee54898987f10fcdd35d571820a" } ] } @@ -13605,7 +16047,7 @@ { "i128": { "hi": 0, - "lo": 67 + "lo": 43 } } ] @@ -13831,7 +16273,7 @@ { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } ] @@ -13865,7 +16307,7 @@ "data": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } } @@ -13894,6 +16336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13917,7 +16407,7 @@ { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } }, { @@ -13956,7 +16446,7 @@ "val": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } }, @@ -13975,7 +16465,7 @@ "val": { "i128": { "hi": 0, - "lo": 67 + "lo": 43 } } }, @@ -14091,7 +16581,7 @@ "data": { "i128": { "hi": 0, - "lo": 1027 + "lo": 1075 } } } @@ -14139,7 +16629,7 @@ "val": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } }, @@ -14261,7 +16751,7 @@ { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } ] @@ -14295,7 +16785,7 @@ "data": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } } @@ -14350,8 +16840,62 @@ { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -14383,6 +16927,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 9 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14412,14 +17164,14 @@ { "i128": { "hi": 0, - "lo": 68 + "lo": 44 } }, { "u64": 0 }, { - "bytes": "bc2db9f91104247812fa54e4259a76ad46e50f0d741db6cb0f306693f2ae03bc7eb7ba5cb621b689995c752713c5a3983419bde6b078c9aaa964af1dc3e30208" + "bytes": "bd6d14233079d62994c1f78474a5b9dbe595889af9613bb99bdaf23ec897e24d2cc6e4ebad90dbb1fd69c7fb1c9271ccaac00bb7142d75075213c7ab4b999f09" } ] } @@ -14454,7 +17206,7 @@ { "i128": { "hi": 0, - "lo": 68 + "lo": 44 } } ] @@ -14680,7 +17432,7 @@ { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } ] @@ -14714,7 +17466,7 @@ "data": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } } @@ -14743,6 +17495,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14766,7 +17566,7 @@ { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } }, { @@ -14805,7 +17605,7 @@ "val": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } }, @@ -14824,7 +17624,7 @@ "val": { "i128": { "hi": 0, - "lo": 68 + "lo": 44 } } }, @@ -14940,7 +17740,7 @@ "data": { "i128": { "hi": 0, - "lo": 1028 + "lo": 1100 } } } @@ -14988,7 +17788,7 @@ "val": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } }, @@ -15110,7 +17910,7 @@ { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } ] @@ -15144,7 +17944,7 @@ "data": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } } @@ -15199,8 +17999,62 @@ { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -15232,6 +18086,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15261,14 +18323,14 @@ { "i128": { "hi": 0, - "lo": 69 + "lo": 45 } }, { "u64": 0 }, { - "bytes": "66e8486454c77b759cc376b0eb15143c9e50e06456313d59b0132e7c7d098d42990a7a81424e0a85d7f08b79c92e8d6dfb018472b4b350eb5633a6faa8afd00e" + "bytes": "8d79fcbea3330ec3aea06ed4c3f3bf751a26277b4cbdb54a63313defb9e2a19f13c508e26290843c87da97af908f08274073bc0a5ab33bdf4f22df60952b0f06" } ] } @@ -15303,7 +18365,7 @@ { "i128": { "hi": 0, - "lo": 69 + "lo": 45 } } ] @@ -15529,7 +18591,7 @@ { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } ] @@ -15563,7 +18625,7 @@ "data": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } } @@ -15592,6 +18654,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15615,7 +18725,7 @@ { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } }, { @@ -15654,7 +18764,7 @@ "val": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } }, @@ -15673,7 +18783,7 @@ "val": { "i128": { "hi": 0, - "lo": 69 + "lo": 45 } } }, @@ -15789,7 +18899,7 @@ "data": { "i128": { "hi": 0, - "lo": 1029 + "lo": 1125 } } } @@ -15893,7 +19003,7 @@ "data": { "i128": { "hi": 0, - "lo": 989755 + "lo": 989075 } } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.4.json b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.4.json index 47d4d40..98320cb 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.4.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.4.json @@ -77,7 +77,7 @@ "val": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } }, @@ -182,7 +182,7 @@ { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } ] @@ -195,6 +195,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -282,7 +283,7 @@ "val": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } }, @@ -387,7 +388,7 @@ { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } ] @@ -400,6 +401,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -487,7 +489,7 @@ "val": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } }, @@ -592,7 +594,7 @@ { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } ] @@ -605,6 +607,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -692,7 +695,7 @@ "val": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } }, @@ -797,7 +800,7 @@ { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } ] @@ -810,6 +813,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -897,7 +901,7 @@ "val": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } }, @@ -1002,7 +1006,7 @@ { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } ] @@ -1015,6 +1019,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1102,7 +1107,7 @@ "val": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } }, @@ -1207,7 +1212,7 @@ { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } ] @@ -1220,6 +1225,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1307,7 +1313,7 @@ "val": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } }, @@ -1412,7 +1418,7 @@ { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } ] @@ -1425,6 +1431,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1512,7 +1519,7 @@ "val": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } }, @@ -1617,7 +1624,7 @@ { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } ] @@ -1630,6 +1637,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1717,7 +1725,7 @@ "val": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } }, @@ -1822,7 +1830,7 @@ { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } ] @@ -1835,6 +1843,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1922,7 +1931,7 @@ "val": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } }, @@ -2027,7 +2036,7 @@ { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } ] @@ -2040,6 +2049,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -2280,7 +2290,7 @@ "val": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } }, @@ -2299,7 +2309,7 @@ "val": { "i128": { "hi": 0, - "lo": 70 + "lo": 46 } } }, @@ -2484,7 +2494,7 @@ "val": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } }, @@ -2503,7 +2513,7 @@ "val": { "i128": { "hi": 0, - "lo": 71 + "lo": 47 } } }, @@ -2688,7 +2698,7 @@ "val": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } }, @@ -2707,7 +2717,7 @@ "val": { "i128": { "hi": 0, - "lo": 72 + "lo": 40 } } }, @@ -2892,7 +2902,7 @@ "val": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } }, @@ -2911,7 +2921,7 @@ "val": { "i128": { "hi": 0, - "lo": 73 + "lo": 41 } } }, @@ -3096,7 +3106,7 @@ "val": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } }, @@ -3115,7 +3125,7 @@ "val": { "i128": { "hi": 0, - "lo": 74 + "lo": 42 } } }, @@ -3300,7 +3310,7 @@ "val": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } }, @@ -3319,7 +3329,7 @@ "val": { "i128": { "hi": 0, - "lo": 75 + "lo": 43 } } }, @@ -3504,7 +3514,7 @@ "val": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } }, @@ -3523,7 +3533,7 @@ "val": { "i128": { "hi": 0, - "lo": 76 + "lo": 44 } } }, @@ -3708,7 +3718,7 @@ "val": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } }, @@ -3727,7 +3737,7 @@ "val": { "i128": { "hi": 0, - "lo": 77 + "lo": 45 } } }, @@ -3912,7 +3922,7 @@ "val": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } }, @@ -3931,7 +3941,7 @@ "val": { "i128": { "hi": 0, - "lo": 78 + "lo": 46 } } }, @@ -4116,7 +4126,7 @@ "val": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } }, @@ -4135,7 +4145,7 @@ "val": { "i128": { "hi": 0, - "lo": 79 + "lo": 47 } } }, @@ -6203,7 +6213,7 @@ "val": { "i128": { "hi": 0, - "lo": 989655 + "lo": 988975 } } }, @@ -6276,7 +6286,7 @@ "val": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } }, @@ -6349,7 +6359,7 @@ "val": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } }, @@ -6422,7 +6432,7 @@ "val": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } }, @@ -6495,7 +6505,7 @@ "val": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } }, @@ -6568,7 +6578,7 @@ "val": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } }, @@ -6641,7 +6651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } }, @@ -6714,7 +6724,7 @@ "val": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } }, @@ -6787,7 +6797,7 @@ "val": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } }, @@ -6860,7 +6870,7 @@ "val": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } }, @@ -6933,7 +6943,7 @@ "val": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } }, @@ -7347,7 +7357,7 @@ "val": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } }, @@ -7469,7 +7479,7 @@ { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } ] @@ -7503,7 +7513,7 @@ "data": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } } @@ -7558,8 +7568,62 @@ { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -7591,6 +7655,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7620,14 +7892,14 @@ { "i128": { "hi": 0, - "lo": 70 + "lo": 46 } }, { "u64": 0 }, { - "bytes": "5a22ea413ca0401b50ee3247d52ff3b500806802a88cf6ae06a0c22c579bbe4b975325be1e325403bc096f467857e34d3e2fc972fcb0adc902fb0ffb07193f02" + "bytes": "364c12645f814ed95cc74f4f7d445f783462b459f2323a462662d9c674058c6b402e1af8f49524a489dab0d79b4d7b21d64c505e77ad303260e54d06e169680f" } ] } @@ -7662,7 +7934,7 @@ { "i128": { "hi": 0, - "lo": 70 + "lo": 46 } } ] @@ -7888,7 +8160,7 @@ { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } ] @@ -7922,7 +8194,7 @@ "data": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } } @@ -7951,6 +8223,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7974,7 +8294,7 @@ { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } }, { @@ -8013,7 +8333,7 @@ "val": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } }, @@ -8032,7 +8352,7 @@ "val": { "i128": { "hi": 0, - "lo": 70 + "lo": 46 } } }, @@ -8148,7 +8468,7 @@ "data": { "i128": { "hi": 0, - "lo": 1030 + "lo": 1150 } } } @@ -8196,7 +8516,7 @@ "val": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } }, @@ -8318,7 +8638,7 @@ { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } ] @@ -8352,7 +8672,7 @@ "data": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } } @@ -8407,7 +8727,7 @@ { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } ] @@ -8421,41 +8741,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 2 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -8467,16 +8761,304 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 71 - } + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 47 + } }, { "u64": 0 }, { - "bytes": "2117b0b382778e26a644d474734c7e729c8449fa371cb7462a6f89d1198b80d050e6b0bbfffb48a3a13c284f6f63bd13e849d9d3584419da8cfdd03b1f92830b" + "bytes": "b73e74e2d5b9ccaa9030f43428d4f3b3ec40d1a5bbe37081c0723af0743edabddd58fd0d8a64712a92682aaa5bf0352a5e05fef7455900e0ab8e96026d5a2b04" } ] } @@ -8511,7 +9093,7 @@ { "i128": { "hi": 0, - "lo": 71 + "lo": 47 } } ] @@ -8737,7 +9319,7 @@ { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } ] @@ -8771,7 +9353,7 @@ "data": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } } @@ -8800,6 +9382,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8823,7 +9453,7 @@ { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } }, { @@ -8862,7 +9492,7 @@ "val": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } }, @@ -8881,7 +9511,7 @@ "val": { "i128": { "hi": 0, - "lo": 71 + "lo": 47 } } }, @@ -8997,7 +9627,7 @@ "data": { "i128": { "hi": 0, - "lo": 1031 + "lo": 1175 } } } @@ -9045,7 +9675,7 @@ "val": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } }, @@ -9167,7 +9797,7 @@ { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } ] @@ -9201,7 +9831,7 @@ "data": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } } @@ -9256,7 +9886,292 @@ { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] } } ] @@ -9266,29 +10181,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 3 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -9318,14 +10210,14 @@ { "i128": { "hi": 0, - "lo": 72 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "8c67db2b74dde5a5a6529e777b02b6dcf23ccd78bbe567ba4f74c2344429aabaff04ce612f9de35df1e9b74cee5f108394d9a0a013b7566203f17bcbbfbd340f" + "bytes": "768256c8b84d94eefb8ae111dd915eb7867b0f9c0d377d7ad833eda0384a04c155423a9e670221ade98ba37f8c580fa09499ef176077dee903a21ef69d547109" } ] } @@ -9360,7 +10252,7 @@ { "i128": { "hi": 0, - "lo": 72 + "lo": 40 } } ] @@ -9586,7 +10478,7 @@ { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } ] @@ -9620,7 +10512,7 @@ "data": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } } @@ -9649,6 +10541,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -9672,7 +10612,7 @@ { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } }, { @@ -9711,7 +10651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } }, @@ -9730,7 +10670,7 @@ "val": { "i128": { "hi": 0, - "lo": 72 + "lo": 40 } } }, @@ -9846,7 +10786,7 @@ "data": { "i128": { "hi": 0, - "lo": 1032 + "lo": 1000 } } } @@ -9894,7 +10834,7 @@ "val": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } }, @@ -10016,7 +10956,7 @@ { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } ] @@ -10050,7 +10990,7 @@ "data": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } } @@ -10105,7 +11045,292 @@ { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -10115,29 +11340,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 4 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -10167,14 +11369,14 @@ { "i128": { "hi": 0, - "lo": 73 + "lo": 41 } }, { "u64": 0 }, { - "bytes": "b66692099a7804383f5a7e641db230d95aed95c1b1b67bdd844660d4e38263e877755e0a69d931841c23c6979eb1f39741c1044446dbc37abb01453c9cb8c80a" + "bytes": "02f6671462b560aba5667318689eb2d05610d976d43e98a483803261a1717718a779e7b4c2aa962c460c2c7ee6d2a8b590f01f8c382a8f7a74ece58e3eda440c" } ] } @@ -10209,7 +11411,7 @@ { "i128": { "hi": 0, - "lo": 73 + "lo": 41 } } ] @@ -10435,7 +11637,7 @@ { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } ] @@ -10469,7 +11671,7 @@ "data": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } } @@ -10498,6 +11700,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -10521,7 +11771,7 @@ { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } }, { @@ -10560,7 +11810,7 @@ "val": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } }, @@ -10579,7 +11829,7 @@ "val": { "i128": { "hi": 0, - "lo": 73 + "lo": 41 } } }, @@ -10695,7 +11945,7 @@ "data": { "i128": { "hi": 0, - "lo": 1033 + "lo": 1025 } } } @@ -10743,7 +11993,7 @@ "val": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } }, @@ -10865,7 +12115,7 @@ { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } ] @@ -10899,7 +12149,7 @@ "data": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } } @@ -10954,7 +12204,292 @@ { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -10964,29 +12499,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 5 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11016,14 +12528,14 @@ { "i128": { "hi": 0, - "lo": 74 + "lo": 42 } }, { "u64": 0 }, { - "bytes": "3d8f1fa603ed3e54929365e7c658fea48a123129844716e672e172f7fedcf1b239597985059f4ff2ede356a4345c4964d7647eeb8bbddea080da04a9948c3501" + "bytes": "325e8902c6ab4247e807f1604fc409ebe33d15a04165f1aa30893ff4381e6bb14145920301f7c740bfbd6034c2806435a2ce42aa58889794b9a572af5ee9620b" } ] } @@ -11058,7 +12570,7 @@ { "i128": { "hi": 0, - "lo": 74 + "lo": 42 } } ] @@ -11284,7 +12796,7 @@ { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } ] @@ -11318,7 +12830,7 @@ "data": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } } @@ -11347,6 +12859,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11370,7 +12930,7 @@ { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } }, { @@ -11409,7 +12969,7 @@ "val": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } }, @@ -11428,7 +12988,7 @@ "val": { "i128": { "hi": 0, - "lo": 74 + "lo": 42 } } }, @@ -11544,7 +13104,7 @@ "data": { "i128": { "hi": 0, - "lo": 1034 + "lo": 1050 } } } @@ -11592,7 +13152,7 @@ "val": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } }, @@ -11714,7 +13274,7 @@ { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } ] @@ -11748,7 +13308,7 @@ "data": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } } @@ -11803,7 +13363,292 @@ { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -11813,29 +13658,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 6 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11865,14 +13687,14 @@ { "i128": { "hi": 0, - "lo": 75 + "lo": 43 } }, { "u64": 0 }, { - "bytes": "82319cdad7c54deb5dd1e94e261f930f7f4296fe7a1b3a1f02e89a9901fae196068164800891f623db1a7fcaf09f095fb379fb1f136a024b94f96413d3d6ee04" + "bytes": "5db6e9be3c6e6f2c25fd2451aaec2678778c007bba3235062eee3d8bdd7665253733b5c59e669854ea10c7e265354faf690bac9bf48606d888ab6910dc1bbb0c" } ] } @@ -11907,7 +13729,7 @@ { "i128": { "hi": 0, - "lo": 75 + "lo": 43 } } ] @@ -12133,7 +13955,7 @@ { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } ] @@ -12167,7 +13989,7 @@ "data": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } } @@ -12196,6 +14018,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -12219,7 +14089,7 @@ { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } }, { @@ -12258,7 +14128,7 @@ "val": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } }, @@ -12277,7 +14147,7 @@ "val": { "i128": { "hi": 0, - "lo": 75 + "lo": 43 } } }, @@ -12393,7 +14263,7 @@ "data": { "i128": { "hi": 0, - "lo": 1035 + "lo": 1075 } } } @@ -12441,7 +14311,7 @@ "val": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } }, @@ -12563,7 +14433,7 @@ { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } ] @@ -12597,7 +14467,7 @@ "data": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } } @@ -12652,7 +14522,292 @@ { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + } + ] } } ] @@ -12662,29 +14817,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 7 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -12714,14 +14846,14 @@ { "i128": { "hi": 0, - "lo": 76 + "lo": 44 } }, { "u64": 0 }, { - "bytes": "19060c5d8f3a537ab390474ef80637ea8c00d844d4b1a9175bff864ee2836209d6a088fa9aaf037bef96b7e85394a7e35f05aca1d428fcfb2aa11a778b9cb602" + "bytes": "cda731688cee9b92b5f21572170b6dbdb4135bfd64b8be21a300cd774da7960fcdd80ff493786d709519136272fc6e1b7c7566ae5f2dd1ea6a00c25aa1ca7f0d" } ] } @@ -12756,7 +14888,7 @@ { "i128": { "hi": 0, - "lo": 76 + "lo": 44 } } ] @@ -12982,7 +15114,7 @@ { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } ] @@ -13016,7 +15148,7 @@ "data": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } } @@ -13045,6 +15177,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13068,7 +15248,7 @@ { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } }, { @@ -13107,7 +15287,7 @@ "val": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } }, @@ -13126,7 +15306,7 @@ "val": { "i128": { "hi": 0, - "lo": 76 + "lo": 44 } } }, @@ -13242,7 +15422,7 @@ "data": { "i128": { "hi": 0, - "lo": 1036 + "lo": 1100 } } } @@ -13290,7 +15470,7 @@ "val": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } }, @@ -13412,7 +15592,7 @@ { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } ] @@ -13446,7 +15626,7 @@ "data": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } } @@ -13501,7 +15681,292 @@ { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] } } ] @@ -13511,29 +15976,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 8 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -13563,14 +16005,14 @@ { "i128": { "hi": 0, - "lo": 77 + "lo": 45 } }, { "u64": 0 }, { - "bytes": "3e45c0d6a763f893f705710184269c18f5600f05b0a0126194e62aea00f2faf73f7cf8c34c992ab19e675518306f9ecd2f9f81105f8e7f7f48d577d11e17fe0c" + "bytes": "85cdcd47e10007537035e84d29dbf2c31202ec64b1310fe7bfa611b2b169200fbf22944745f40538591443ab19084f869b3081b63ba5086d36f2d2136e8e4b06" } ] } @@ -13605,7 +16047,7 @@ { "i128": { "hi": 0, - "lo": 77 + "lo": 45 } } ] @@ -13831,7 +16273,7 @@ { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } ] @@ -13865,7 +16307,7 @@ "data": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } } @@ -13894,6 +16336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13917,7 +16407,7 @@ { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } }, { @@ -13956,7 +16446,7 @@ "val": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } }, @@ -13975,7 +16465,7 @@ "val": { "i128": { "hi": 0, - "lo": 77 + "lo": 45 } } }, @@ -14091,7 +16581,7 @@ "data": { "i128": { "hi": 0, - "lo": 1037 + "lo": 1125 } } } @@ -14139,7 +16629,7 @@ "val": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } }, @@ -14261,7 +16751,7 @@ { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } ] @@ -14295,7 +16785,7 @@ "data": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } } @@ -14350,8 +16840,62 @@ { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -14383,6 +16927,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 9 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14412,14 +17164,14 @@ { "i128": { "hi": 0, - "lo": 78 + "lo": 46 } }, { "u64": 0 }, { - "bytes": "1c8b86304c5955c33f80e62593952cbdaed3183285d32f76790324ea32dd106c16cfc4a301f2042cb3d810db26001301b6f2b4e08924d186fba67bf93283140f" + "bytes": "40f2dd7690303bb23b90b95c85a15cb516b463f18b72e05af08a08e2f8ed263769a66763816f7daccc9d12e54a179817ad2a699f7054b9b886c375999683cf07" } ] } @@ -14454,7 +17206,7 @@ { "i128": { "hi": 0, - "lo": 78 + "lo": 46 } } ] @@ -14680,7 +17432,7 @@ { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } ] @@ -14714,7 +17466,7 @@ "data": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } } @@ -14743,6 +17495,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14766,7 +17566,7 @@ { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } }, { @@ -14805,7 +17605,7 @@ "val": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } }, @@ -14824,7 +17624,7 @@ "val": { "i128": { "hi": 0, - "lo": 78 + "lo": 46 } } }, @@ -14940,7 +17740,7 @@ "data": { "i128": { "hi": 0, - "lo": 1038 + "lo": 1150 } } } @@ -14988,7 +17788,7 @@ "val": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } }, @@ -15110,7 +17910,7 @@ { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } ] @@ -15144,7 +17944,7 @@ "data": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } } @@ -15199,8 +17999,62 @@ { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -15232,6 +18086,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15261,14 +18323,14 @@ { "i128": { "hi": 0, - "lo": 79 + "lo": 47 } }, { "u64": 0 }, { - "bytes": "dd384682c50396e7c61c0611d7a18e044172868409fbcae3078add45887f49490324030c20506fe2f8b717a9f56b149b6c70fa39b239fbac5c541bf45870bb07" + "bytes": "f0a59e86f41e9b1ccbc96b183541801819609f9b0a18157355ad5ad3948d08fbc2f9df2d8239b281e8ba086107eb10ddb3380232be6b416a750c055399f0c705" } ] } @@ -15303,7 +18365,7 @@ { "i128": { "hi": 0, - "lo": 79 + "lo": 47 } } ] @@ -15529,7 +18591,7 @@ { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } ] @@ -15563,7 +18625,7 @@ "data": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } } @@ -15592,6 +18654,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15615,7 +18725,7 @@ { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } }, { @@ -15654,7 +18764,7 @@ "val": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } }, @@ -15673,7 +18783,7 @@ "val": { "i128": { "hi": 0, - "lo": 79 + "lo": 47 } } }, @@ -15789,7 +18899,7 @@ "data": { "i128": { "hi": 0, - "lo": 1039 + "lo": 1175 } } } @@ -15893,7 +19003,7 @@ "data": { "i128": { "hi": 0, - "lo": 989655 + "lo": 988975 } } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.5.json b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.5.json index 2877959..3977844 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.5.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_fifty_user_end_to_end_simulation.5.json @@ -77,7 +77,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } }, @@ -182,7 +182,7 @@ { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } ] @@ -195,6 +195,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -282,7 +283,7 @@ "val": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } }, @@ -387,7 +388,7 @@ { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } ] @@ -400,6 +401,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -487,7 +489,7 @@ "val": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } }, @@ -592,7 +594,7 @@ { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } ] @@ -605,6 +607,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -692,7 +695,7 @@ "val": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } }, @@ -797,7 +800,7 @@ { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } ] @@ -810,6 +813,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -897,7 +901,7 @@ "val": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } }, @@ -1002,7 +1006,7 @@ { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } ] @@ -1015,6 +1019,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1102,7 +1107,7 @@ "val": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } }, @@ -1207,7 +1212,7 @@ { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } ] @@ -1220,6 +1225,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1307,7 +1313,7 @@ "val": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } }, @@ -1412,7 +1418,7 @@ { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } ] @@ -1425,6 +1431,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1512,7 +1519,7 @@ "val": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } }, @@ -1617,7 +1624,7 @@ { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } ] @@ -1630,6 +1637,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1717,7 +1725,7 @@ "val": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } }, @@ -1822,7 +1830,7 @@ { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } ] @@ -1835,6 +1843,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1922,7 +1931,7 @@ "val": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } }, @@ -2027,7 +2036,7 @@ { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } ] @@ -2040,6 +2049,7 @@ ] ], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -2280,7 +2290,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } }, @@ -2299,7 +2309,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, @@ -2484,7 +2494,7 @@ "val": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } }, @@ -2503,7 +2513,7 @@ "val": { "i128": { "hi": 0, - "lo": 81 + "lo": 41 } } }, @@ -2688,7 +2698,7 @@ "val": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } }, @@ -2707,7 +2717,7 @@ "val": { "i128": { "hi": 0, - "lo": 82 + "lo": 42 } } }, @@ -2892,7 +2902,7 @@ "val": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } }, @@ -2911,7 +2921,7 @@ "val": { "i128": { "hi": 0, - "lo": 83 + "lo": 43 } } }, @@ -3096,7 +3106,7 @@ "val": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } }, @@ -3115,7 +3125,7 @@ "val": { "i128": { "hi": 0, - "lo": 84 + "lo": 44 } } }, @@ -3300,7 +3310,7 @@ "val": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } }, @@ -3319,7 +3329,7 @@ "val": { "i128": { "hi": 0, - "lo": 85 + "lo": 45 } } }, @@ -3504,7 +3514,7 @@ "val": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } }, @@ -3523,7 +3533,7 @@ "val": { "i128": { "hi": 0, - "lo": 86 + "lo": 46 } } }, @@ -3708,7 +3718,7 @@ "val": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } }, @@ -3727,7 +3737,7 @@ "val": { "i128": { "hi": 0, - "lo": 87 + "lo": 47 } } }, @@ -3912,7 +3922,7 @@ "val": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } }, @@ -3931,7 +3941,7 @@ "val": { "i128": { "hi": 0, - "lo": 88 + "lo": 40 } } }, @@ -4116,7 +4126,7 @@ "val": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } }, @@ -4135,7 +4145,7 @@ "val": { "i128": { "hi": 0, - "lo": 89 + "lo": 41 } } }, @@ -6203,7 +6213,7 @@ "val": { "i128": { "hi": 0, - "lo": 989555 + "lo": 989275 } } }, @@ -6276,7 +6286,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } }, @@ -6349,7 +6359,7 @@ "val": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } }, @@ -6422,7 +6432,7 @@ "val": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } }, @@ -6495,7 +6505,7 @@ "val": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } }, @@ -6568,7 +6578,7 @@ "val": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } }, @@ -6641,7 +6651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } }, @@ -6714,7 +6724,7 @@ "val": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } }, @@ -6787,7 +6797,7 @@ "val": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } }, @@ -6860,7 +6870,7 @@ "val": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } }, @@ -6933,7 +6943,7 @@ "val": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } }, @@ -7347,7 +7357,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } }, @@ -7469,7 +7479,7 @@ { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } ] @@ -7503,7 +7513,7 @@ "data": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } } @@ -7558,8 +7568,62 @@ { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -7591,6 +7655,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7620,14 +7892,14 @@ { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "e2a94c3b6dff951ce7886580f6059823e7b14f23b83ee8dc1085cf1f0dce39ac69d532ca0c7d9eb9956e2f444a08b3947f1c9cccb8cba27fe6293485f20c6a04" + "bytes": "f3bdf6eb753157650d1687999d22798556297ff817141b43381969e7a981c98f73d17aa603cb38614a91578a2b58c0ad1f16643ee4d0613d2b19b5ca3712dc05" } ] } @@ -7662,7 +7934,7 @@ { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } ] @@ -7888,7 +8160,7 @@ { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } ] @@ -7922,7 +8194,7 @@ "data": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } } @@ -7951,6 +8223,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -7974,7 +8294,7 @@ { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } }, { @@ -8013,7 +8333,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } }, @@ -8032,7 +8352,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, @@ -8148,7 +8468,7 @@ "data": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1000 } } } @@ -8196,7 +8516,7 @@ "val": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } }, @@ -8318,7 +8638,7 @@ { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } ] @@ -8352,7 +8672,7 @@ "data": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } } @@ -8407,7 +8727,7 @@ { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } ] @@ -8421,41 +8741,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 2 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -8467,16 +8761,304 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 81 - } + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 41 + } }, { "u64": 0 }, { - "bytes": "de0c941d45277de86bb6075286efd0f1ab3c3fee9cb8ad63952ea2f2745fd6917a87845720472aea6b6dc2f68751d1c3a87a293318224f244224e771a3c18d0c" + "bytes": "b0c4a348b2142ce3d68ecf85aa173005d75bf5dd1f736fe95f07862e505da9c9fdb63f13924aa5ea93883df60ed73a2a4984592361cf135dc3a35075ab28a905" } ] } @@ -8511,7 +9093,7 @@ { "i128": { "hi": 0, - "lo": 81 + "lo": 41 } } ] @@ -8737,7 +9319,7 @@ { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } ] @@ -8771,7 +9353,7 @@ "data": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } } @@ -8800,6 +9382,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -8823,7 +9453,7 @@ { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } }, { @@ -8862,7 +9492,7 @@ "val": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } }, @@ -8881,7 +9511,7 @@ "val": { "i128": { "hi": 0, - "lo": 81 + "lo": 41 } } }, @@ -8997,7 +9627,7 @@ "data": { "i128": { "hi": 0, - "lo": 1041 + "lo": 1025 } } } @@ -9045,7 +9675,7 @@ "val": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } }, @@ -9167,7 +9797,7 @@ { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } ] @@ -9201,7 +9831,7 @@ "data": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } } @@ -9256,7 +9886,292 @@ { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 3 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1050 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] } } ] @@ -9266,29 +10181,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 3 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -9318,14 +10210,14 @@ { "i128": { "hi": 0, - "lo": 82 + "lo": 42 } }, { "u64": 0 }, { - "bytes": "efbf61f0cacf3470547d075e91b907a789b6a32733ad3fd2df2d2bdb3cd56d52449b81756224a8851f889e6521885282802a187827f64350bfde404c2e9abe0d" + "bytes": "58203a1c6196802ee6dd1506d260d164fa4fff95bf83641bb1f2e7d06c98d17fdd99897719feeeb6f12878bd09594bd86d8fea0726bf4c283a5d547a24497b0b" } ] } @@ -9360,7 +10252,7 @@ { "i128": { "hi": 0, - "lo": 82 + "lo": 42 } } ] @@ -9586,7 +10478,7 @@ { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } ] @@ -9620,7 +10512,7 @@ "data": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } } @@ -9649,6 +10541,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1050 + } + }, + { + "i128": { + "hi": 0, + "lo": 42 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -9672,7 +10612,7 @@ { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } }, { @@ -9711,7 +10651,7 @@ "val": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } }, @@ -9730,7 +10670,7 @@ "val": { "i128": { "hi": 0, - "lo": 82 + "lo": 42 } } }, @@ -9846,7 +10786,7 @@ "data": { "i128": { "hi": 0, - "lo": 1042 + "lo": 1050 } } } @@ -9894,7 +10834,7 @@ "val": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } }, @@ -10016,7 +10956,7 @@ { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } ] @@ -10050,7 +10990,7 @@ "data": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } } @@ -10105,7 +11045,292 @@ { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 4 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1075 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + } + ] + } + ] } } ] @@ -10115,29 +11340,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 4 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -10167,14 +11369,14 @@ { "i128": { "hi": 0, - "lo": 83 + "lo": 43 } }, { "u64": 0 }, { - "bytes": "38a44d079e557be046a134aff30f7b70bb7fd887a192eec86a12679e17b84f23d077ed3ec9a4164304cb4b071d64bc8792f8a31253ecbc98a7f74e344aeb1005" + "bytes": "ce92b45540af8346ab71c2323647054722445dcae4ddd77c0ca81664d72532bee6130e61a9da5ce71f87a4c29d06c4319a973bfa81d42c076c47afd534f7ef07" } ] } @@ -10209,7 +11411,7 @@ { "i128": { "hi": 0, - "lo": 83 + "lo": 43 } } ] @@ -10435,7 +11637,7 @@ { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } ] @@ -10469,7 +11671,7 @@ "data": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } } @@ -10498,6 +11700,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 4 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1075 + } + }, + { + "i128": { + "hi": 0, + "lo": 43 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -10521,7 +11771,7 @@ { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } }, { @@ -10560,7 +11810,7 @@ "val": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } }, @@ -10579,7 +11829,7 @@ "val": { "i128": { "hi": 0, - "lo": 83 + "lo": 43 } } }, @@ -10695,7 +11945,7 @@ "data": { "i128": { "hi": 0, - "lo": 1043 + "lo": 1075 } } } @@ -10743,7 +11993,7 @@ "val": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } }, @@ -10865,7 +12115,7 @@ { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } ] @@ -10899,7 +12149,7 @@ "data": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } } @@ -10954,7 +12204,292 @@ { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 5 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + } + ] + } + ] } } ] @@ -10964,29 +12499,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 5 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11016,14 +12528,14 @@ { "i128": { "hi": 0, - "lo": 84 + "lo": 44 } }, { "u64": 0 }, { - "bytes": "8c351f097382ba7cb2302ab83ef686a8c0999dcd8e6aad88e1cb41c19f2721a6fe3585f5455d9d6fa9e55e1ee583335668984a8d5e0ba8c8ac704b95dfc99807" + "bytes": "86df43a17e7b99e9b4ac8c3288c9b2427c135f9156b1067eec8bb704aa12d19fe9605505e9e63c13348f37ca9332066a4fecb7b52d99a9424ff9fab4f01a2507" } ] } @@ -11058,7 +12570,7 @@ { "i128": { "hi": 0, - "lo": 84 + "lo": 44 } } ] @@ -11284,7 +12796,7 @@ { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } ] @@ -11318,7 +12830,7 @@ "data": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } } @@ -11347,6 +12859,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 5 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1100 + } + }, + { + "i128": { + "hi": 0, + "lo": 44 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -11370,7 +12930,7 @@ { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } }, { @@ -11409,7 +12969,7 @@ "val": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } }, @@ -11428,7 +12988,7 @@ "val": { "i128": { "hi": 0, - "lo": 84 + "lo": 44 } } }, @@ -11544,7 +13104,7 @@ "data": { "i128": { "hi": 0, - "lo": 1044 + "lo": 1100 } } } @@ -11592,7 +13152,7 @@ "val": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } }, @@ -11714,7 +13274,7 @@ { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } ] @@ -11748,7 +13308,7 @@ "data": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } } @@ -11803,7 +13363,292 @@ { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 6 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1125 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + } + ] + } + ] } } ] @@ -11813,29 +13658,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 6 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -11865,14 +13687,14 @@ { "i128": { "hi": 0, - "lo": 85 + "lo": 45 } }, { "u64": 0 }, { - "bytes": "67095d1c9b515479564af51b896c715701809d5573a3de47cf66fb757ae9f4f7858c4c6caca12ec3e1ff14fbea765ab612883547e8597a0fa756160c8774d809" + "bytes": "93db3d860077a2b9de2d750e66d22966fb2a983a0abd22344576e4ce758db78145e3ae1a13468cae55ac42158bab13528b13837de933d0692d8f623124fc2506" } ] } @@ -11907,7 +13729,7 @@ { "i128": { "hi": 0, - "lo": 85 + "lo": 45 } } ] @@ -12133,7 +13955,7 @@ { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } ] @@ -12167,7 +13989,7 @@ "data": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } } @@ -12196,6 +14018,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 6 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1125 + } + }, + { + "i128": { + "hi": 0, + "lo": 45 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -12219,7 +14089,7 @@ { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } }, { @@ -12258,7 +14128,7 @@ "val": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } }, @@ -12277,7 +14147,7 @@ "val": { "i128": { "hi": 0, - "lo": 85 + "lo": 45 } } }, @@ -12393,7 +14263,7 @@ "data": { "i128": { "hi": 0, - "lo": 1045 + "lo": 1125 } } } @@ -12441,7 +14311,7 @@ "val": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } }, @@ -12563,7 +14433,7 @@ { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } ] @@ -12597,7 +14467,7 @@ "data": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } } @@ -12652,7 +14522,292 @@ { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 7 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1150 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + } + ] } } ] @@ -12662,29 +14817,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 7 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -12714,14 +14846,14 @@ { "i128": { "hi": 0, - "lo": 86 + "lo": 46 } }, { "u64": 0 }, { - "bytes": "19ed90dbec83cb4b90365d366992e0080ea80c7a15822620d7977d65177bcdec37ee509570fdbe99bf6eab3ee34830abd383f0fcd6ef8d3a1b13a79b8ddb1705" + "bytes": "78de14750f19ddd60f832717c51cdaebb03269672380e312d2a52f14124acacf2fd1d76793d52b901fd9d5924af9e7846eb7285613512f80b677dddafe79cf0c" } ] } @@ -12756,7 +14888,7 @@ { "i128": { "hi": 0, - "lo": 86 + "lo": 46 } } ] @@ -12982,7 +15114,7 @@ { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } ] @@ -13016,7 +15148,7 @@ "data": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } } @@ -13045,6 +15177,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 7 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1150 + } + }, + { + "i128": { + "hi": 0, + "lo": 46 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13068,7 +15248,7 @@ { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } }, { @@ -13107,7 +15287,7 @@ "val": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } }, @@ -13126,7 +15306,7 @@ "val": { "i128": { "hi": 0, - "lo": 86 + "lo": 46 } } }, @@ -13242,7 +15422,7 @@ "data": { "i128": { "hi": 0, - "lo": 1046 + "lo": 1150 } } } @@ -13290,7 +15470,7 @@ "val": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } }, @@ -13412,7 +15592,7 @@ { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } ] @@ -13446,7 +15626,7 @@ "data": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } } @@ -13501,7 +15681,292 @@ { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 8 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1175 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] } } ] @@ -13511,29 +15976,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 8 - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -13563,14 +16005,14 @@ { "i128": { "hi": 0, - "lo": 87 + "lo": 47 } }, { "u64": 0 }, { - "bytes": "be77e46e11a4445948d6be660cc6b98426ec50a2ffcaa18613c4354ab81eceb874cddb51f066fa38f9d9f4be5220a81e2e7afef83c773e15ff8e2e4dded7840b" + "bytes": "21d4c6568c4c893fc4c0af018bd4d0ed57effc83e1fad9ddf600a74ee7f2a9a2161f38819e538a6e0e0dc181fb3675662308f82c3fa07b58cb3b62aa8f7bf407" } ] } @@ -13605,7 +16047,7 @@ { "i128": { "hi": 0, - "lo": 87 + "lo": 47 } } ] @@ -13831,7 +16273,7 @@ { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } ] @@ -13865,7 +16307,7 @@ "data": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } } @@ -13894,6 +16336,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 8 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1175 + } + }, + { + "i128": { + "hi": 0, + "lo": 47 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -13917,7 +16407,7 @@ { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } }, { @@ -13956,7 +16446,7 @@ "val": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } }, @@ -13975,7 +16465,7 @@ "val": { "i128": { "hi": 0, - "lo": 87 + "lo": 47 } } }, @@ -14091,7 +16581,7 @@ "data": { "i128": { "hi": 0, - "lo": 1047 + "lo": 1175 } } } @@ -14139,7 +16629,7 @@ "val": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } }, @@ -14261,7 +16751,7 @@ { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } ] @@ -14295,7 +16785,7 @@ "data": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } } @@ -14350,8 +16840,62 @@ { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -14383,6 +16927,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 9 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14412,14 +17164,14 @@ { "i128": { "hi": 0, - "lo": 88 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "4b369131d5cfa8fdf3b05573d0c0d853185b87b879639c8959d4d207b74c85548020b7f8991f8b753a19e0b743f562fafda60fb9d571f61c9ca17d90b7adda0f" + "bytes": "d4648fd46183ca8c9f003f3259fb7b7bf958d251b12f5e89c49bd6751b6fd42c665652a2f062db9be7c17fe7b92293e3c7fec3155edfdb906dc2914298af780d" } ] } @@ -14454,7 +17206,7 @@ { "i128": { "hi": 0, - "lo": 88 + "lo": 40 } } ] @@ -14680,7 +17432,7 @@ { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } ] @@ -14714,7 +17466,7 @@ "data": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } } @@ -14743,6 +17495,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 9 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4BV5" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -14766,7 +17566,7 @@ { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } }, { @@ -14805,7 +17605,7 @@ "val": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } }, @@ -14824,7 +17624,7 @@ "val": { "i128": { "hi": 0, - "lo": 88 + "lo": 40 } } }, @@ -14940,7 +17740,7 @@ "data": { "i128": { "hi": 0, - "lo": 1048 + "lo": 1000 } } } @@ -14988,7 +17788,7 @@ "val": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } }, @@ -15110,7 +17910,7 @@ { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } ] @@ -15144,7 +17944,7 @@ "data": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } } @@ -15199,8 +17999,62 @@ { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 25 } + }, + { + "u64": 1 + }, + { + "u64": 2 } ] } @@ -15232,6 +18086,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1025 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 25 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15261,14 +18323,14 @@ { "i128": { "hi": 0, - "lo": 89 + "lo": 41 } }, { "u64": 0 }, { - "bytes": "bb05f06a2a202215b8cd6f8e2c2ce842730c5259dcec757ab59392b684a83660e71a067997e32f0e08c334e8eb5a39c5ee4e5db4921f9e7754eafa74442e1a0d" + "bytes": "fcc334487ed0de43025a2cd64a471dd18bd7aa9999f5843b4e3820b640616df48dd1771190860bb7c67960c7dbe62e7b957d35e0cfbefc2fc0ebf2387a72a600" } ] } @@ -15303,7 +18365,7 @@ { "i128": { "hi": 0, - "lo": 89 + "lo": 41 } } ] @@ -15529,7 +18591,7 @@ { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } ] @@ -15563,7 +18625,7 @@ "data": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } } @@ -15592,6 +18654,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 10 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": { + "hi": 0, + "lo": 1025 + } + }, + { + "i128": { + "hi": 0, + "lo": 41 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -15615,7 +18725,7 @@ { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } }, { @@ -15654,7 +18764,7 @@ "val": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } }, @@ -15673,7 +18783,7 @@ "val": { "i128": { "hi": 0, - "lo": 89 + "lo": 41 } } }, @@ -15789,7 +18899,7 @@ "data": { "i128": { "hi": 0, - "lo": 1049 + "lo": 1025 } } } @@ -15893,7 +19003,7 @@ "data": { "i128": { "hi": 0, - "lo": 989555 + "lo": 989275 } } } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_finalize_payment_alias_matches_pay_batch.1.json b/contracts/core-flow/test_snapshots/test/tests/test_finalize_payment_alias_matches_pay_batch.1.json index c63f5bb..a39bb22 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_finalize_payment_alias_matches_pay_batch.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_finalize_payment_alias_matches_pay_batch.1.json @@ -195,6 +195,9 @@ ] ], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1635,6 +1638,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1658,6 +1715,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1694,7 +2216,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -2018,6 +2540,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_finalize_transfers_funds_to_workers.1.json b/contracts/core-flow/test_snapshots/test/tests/test_finalize_transfers_funds_to_workers.1.json index 68ad5af..e5f04ef 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_finalize_transfers_funds_to_workers.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_finalize_transfers_funds_to_workers.1.json @@ -290,6 +290,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -549,7 +554,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 20 } } }, @@ -642,7 +647,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32 } } }, @@ -1995,93 +2000,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" - }, - { - "symbol": "balance" - } - ], - "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "balance" - } - ], - "data": { - "i128": { - "hi": 0, - "lo": 13000 - } - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -2093,118 +2020,28 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "hours" - }, - { - "symbol": "submit" - } - ], - "data": { - "vec": [ - { - "u32": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" }, { - "u32": 0 + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" }, { "i128": { "hi": 0, - "lo": 40 + "lo": 5000 } - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 1 }, { "i128": { "hi": 0, - "lo": 40 + "lo": 250 } }, { - "u64": 1 + "u64": 1000 }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" + "u64": 2000 } ] } @@ -2222,10 +2059,10 @@ "v0": { "topics": [ { - "symbol": "hours" + "symbol": "payment" }, { - "symbol": "submit" + "symbol": "add" } ], "data": { @@ -2236,11 +2073,29 @@ { "u32": 1 }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, { "i128": { "hi": 0, - "lo": 40 + "lo": 250 } + }, + { + "u64": 1000 + }, + { + "u64": 2000 } ] } @@ -2261,10 +2116,12 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "initialize_multi_sig_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -2282,37 +2139,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "manager_approve" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "approve" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { - "symbol": "manager" + "symbol": "balance" } ], "data": { - "u32": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -2322,7 +2156,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -2331,10 +2165,15 @@ "symbol": "fn_return" }, { - "symbol": "manager_approve" + "symbol": "balance" } ], - "data": "void" + "data": { + "i128": { + "hi": 0, + "lo": 13000 + } + } } } }, @@ -2355,7 +2194,7 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "finance_approve" + "symbol": "get_escrow" } ], "data": { @@ -2370,23 +2209,1298 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "approve" + "symbol": "fn_return" }, { - "symbol": "finance" + "symbol": "get_escrow" } ], "data": { - "u32": 1 - } - } - } - }, + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + }, + { + "u64": 0 + }, + { + "bytes": "b6ebcd4469c7dae15fe1dc6dcc0bd31e39ca0640e1168820a1bc0df3688c27720d8c78dc3c5c44fac27997fb2dfd7adc92cb1afc9487eff89d103fb1237f4700" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + }, + { + "u64": 1 + }, + { + "bytes": "9893e1c074420b367134a73ff93579053a8f08d96398000b7aa74f6de48dea491df543614af5a469ee16fe41be96b69e39fcda3a004afa8a5f0194cc65e3f904" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "manager_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "manager" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "manager_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "finance_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "finance" + } + ], + "data": { + "u32": 1 + } + } + } + }, "failed_call": false }, { @@ -2528,6 +3642,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2620,6 +3782,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2701,7 +3911,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 20 } } }, @@ -2794,7 +4004,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_finalize_without_finance_approval_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_finalize_without_finance_approval_fails.1.json index c6bda2e..c3953ea 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_finalize_without_finance_approval_fails.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_finalize_without_finance_approval_fails.1.json @@ -1457,6 +1457,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_full_approval_and_finalize_flow.1.json b/contracts/core-flow/test_snapshots/test/tests/test_full_approval_and_finalize_flow.1.json index 9f16876..89d5fa6 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_full_approval_and_finalize_flow.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_full_approval_and_finalize_flow.1.json @@ -195,6 +195,9 @@ ] ], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1640,41 +1643,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -1686,52 +1663,28 @@ "u32": 0 }, { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "hours" - }, - { - "symbol": "submit" - } - ], - "data": { - "vec": [ - { - "u32": 1 + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" }, { - "u32": 0 + "i128": { + "hi": 0, + "lo": 10000 + } }, { "i128": { "hi": 0, - "lo": 40 + "lo": 250 } + }, + { + "u64": 1000 + }, + { + "u64": 2000 } ] } @@ -1752,54 +1705,7 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "manager_approve" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "approve" - }, - { - "symbol": "manager" + "symbol": "initialize_multi_sig_escrow" } ], "data": { @@ -1810,27 +1716,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "manager_approve" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -1911,7 +1796,7 @@ "symbol": "manager_approved" }, "val": { - "bool": true + "bool": false } }, { @@ -1981,7 +1866,7 @@ "symbol": "proof_verified" }, "val": { - "bool": true + "bool": false } }, { @@ -2054,30 +1939,7 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "finance_approve" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "approve" - }, - { - "symbol": "finance" + "symbol": "get_nonce" } ], "data": { @@ -2100,10 +1962,12 @@ "symbol": "fn_return" }, { - "symbol": "finance_approve" + "symbol": "get_nonce" } ], - "data": "void" + "data": { + "u64": 0 + } } } }, @@ -2165,7 +2029,7 @@ "symbol": "finance_approved" }, "val": { - "bool": true + "bool": false } }, { @@ -2189,7 +2053,7 @@ "symbol": "manager_approved" }, "val": { - "bool": true + "bool": false } }, { @@ -2259,7 +2123,7 @@ "symbol": "proof_verified" }, "val": { - "bool": true + "bool": false } }, { @@ -2332,11 +2196,30 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "finalize_payment" + "symbol": "submit_hours_proof" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + }, + { + "u64": 0 + }, + { + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" + } + ] } } } @@ -2347,32 +2230,29 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + "symbol": "hours" }, { - "symbol": "transfer" + "symbol": "submit" } ], "data": { "vec": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u32": 1 }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "u32": 0 }, { "i128": { "hi": 0, - "lo": 10000 + "lo": 40 } } ] @@ -2385,30 +2265,19 @@ { "event": { "ext": "v0", - "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", - "type_": "contract", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "transfer" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + "symbol": "fn_return" }, { - "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + "symbol": "submit_hours_proof" } ], - "data": { - "i128": { - "hi": 0, - "lo": 10000 - } - } + "data": "void" } } }, @@ -2417,19 +2286,720 @@ { "event": { "ext": "v0", - "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "transfer" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "manager_approve" } ], - "data": "void" + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "manager" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "manager_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "finance_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "finance" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "finance_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "finalize_payment" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_get_nonce_tracks_expected_value.1.json b/contracts/core-flow/test_snapshots/test/tests/test_get_nonce_tracks_expected_value.1.json index a58e445..71bdb5c 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_get_nonce_tracks_expected_value.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_get_nonce_tracks_expected_value.1.json @@ -196,6 +196,7 @@ ], [], [], + [], [] ], "ledger": { @@ -1407,6 +1408,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1479,6 +1534,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1515,7 +1778,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_hours_must_match_escrowed_amount.1.json b/contracts/core-flow/test_snapshots/test/tests/test_hours_must_match_escrowed_amount.1.json new file mode 100644 index 0000000..25c7bb1 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_hours_must_match_escrowed_amount.1.json @@ -0,0 +1,2053 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 80 + } + }, + { + "u64": 0 + }, + { + "bytes": "f34fa264aee6a0771c184e41b9f9ccbed3b6551284a5336af1f2a13ed95ff6b6bb87796dd50d7cb8388a41e062467ac4a62e73af50b80e7efac3479d6b06fe03" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "error": { + "contract": 17 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 17 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 17 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "submit_hours_proof" + }, + { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 80 + } + }, + { + "u64": 0 + }, + { + "bytes": "f34fa264aee6a0771c184e41b9f9ccbed3b6551284a5336af1f2a13ed95ff6b6bb87796dd50d7cb8388a41e062467ac4a62e73af50b80e7efac3479d6b06fe03" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_init_admin_is_first_caller_wins_without_a_pin.1.json b/contracts/core-flow/test_snapshots/test/tests/test_init_admin_is_first_caller_wins_without_a_pin.1.json new file mode 100644 index 0000000..d881dfc --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_init_admin_is_first_caller_wins_without_a_pin.1.json @@ -0,0 +1,374 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "error": { + "contract": 12 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 12 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 12 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "init_admin" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_initialize_multi_sig_escrow_happy_path.1.json b/contracts/core-flow/test_snapshots/test/tests/test_initialize_multi_sig_escrow_happy_path.1.json index d6f0e0e..225ea73 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_initialize_multi_sig_escrow_happy_path.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_initialize_multi_sig_escrow_happy_path.1.json @@ -1405,6 +1405,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_initialize_pulls_funds_into_custody.1.json b/contracts/core-flow/test_snapshots/test/tests/test_initialize_pulls_funds_into_custody.1.json index ff14d64..f695b08 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_initialize_pulls_funds_into_custody.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_initialize_pulls_funds_into_custody.1.json @@ -1406,6 +1406,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_inverted_pay_period_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_inverted_pay_period_rejected.1.json new file mode 100644 index 0000000..4e42d7d --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_inverted_pay_period_rejected.1.json @@ -0,0 +1,933 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000006" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "04cadb4a570fd2e4652e814101509912cce6c9a2325d6eec8d7100caf859f3e0", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "error": { + "contract": 18 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 18 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 18 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "initialize_multi_sig_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_multiple_escrows_sequential_ids.1.json b/contracts/core-flow/test_snapshots/test/tests/test_multiple_escrows_sequential_ids.1.json index 9fff45d..fb2d123 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_multiple_escrows_sequential_ids.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_multiple_escrows_sequential_ids.1.json @@ -2260,6 +2260,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2544,6 +2598,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 2 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2828,6 +2936,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_multiple_payment_schedules.1.json b/contracts/core-flow/test_snapshots/test/tests/test_multiple_payment_schedules.1.json index a8a389f..2fe35fc 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_multiple_payment_schedules.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_multiple_payment_schedules.1.json @@ -290,6 +290,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -546,7 +551,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 20 } } }, @@ -639,7 +644,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32 } } }, @@ -1988,6 +1993,114 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2327,66 +2440,11 @@ "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_hours_proof" - } - ], - "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "hours" - }, - { - "symbol": "submit" + "symbol": "get_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "u32": 1 } } } @@ -2405,87 +2463,1143 @@ "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "hours" - }, - { - "symbol": "submit" - } - ], - "data": { - "vec": [ - { - "u32": 1 + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } }, { - "i128": { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + }, + { + "u64": 0 + }, + { + "bytes": "b6ebcd4469c7dae15fe1dc6dcc0bd31e39ca0640e1168820a1bc0df3688c27720d8c78dc3c5c44fac27997fb2dfd7adc92cb1afc9487eff89d103fb1237f4700" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + }, + { + "u64": 1 + }, + { + "bytes": "9893e1c074420b367134a73ff93579053a8f08d96398000b7aa74f6de48dea491df543614af5a469ee16fe41be96b69e39fcda3a004afa8a5f0194cc65e3f904" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { "hi": 0, - "lo": 40 + "lo": 32 } } ] @@ -2774,6 +3888,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2866,6 +4028,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -2947,7 +4157,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 20 } } }, @@ -3040,7 +4250,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 32 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_nonce_increments_after_proof.1.json b/contracts/core-flow/test_snapshots/test/tests/test_nonce_increments_after_proof.1.json index dcfb47a..c2108a7 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_nonce_increments_after_proof.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_nonce_increments_after_proof.1.json @@ -196,6 +196,10 @@ ], [], [], + [], + [], + [], + [], [] ], "ledger": { @@ -396,7 +400,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, @@ -1411,41 +1415,15 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "initialize_multi_sig_escrow" - } - ], - "data": { - "u32": 1 - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "payment" }, { - "symbol": "submit_hours_proof" + "symbol": "add" } ], "data": { @@ -1456,17 +1434,29 @@ { "u32": 0 }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, { "i128": { "hi": 0, - "lo": 40 + "lo": 10000 } }, { - "u64": 0 + "i128": { + "hi": 0, + "lo": 250 + } }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "u64": 1000 + }, + { + "u64": 2000 } ] } @@ -1479,32 +1469,19 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "u32": 1 } } } @@ -1514,19 +1491,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -1535,40 +1517,177 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 0 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 80 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } }, { - "bytes": "a4b9cf06f3a7bdcbbc5b2df03ddb68a1bcda6ac34f586250b9461fc1bbe8f54ca8416cc3e96d53cdf9ba701022ec8114224f2612bb193296736c88fe43bf4d0c" + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } } ] } @@ -1580,16 +1699,19 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": null, + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_call" }, { - "symbol": "submit" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" } ], "data": { @@ -1603,11 +1725,412 @@ { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } - } - ] - } + }, + { + "u64": 0 + }, + { + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + }, + { + "u64": 1 + }, + { + "bytes": "3db63200510731d5b7fd44cb1d446ada545d7f880706526f1f0a7e30122fae733b6092d20b01a9af5bb9e49cc76ab2aec487dfe3edfc07e990b2fdf952085805" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } } } }, @@ -1634,6 +2157,55 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1767,7 +2339,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_nonce_replay_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_nonce_replay_rejected.1.json index 1eb1eb1..87a30ef 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_nonce_replay_rejected.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_nonce_replay_rejected.1.json @@ -195,6 +195,7 @@ ] ], [], + [], [] ], "ledger": { @@ -1406,6 +1407,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1429,6 +1484,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1465,7 +1728,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1567,7 +1830,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1669,7 +1932,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_pause_blocks_new_escrow.1.json b/contracts/core-flow/test_snapshots/test/tests/test_pause_blocks_new_escrow.1.json index cec2d95..bddf86f 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_pause_blocks_new_escrow.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_pause_blocks_new_escrow.1.json @@ -67,6 +67,25 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -160,6 +179,51 @@ 15 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], [ { "contract_data": { @@ -250,6 +314,39 @@ 15 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], [ { "contract_data": { @@ -760,6 +857,29 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -781,6 +901,76 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_pause_for_upgrade_does_not_trap_funds.1.json b/contracts/core-flow/test_snapshots/test/tests/test_pause_for_upgrade_does_not_trap_funds.1.json new file mode 100644 index 0000000..9d9cd11 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_pause_for_upgrade_does_not_trap_funds.1.json @@ -0,0 +1,2271 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "cancel_escrow", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": true + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "cancel_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "cancel" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "cancel_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_refuses_payment_without_oracle_proof.1.json b/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_refuses_payment_without_oracle_proof.1.json index f234d22..ec1a564 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_refuses_payment_without_oracle_proof.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_refuses_payment_without_oracle_proof.1.json @@ -1511,6 +1511,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_settles_two_assets_in_one_call.1.json b/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_settles_two_assets_in_one_call.1.json index fe39e73..4b822eb 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_settles_two_assets_in_one_call.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_pay_batch_settles_two_assets_in_one_call.1.json @@ -358,6 +358,11 @@ [], [], [], + [], + [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -681,7 +686,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 5000 } } }, @@ -774,7 +779,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 8000 } } }, @@ -2719,19 +2724,50 @@ "event": { "ext": "v0", "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "payment" }, { - "symbol": "initialize_multi_sig_escrow" + "symbol": "add" } ], "data": { - "u32": 1 + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -2741,23 +2777,51 @@ { "event": { "ext": "v0", - "contract_id": null, - "type_": "diagnostic", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + "symbol": "payment" }, { - "symbol": "balance" + "symbol": "add" } ], "data": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "u64": 1 + }, + { + "u64": 2 + } + ] } } } @@ -2767,7 +2831,7 @@ { "event": { "ext": "v0", - "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -2776,14 +2840,11 @@ "symbol": "fn_return" }, { - "symbol": "balance" + "symbol": "initialize_multi_sig_escrow" } ], "data": { - "i128": { - "hi": 0, - "lo": 5000 - } + "u32": 1 } } } @@ -2802,7 +2863,7 @@ "symbol": "fn_call" }, { - "bytes": "8afcb854dec16be9aabf3f71fadd4b7057fd94efc738143dbb9d09257290b8f6" + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" }, { "symbol": "balance" @@ -2819,7 +2880,7 @@ { "event": { "ext": "v0", - "contract_id": "8afcb854dec16be9aabf3f71fadd4b7057fd94efc738143dbb9d09257290b8f6", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", "type_": "diagnostic", "body": { "v0": { @@ -2834,7 +2895,7 @@ "data": { "i128": { "hi": 0, - "lo": 8000 + "lo": 5000 } } } @@ -2854,33 +2915,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "bytes": "8afcb854dec16be9aabf3f71fadd4b7057fd94efc738143dbb9d09257290b8f6" }, { - "symbol": "submit_hours_proof" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - }, - { - "u64": 0 - }, - { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } } } @@ -2890,33 +2932,23 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", + "contract_id": "8afcb854dec16be9aabf3f71fadd4b7057fd94efc738143dbb9d09257290b8f6", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "hours" + "symbol": "fn_return" }, { - "symbol": "submit" + "symbol": "balance" } ], "data": { - "vec": [ - { - "u32": 1 - }, - { - "u32": 0 - }, - { - "i128": { - "hi": 0, - "lo": 40 - } - } - ] + "i128": { + "hi": 0, + "lo": 8000 + } } } } @@ -2926,19 +2958,24 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "contract_id": null, "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" + "symbol": "fn_call" }, { - "symbol": "submit_hours_proof" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" } ], - "data": "void" + "data": { + "u32": 1 + } } } }, @@ -2947,124 +2984,1201 @@ { "event": { "ext": "v0", - "contract_id": null, + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + "symbol": "fn_return" }, { - "symbol": "submit_hours_proof" + "symbol": "get_escrow" } ], "data": { - "vec": [ + "map": [ { - "u32": 1 + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { - "u64": 1 + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } }, { - "bytes": "545f3be0dcd295023476a3607937196eb3b6bcd47e2bf7af5a5a1e72b485af566db6496d36b3c076cf68c07d96b4f65845979ffcd3281947244628009bb1cd08" - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "symbol": "hours" - }, - { - "symbol": "submit" - } - ], - "data": { - "vec": [ - { - "u32": 1 + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } }, { - "u32": 1 + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } }, { - "i128": { - "hi": 0, - "lo": 40 + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 } - } - ] - } - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_return" - }, - { - "symbol": "submit_hours_proof" - } - ], - "data": "void" - } - } - }, - "failed_call": false - }, - { - "event": { - "ext": "v0", - "contract_id": null, - "type_": "diagnostic", - "body": { - "v0": { - "topics": [ - { - "symbol": "fn_call" - }, - { - "bytes": "0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "symbol": "manager_approve" - } - ], - "data": { + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "u64": 0 + }, + { + "bytes": "16948d36522fa6266fe7885233868b8455914850a1827343ab1328314bbf227ecdf45964d1b136b60fe42b5b2b18816499d4779175362784e9ca24a7edf33f07" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "u64": 1 + }, + { + "bytes": "4b59f43c8cc29da8ccc0bea1e894bf2c75478b75d8382a94642015aece339db82a1edfc8571230b2583462d46e4ef1a41b48932057ac28a9fbb924aa51a11c09" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "manager_approve" + } + ], + "data": { "u32": 1 } } @@ -3304,6 +4418,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3396,6 +4558,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -3477,7 +4687,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 5000 } } }, @@ -3570,7 +4780,7 @@ "val": { "i128": { "hi": 0, - "lo": 40 + "lo": 8000 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_register_oracle_key_requires_admin_authorization.1.json b/contracts/core-flow/test_snapshots/test/tests/test_register_oracle_key_requires_admin_authorization.1.json new file mode 100644 index 0000000..0f61a1a --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_register_oracle_key_requires_admin_authorization.1.json @@ -0,0 +1,377 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_replayed_nonce_is_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_replayed_nonce_is_rejected.1.json index 2e841b9..75d7586 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_replayed_nonce_is_rejected.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_replayed_nonce_is_rejected.1.json @@ -197,6 +197,7 @@ [], [], [], + [], [] ], "ledger": { @@ -1408,6 +1409,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1431,6 +1486,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1467,7 +1730,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1618,7 +1881,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1720,7 +1983,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_revoke_oracle_key_requires_admin_authorization.1.json b/contracts/core-flow/test_snapshots/test/tests/test_revoke_oracle_key_requires_admin_authorization.1.json new file mode 100644 index 0000000..99f1f93 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_revoke_oracle_key_requires_admin_authorization.1.json @@ -0,0 +1,454 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "revoke_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "revoke_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "revoke" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "revoke_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_revoked_key_cannot_back_a_new_escrow.1.json b/contracts/core-flow/test_snapshots/test/tests/test_revoked_key_cannot_back_a_new_escrow.1.json new file mode 100644 index 0000000..3a9f3c6 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_revoked_key_cannot_back_a_new_escrow.1.json @@ -0,0 +1,1412 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "revoke_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "revoke_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "revoke" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "revoke_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bool": false + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "error": { + "contract": 16 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "initialize_multi_sig_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_rotation_invalidates_retired_key_signatures.1.json b/contracts/core-flow/test_snapshots/test/tests/test_rotation_invalidates_retired_key_signatures.1.json index 81a40a1..9395c9f 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_rotation_invalidates_retired_key_signatures.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_rotation_invalidates_retired_key_signatures.1.json @@ -218,6 +218,7 @@ ], [], [], + [], [] ], "ledger": { @@ -1462,6 +1463,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1777,6 +1832,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "fd1724385aa0c75b64fb78cd602fa1d991fdebf76b13c58ed702eac835e9f618" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1813,7 +2076,7 @@ "u64": 0 }, { - "bytes": "3b87b0b7ef1445a9b6bdd887950d6a04f1ba399b8a6d86c0972d603589e25bec9212d24a0ee04382757645e8c2dab59a825f63af11fde1a51e9c17c1493b8a0d" + "bytes": "c9796b8e511b81e823107a66524b375b4e22fd59d1c2d71929e7c922ef1031ef8ad171a071e433b35595f8a7f34be555dd3c4dd96b28d9b05d7269ee8f16b60f" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_rotation_refused_after_settlement.1.json b/contracts/core-flow/test_snapshots/test/tests/test_rotation_refused_after_settlement.1.json index 59a8eb0..24f699d 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_rotation_refused_after_settlement.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_rotation_refused_after_settlement.1.json @@ -195,6 +195,9 @@ ] ], [], + [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1635,6 +1638,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1658,6 +1715,471 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1694,7 +2216,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -2018,6 +2540,54 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_rotation_revokes_previously_verified_proofs.1.json b/contracts/core-flow/test_snapshots/test/tests/test_rotation_revokes_previously_verified_proofs.1.json index ef61470..ee90d97 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_rotation_revokes_previously_verified_proofs.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_rotation_revokes_previously_verified_proofs.1.json @@ -196,6 +196,7 @@ ], [], [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -1567,6 +1568,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1590,6 +1645,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1626,7 +1889,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_rotation_to_unregistered_key_rejected.1.json b/contracts/core-flow/test_snapshots/test/tests/test_rotation_to_unregistered_key_rejected.1.json new file mode 100644 index 0000000..7badf7e --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_rotation_to_unregistered_key_rejected.1.json @@ -0,0 +1,2122 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 0 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "rotate_oracle_key" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "bytes": "0909090909090909090909090909090909090909090909090909090909090909" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "rotate_oracle_key" + } + ], + "data": { + "error": { + "contract": 16 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 16 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "rotate_oracle_key" + }, + { + "vec": [ + { + "u32": 1 + }, + { + "bytes": "0909090909090909090909090909090909090909090909090909090909090909" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_settlement_emits_one_event_per_payment.1.json b/contracts/core-flow/test_snapshots/test/tests/test_settlement_emits_one_event_per_payment.1.json new file mode 100644 index 0000000..8c9891a --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_settlement_emits_one_event_per_payment.1.json @@ -0,0 +1,4024 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "manager_approve", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "finance_approve", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "pay_batch", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 2 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 987000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 13000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + }, + { + "u64": 0 + }, + { + "bytes": "b6ebcd4469c7dae15fe1dc6dcc0bd31e39ca0640e1168820a1bc0df3688c27720d8c78dc3c5c44fac27997fb2dfd7adc92cb1afc9487eff89d103fb1237f4700" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + }, + { + "u64": 1 + }, + { + "bytes": "9893e1c074420b367134a73ff93579053a8f08d96398000b7aa74f6de48dea491df543614af5a469ee16fe41be96b69e39fcda3a004afa8a5f0194cc65e3f904" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "manager_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "manager" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "manager_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "finance_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "finance" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "finance_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "pay_batch" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + }, + { + "i128": { + "hi": 0, + "lo": 20 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 8000 + } + }, + { + "i128": { + "hi": 0, + "lo": 32 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "final" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 13000 + } + }, + { + "u32": 2 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "pay_batch" + } + ], + "data": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 20 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 8000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 32 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_after_approval_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_after_approval_fails.1.json index ca5d5c2..597c473 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_after_approval_fails.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_after_approval_fails.1.json @@ -213,6 +213,7 @@ } ] ], + [], [] ], "ledger": { @@ -1457,6 +1458,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1550,6 +1605,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1586,7 +1849,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1688,7 +1951,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_cancelled_escrow_fails.1.json b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_cancelled_escrow_fails.1.json index f8ed56c..cf34b70 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_cancelled_escrow_fails.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_cancelled_escrow_fails.1.json @@ -213,6 +213,7 @@ } ] ], + [], [] ], "ledger": { @@ -1457,6 +1458,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1598,6 +1653,36 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "cancel" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1642,6 +1727,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 4 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1678,7 +1971,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1780,7 +2073,7 @@ "u64": 0 }, { - "bytes": "18b2d8e96e718093ac9417a09ce651296f21324e8f8cdefef2c1d48994e12f55a088fcf42b739934eb8a1dd789ae594e76d68cd784b60c979e10ec5a62ff6b0c" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } diff --git a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_proof_with_valid_ed25519_signature.1.json b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_proof_with_valid_ed25519_signature.1.json index fd4214a..3586e81 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_proof_with_valid_ed25519_signature.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_submit_hours_proof_with_valid_ed25519_signature.1.json @@ -195,6 +195,7 @@ ] ], [], + [], [] ], "ledger": { @@ -395,7 +396,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, @@ -1406,6 +1407,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1429,6 +1484,214 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1458,14 +1721,14 @@ { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } }, { "u64": 0 }, { - "bytes": "e2a94c3b6dff951ce7886580f6059823e7b14f23b83ee8dc1085cf1f0dce39ac69d532ca0c7d9eb9956e2f444a08b3947f1c9cccb8cba27fe6293485f20c6a04" + "bytes": "14ca76db4040720f8a4c055d61aaf9514e5ddae23e2b2d0a099d0823d60336866e0603c7b7f66e37a882ad58d8b30af02a1835c2b67adb14ba799c6f061b0500" } ] } @@ -1500,7 +1763,7 @@ { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } ] @@ -1664,7 +1927,7 @@ "val": { "i128": { "hi": 0, - "lo": 80 + "lo": 40 } } }, diff --git a/contracts/core-flow/test_snapshots/test/tests/test_test_builds_are_unpinned.1.json b/contracts/core-flow/test_snapshots/test/tests/test_test_builds_are_unpinned.1.json new file mode 100644 index 0000000..862626b --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_test_builds_are_unpinned.1.json @@ -0,0 +1,121 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "expected_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "expected_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_unpause_restores_operations.1.json b/contracts/core-flow/test_snapshots/test/tests/test_unpause_restores_operations.1.json index 5074aee..9c5872b 100644 --- a/contracts/core-flow/test_snapshots/test/tests/test_unpause_restores_operations.1.json +++ b/contracts/core-flow/test_snapshots/test/tests/test_unpause_restores_operations.1.json @@ -67,6 +67,25 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -611,6 +630,51 @@ 1555200 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], [ { "contract_data": { @@ -734,6 +798,39 @@ 15 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], [ { "contract_data": { @@ -773,7 +870,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 8370022561469687789 } }, "durability": "temporary" @@ -788,7 +885,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 8370022561469687789 } }, "durability": "temporary", @@ -1350,6 +1447,29 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1371,6 +1491,76 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", @@ -1772,6 +1962,60 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_emits_an_event_carrying_the_wasm_hash.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_emits_an_event_carrying_the_wasm_hash.1.json new file mode 100644 index 0000000..b88aa0a --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_emits_an_event_carrying_the_wasm_hash.1.json @@ -0,0 +1,527 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "upgrade", + "args": [ + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": true + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da", + "code": "0061736d01000000019b022960037f7f7f017f60027f7f017f60047e7e7e7e017e60027e7e017e60037e7e7e017e6000017e60017e017e60037f7f7f0060027f7e0060047f7f7f7f0060057f7f7e7f7f0060027f7f017e60047f7f7f7e0060027e7f017e60017f017e60017e017f6000017f60017f0060017f017f60027f7e017f60057f7e7e7e7e0060057e7e7e7e7e017e60067f7f7e7e7e7e017f60027f7f0060067f7f7f7e7e7e0060077f7f7f7f7e7e7e017e60057f7f7f7f7f0060000060057f7e7e7f7f017e60057f7e7e7f7f0060057f7f7f7f7f017e60067f7e7f7f7f7f017e60037f7f7f017e60037f7e7e017f60037f7e7e017e60027f7e017e60047f7e7e7e017e60057f7e7e7e7e017e60037f7e7e0060047f7e7e7f0060067f7e7e7e7e7f0002d901240162013200020162013100020162016a0003016d01390004016d016100020176016700030178013000030178013100030178013300050178013600050178013700050178013800050169015f00060169013000060169013600030169013700060169013800060176015f0005017601300004017601310003017601330006017601360003016c015f0004016c01300003016c01310003016c01320003016c01360006016c01370002016c013800030164015f00040162015f00060162013400050162013800060163015f000601630130000401610130000603f301f1010707070708090a010b07070701070c070c070c070c0b0c0b010701070707070b07070707070707070d0e0b0b0b0b0b070b0b0b0b0b0b0b070b0b0b0107060f05060f0510051106120510060f060f060f060f0313021415160612061206170612061706120218060e1110120b1907070707071a070707070707070707070701111b0b0b0b0b0b0e0c1c1d1e1f20071120010107070b0912120e11122122072223230e2422232224222325230e230b09070e1c1d201e1f2022220e0e0e0e23232223230e242223222422222223252224230e23232423050717060f0e0f0808260007171a11010101001111271427281400140405017001040405030100110619037f01418080c0000b7f0041d08ac0000b7f0041d08ac0000b07ba031b066d656d6f727902000a696e69745f61646d696e00610e65787065637465645f61646d696e00630d70726f706f73655f61646d696e00640c6163636570745f61646d696e0066096765745f61646d696e00680a7365745f706175736564006a0969735f706175736564006c0775706772616465006e1372656769737465725f6f7261636c655f6b65790070117265766f6b655f6f7261636c655f6b657900721869735f6f7261636c655f6b65795f72656769737465726564007411726f746174655f6f7261636c655f6b657900761b696e697469616c697a655f6d756c74695f7369675f657363726f770078127375626d69745f686f7572735f70726f6f66007a0f6d616e616765725f617070726f7665007c0f66696e616e63655f617070726f7665007e1066696e616c697a655f7061796d656e740080010d63616e63656c5f657363726f770082010a6765745f657363726f7700840111657874656e645f657363726f775f74746c0086010e70726f6f665f707265696d616765008801096765745f6e6f6e6365008a01097061795f6261746368008001015f00a4010a5f5f646174615f656e6403010b5f5f686561705f626173650302090c010041010b03a201890288020adff901f1014602017f017e23808080800041106b220324808080800020032001200210a580808000200329030821042000200329030037030020002004370308200341106a2480808080000b6102017f017e23808080800041106b22032480808080002003200229030022041081828080000240024020032802000d00200329030821040c010b2001200410c38180800021040b2000420037030020002004370308200341106a2480808080000b6401027e02400240024020022903002203a741ff0171220241c000460d0020024106470d0142002104200310fc8180800021030c020b420021042001200310c48180800021030c010b4201210410f98180800021030b20002004370300200020033703080bf80304027f017e017f047e23808080800041d0006b22032480808080004100210402400340200441c000460d01200320046a4202370300200441086a21040c000b0b0240024002400240024002400240024002402002290300220542ff018342cc00520d0020012005419482c0800041082003410810af818080001a410120032d0000220441004741017420044101461b22044102460d01410120032d0008220241004741017420024101461b22024102460d02200341c0006a200341106a2001109c8180800020032802400d0320032903482105200341c0006a200341186a2001109c8180800020032802400d04410120032d0020220641004741017420064101461b22064102460d0520032903482107200341c0006a200341286a2001109a8180800020032802400d062003290330220842ff01834204520d0702402003290338220942ff018342cb00520d002003290348210a200020043a0026200020023a0025200020063a002420002008422088a7360220200020093703182000200a37031020002005370308200020073703000c090b200041023a00260c080b200041023a00260c070b200041023a00260c060b200041023a00260c050b200041023a00260c040b200041023a00260c030b200041023a00260c020b200041023a00260c010b200041023a00260b200341d0006a2480808080000b3b01017f23808080800041106b2202248080808000200220013703082000200241086a10d48180800010cc818080001a200241106a2480808080000b12002000200142012002200310aa808080000b270020002000200110ac808080002002200310fe81808000200410fe8180800010cd818080001a0b4d02017f017e41022102024020002000200110ac808080002203420110bf81808000450d00410121020240024020002003420110c081808000a741ff01710e020102000b000b410021020b20020bcb0502017f017e23808080800041306b220224808080800002400240024002400240024002400240024020012802000e0700010203040506000b200241206a200041e082c08000109f8180800020022802200d07200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c060b200241206a200041f082c08000109f8180800020022802200d0620022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d062002200229032837031020022003370308200241206a200241086a2000109d818080000c050b200241206a2000418083c08000109f8180800020022802200d0520022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d052002200229032837031020022003370308200241206a200241086a2000109d818080000c040b200241206a2000419083c08000109f8180800020022802200d04200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c030b200241206a200041a483c08000109f8180800020022802200d03200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c020b200241206a200041b483c08000109f8180800020022802200d02200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c010b200241206a200041c883c08000109f8180800020022802200d0120022002290328370318200241186a10d4818080002103200241206a200141086a200010a18180800020022802200d012002200229032837031020022003370308200241206a200241086a2000109d818080000b20022903282103200229032050450d00200241306a24808080800020030f0b000b5e01017e02400240024020012001200210ac808080002203420110bf818080000d00410021010c010b20012003420110c081808000220342ff01834204520d012003422088a72102410121010b20002002360204200020013602000f0b000b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200042003703000c010b200320012004420110c081808000370308200341106a2001200341086a10a68080800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b880102017f017e23808080800041306b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200041023a00260c010b200320012004420110c081808000370300200341086a2001200310a78080800020032d002e4102460d012000200341086a41281093828080001a0b200341306a2480808080000f0b000b160020002000200110ac80808000420110bf818080000b1000200020012002420110b2808080000b210020002000200110ac808080002002200010a781808000200310ca818080001a0b1000200020012002420110b4808080000b210020002000200110ac808080002000200210b980808000200310ca818080001a0b1000200020012002420110b6808080000b210020002000200110ac808080002002200010a681808000200310ca818080001a0b1000200020012002420110b8808080000b210020002000200110ac808080002000200210bb80808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110db80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b210020002000200110ac808080002002200010a881808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110a480808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4d02017f017e41022102024020002000200110ac808080002203420210bf81808000450d00410121020240024020002003420210c081808000a741ff01710e020102000b000b410021020b20020b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420210bf818080000d00200042003703000c010b200320012004420210c081808000370308200341106a2001200341086a10b18180800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b160020002000200110ac80808000420210bf818080000b1000200020012002420210b6808080000b1000200020012002420210ba808080000b850102017f027e23808080800041106b220324808080800020032001200210b6818080000240024020032802000d00200320032903082204370300420121050240200341086a200410d08180800010ff8180800041c000470d0020002003290300370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b950203017f017e027f23808080800041c0006b22032480808080002001200210c380808000210420032001200241086a10c38080800037030820032004370300410021020240034020024110460d01200341106a20026a4202370300200241086a21020c000b0b200341246a200341106a200341106a41106a2003200341106a109681808000410020032802382202200328023422056b2206200620024b1b21022003280224200541037422066a2105200328022c20066a2106024003402002450d0120052006200110a981808000370300200541086a2105200641086a21062002417f6a21020c000b0b2001200341106a410210b08180800021042000420037030020002004370308200341c0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110d381808000024020022802004101470d00000b20022903082103200241106a24808080800020030b7902017f027e23808080800041206b2203248080808000200341106a2002200110a0818080000240024020032802100d00200320032903183703082001200341086a410110b0818080002104420021050c010b10f9818080002104420121050b2000200537030020002004370308200341206a2480808080000ba30102017f017e23808080800041206b2203248080808000200341106a200120021091818080000240024020032802100d0020032903182104200341106a2001200241046a10918180800020032802100d00200320032903183703082003200437030020012003410210b081808000210420004200370300200020043703080c010b10f981808000210420004201370300200020043703080b200341206a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200210918180800002400240024020032802200d0020032903282104200341206a2001200241046a10918180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200241086a10918180800002400240024020032802200d0020032903282104200341206a20022001109e8180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd00102017f027e23808080800041306b2203248080808000200341206a2001200241106a10918180800002400240024020032802200d0020032903282104200341206a200120021094818080002003290328210520032802200d01200341206a2001200241146a10918180800020032802200d002003200329032837031820032005370310200320043703082001200341086a410310b081808000210520004200370300200020053703080c020b10f98180800021050b20004201370300200020053703080b200341306a2480808080000bd20202017f067e23808080800041c0006b2203248080808000200341306a2001200241206a10918180800002400240024020032802300d0020032903382104200341306a2001200241246a10918180800020032802300d0020032903382105200341306a200241106a2001109e8180800020032802300d0020032903382106200341306a200241186a2001109e8180800020032802300d0020032903382107200341306a200120021094818080002003290338210820032802300d01200341306a2001200241306a1094818080002003290338210902402003280230450d00200921080c020b20032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410610b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341c0006a2480808080000bbd0302017f087e23808080800041d0006b2203248080808000200341c0006a2001200241386a10918180800002400240024020032802400d0020032903482104200341c0006a20012002413c6a10918180800020032802400d0020032903482105200341c0006a200241206a2001109e8180800020032802400d0020032903482106200341c0006a200241286a2001109e8180800020032802400d0020032903482107200341c0006a200120021094818080002003290348210820032802400d01200341c0006a2001200241106a1094818080002003290348210902402003280240450d00200921080c020b200341c0006a2001200241306a10a4808080002003290348210a02402003280240450d00200a21080c020b200341c0006a2001200241c0006a10a4808080002003290348210b02402003280240450d00200b21080c020b2003200b3703382003200a37033020032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410810b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341d0006a2480808080000b2a00024020022802000d0020004200370300200042023703080f0b2000200241086a2001109e818080000b4001017f23808080800041106b2202248080808000200220003703082001200241086a200110a88180800010ce818080002100200241106a24808080800020000b15002000280200417f6aad4220864283808080107c0b4502017f017e23808080800041106b220224808080800020022000200110c780808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1d00024020012802000d0020012903080f0b200141046a10cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c680808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6902027f017e23808080800041106b2202248080808000200141046a21030240024020012802000d00200220002003109181808000024020022802000d00200229030821040c020b10f9818080001a000b200310cd8080800021040b200241106a24808080800020040b4502017f017e23808080800041106b220224808080800020022000200110d380808000024020022802004101470d00000b20022903082103200241106a24808080800020030bd00302017f097e23808080800041e0006b2203248080808000200341d0006a200120021094818080000240024020032802500d0020032903582104200341d0006a2001200241c8006a10a48080800020032802500d0020032903582105200341d0006a2001200241106a10948180800020032802500d0020032903582106200341d0006a2001200241d0006a10918180800020032802500d0020032903582107200341d0006a2001200241d8006a10938180800020032802500d0020032903582108200341d0006a2001200241206a10948180800020032802500d0020032903582109200341d0006a2001200241c0006a10a48080800020032802500d002003290358210a2002350254210b200341d0006a200241386a2001109e8180800020032802500d002003290358210c200341d0006a200241306a2001109e8180800020032802500d00200320032903583703482003200c3703402003200b4220864204843703382003200a370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200141dc80c08000410a2003410a10ae81808000210420004200370300200020043703080c010b200042013703000b200341e0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110c280808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110b781808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110ca80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1700024020012802000d0042020f0b200110cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c980808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c580808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c880808000024020022802004101470d00000b20022903082103200241106a24808080800020030bf20202017f067e23808080800041d0006b2203248080808000200341c0006a2001200241266a1093818080000240024020032802400d0020032903482104200341c0006a2001200241256a10938180800020032802400d0020032903482105200341c0006a200241086a2001109e8180800020032802400d0020032903482106200341c0006a20022001109e8180800020032802400d0020032903482107200341c0006a2001200241246a10938180800020032802400d0020032903482108200341c0006a200241106a200110a18180800020032802400d0020032903482109200341c0006a2001200241206a10918180800020032802400d0020032003290348370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200320022903183703382001419482c0800041082003410810ae81808000210420004200370300200020043703080c010b200042013703000b200341d0006a2480808080000b6802017f017e23808080800041106b22022480808080000240024020012802000d0020022000200141086a10d381808000024020022802000d00200229030821030c020b10f9818080001a000b200141046a10cd8080800021030b200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110cb80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6502017f017e23808080800041106b22022480808080000240024020012d00264102460d0020022000200110db80808000024020022802000d00200229030821030c020b10f9818080001a000b200110cd8080800021030b200241106a24808080800020030b2501017e20002903002202422088a72200410520004105491b4105200242ff01834204511b0b9e0502027f0b7e23808080800041f0006b22032480808080004100210402400340200441d000460d01200320046a4202370300200441086a21040c000b0b024002400240024002400240024002400240024002402002290300220542ff018342cc00520d002001200541dc80c08000410a2003410a10af818080001a200341d0006a2001200310928180800020032802500d01200341e8006a290300210520032903602106200341d0006a2001200341086a10a68080800020032802500d0220032903582107200341d0006a2001200341106a10928180800020032802500d032003290318220842ff01834204520d04410120032d0020220441004741017420044101461b22044102460d05200341e8006a29030021092003290360210a200341d0006a2001200341286a10928180800020032802500d06200341e8006a290300210b2003290360210c200341d0006a2001200341306a10a68080800020032802500d072003290358210d200341386a200410df8080800022024105460d08200341d0006a200341c0006a2001109c8180800020032802500d092003290358210e200341d0006a200341c8006a2001109c81808000024020032802500d002003290358210f2000200c3703202000200a37031020002006370300200020043a00582000200236025420002008422088a7360250200020073703482000200d3703402000200e3703382000200f3703302000200b37032820002009370318200020053703080c0b0b200041053602540c0a0b200041053602540c090b200041053602540c080b200041053602540c070b200041053602540c060b200041053602540c050b200041053602540c040b200041053602540c030b200041053602540c020b200041053602540c010b200041053602540b200341f0006a2480808080000b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e2808080003602082001200141086a10d7808080002100200141206a24808080800020000be90101027f23808080800041306b2201248080808000200120003703082001412f6a10bd81808000410c210202402001412f6a41d083c0800010be808080000d00200141086a10b2818080002001412f6a10bd818080002001412f6a41d083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ef2eed90b3703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020b200141306a24808080800020020b3d02017f017e23808080800041206b2200248080808000200042003703082000411f6a200041086a10dd808080002101200041206a24808080800020010b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e5808080003602082001200141086a10d7808080002100200141206a24808080800020000bdd0101027f23808080800041306b220124808080800020012000370308200141106a108c818080000240024020012802100d002001412f6a10bd818080002001412f6a41e083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ed4b8bacdbed7013703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020c010b200128021421020b200141306a24808080800020020b3e02017f017e23808080800041106b2200248080808000200010e78080800036020c20002000410c6a10d7808080002101200041106a24808080800020010bad0203017f017e017f23808080800041306b22002480808080002000412f6a10bd81808000200041106a2000412f6a41e083c0800010bd808080000240024020002802104101470d00200020002903182201370308200041086a10b2818080002000412f6a10bd818080002000412f6a41d083c08000200041086a10c0808080002000412f6a10bd818080002000412f6a2000412f6a41e083c0800010ac80808000420210cb818080001a2000412f6a10bd818080002000412f6a418087014180d21f10c181808000200020013703202000428ef2b5958ab5023703182000428ee6aeb9ea043703102000412f6a2000412f6a200041106a10d480808000200041206a2000412f6a10a88180800010c2818080001a410021020c010b411521020b200041306a24808080800020020b4102017f017e23808080800041206b2200248080808000200041086a10e9808080002000411f6a200041086a10dd808080002101200041206a24808080800020010b3e01017f23808080800041106b22012480808080002001410f6a10bd8180800020002001410f6a41d083c0800010bd80808000200141106a2480808080000b5c01027f23808080800041106b2201248080808000410121020240024002402000a741ff01710e020102000b000b410021020b2001200210eb8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bdc0101017f23808080800041206b2201248080808000200120003a0007200141086a108c818080000240024020012802080d002001411f6a10bd818080002001411f6a41f083c08000200141076a10bf808080002001411f6a10bd818080002001411f6a418087014180d21f10c181808000200120012d00073a001e2001428ed2aadceeac033703102001428ee6aeb9ea043703082001411f6a2001411f6a200141086a10d4808080002001411e6a2001411f6a10a68180800010c2818080001a410021000c010b200128020c21000b200141206a24808080800020000b4102017f017e23808080800041106b2200248080808000200010ed808080003a000e2000410e6a2000410f6a10a6818080002101200041106a24808080800020010b4401027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000200141fd01710b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010ef808080003602082001200141086a10d7808080002100200141206a24808080800020000bcd0101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd81808000411621022001411f6a41f083c0800010bc8080800041fd0171450d01200120003703102001428ed4a9f3cdadeb013703082001428ee6aeb9ea043703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a2001411f6a10bd818080002001411f6a200010a880808000410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f1808080003602082001200141086a10d7808080002100200141206a24808080800020000be60101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418084c0800010b5808080002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418087014180f6de0010a980808000200120003703102001428ed8ea1b3703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f3808080003602082001200141086a10d7808080002100200141206a24808080800020000bc20101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001411f6a200110ac80808000420110cb818080001a200120003703102001428ed4b0faaebd033703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6b01017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f5808080003a0008200141086a2001411f6a10a6818080002100200141206a24808080800020000b5101027f23808080800041206b22012480808080002001411f6a10bd8180800020014106360208200120003703102001411f6a200141086a10ab808080002102200141206a248080808000200241fd01710b7a01017f23808080800041206b2202248080808000200220013703000240200042ff01834204520d00200241086a2002411f6a2002109b8180800020022802084101460d0020022000422088a7200229031010f7808080003602082002200241086a10d7808080002100200241206a24808080800020000f0b000b990801087f2380808080004180036b2202248080808000200220013703000240108d8180800022030d00200241ff026a10bd81808000200241013602302002200036023420024190026a200241ff026a200241306a10af80808000024020022d00b60222034102460d002002280290022104200241086a41047220024190026a41047241221093828080001a200220033a002e20022004360208200220022d00b7023a002f200241086a10b2818080002002108e8180800022030d014108210320022d002e0d0141002103200241286a2204200229032010c88180800010ff818080002105024002400340024020052003470d00200220013703180240200228022841016a2203450d00200220033602282002200241ff026a10c5818080002201370340200241c8006a21062004200229032010c88180800010ff818080002107200241e9026a220841036a2109410021030340024020072003470d0020022001370320200241ff026a10bd8180800020024190026a41086a2203200241306a41086a22042903003703002002200229033037039002200241ff026a20024190026a200241086a10b380808000200241ff026a10bd81808000200320042903003703002002200229033037039002200241ff026a20024190026a418087014180f6de0010a980808000200220022802283602b401200220003602b0012002428ed4b9b3cebe03370398022002428ed4b1d4f9a60337039002200241ff026a200241ff026a20024190026a10d480808000200241ff026a200241b0016a10d98080800010c2818080001a410021030c080b4105210502402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e08080800020022802e40222054105460d06200241b0016a20024190026a41d4001093828080001a200220092800003600ab01200220082800003602a8010b200241d0006a200241b0016a41d4001093828080001a200220022800ab0136004b200220022802a801360248024020054105460d0020024190026a200241d0006a41d4001093828080001a2009200228004b36000020082002280248360000200241003a00e802200220053602e402200220062002290340200620024190026a10d28080800010c9818080002201370340200341016a21030c010b0b419884c08000108782808000000b418884c08000108c82808000000b02402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e080808000200341016a210320022802e402417d6a0e03020103010b0b41a884c08000108782808000000b410621030c020b000b410421030b20024180036a24808080800020030be70101017f23808080800041c0006b2204248080808000200420013703182004200037031020042002370320200441286a2004413f6a200441106a10b181808000024020042802284101460d0020042903302101200441286a2004413f6a200441186a10b18180800020042802284101460d0020042903302100200441286a2004413f6a200441206a109b8180800020042802284101460d00200342ff018342cb00520d00200441086a200120002004290330200310f980808000200420042903083702282004413f6a200441286a10d1808080002103200441c0006a24808080800020030f0b000bf01104077f027e037f017e23808080800041e0026b220524808080800020052002370320200520013703182005200337032820052004370330410121060240108d8180800022070d00200541186a10b2818080000240200541186a200541206a10b481808000450d00410f21070c010b0240200541386a2208200529033010c88180800010ff81808000450d0002402008200529033010c88180800010ff8180800041e4004d0d00411321070c020b200541286a108e8180800022070d01410021072008200529033010c88180800010ff81808000210920054188026a210a200541106a210b4200210c4200210d0340024002400240024020092007470d00200541df026a10bd818080002005200541df026a41b884c0800010ad808080004100210a02402005280204410020052802004101711b41016a220e450d002005200e36023c2005200541df026a10aa8180800037034020054188016a21062008200529033010c88180800010ff81808000210f02400340200f200a200f200a4b1b211003400240200a2010470d0041002107200541f3006a41003600002005410036027020052005290330370368200520033703602005200529032037035820052005290318370350200541df026a10bd81808000200541013602b8012005200e3602bc01200541df026a200541b8016a200541d0006a10b380808000200541df026a10bd81808000200541e0016a41086a2206200541b8016a41086a290300370300200520052903b8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541023602c8012005200e3602cc01200541df026a200541c8016a41d884c0800010b780808000200541df026a10bd818080002006200541c8016a41086a290300370300200520052903c8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541df026a41b884c080002005413c6a10b180808000200541df026a10bd81808000200541df026a41b884c08000418087014180f6de0010a9808080002005200d3703f8012005200c3703f0012005200528023c3602e801200520052903183703e0012005428ed2eadca9bda3013703c8022005428ef8f49b8ad7023703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10ce8080800010c2818080001a2008200529033010c88180800010ff81808000210620054188026a21090340024020062007470d00200528023c2107410021060c0d0b02402008200529033010c88180800010ff8180800020074d0d00200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b4024105460d0820052903a002210220052903a80221012005290390022104200529039802210320052903e001210d20052903e801210c2005290380022111200520092903003703f801200520113703f0012005200c3703e8012005200d3703e0012005200528023c3602980220052003370388022005200437038002200520013703a00220052002370390022005200736029c022005428ed2a9133703c8022005428ef2b3d5ecb7d6013703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10d68080800010c2818080001a200741016a21070c010b0b41e084c08000108782808000000b2008200529033010c88180800010ff81808000200a4d0d02200520082005290330200a10fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105460d05200a41016a210b200520052903980237034841002107024003400240200a2007470d00200542003703c802200542003703c002410021072008200529033010c88180800010ff81808000210a024003400240200a2007470d002005200541df026a200541c8006a10d1818080003703e001200541e0016a200541186a200541c0006a200541c0026a10d281808000200b210a0c070b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703d801200541e0016a2008200541d8016a10e08080800020052802b40222094105460d0a200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801024002402006200541c8006a10b481808000450d0020052903c80222022005290358220185427f852002200220017c20052903c002220120052903507c2204200154ad7c220185834200530d01200520043703c002200520013703c8020b200741016a21070c010b0b419085c08000108c82808000000b418085c08000108782808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b40222094105460d07200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801200741016a21072006200541c8006a10b481808000450d000b200b210a0c010b0b0b41a085c08000108782808000000b41f084c08000108782808000000b41c884c08000108c82808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105470d020b000b41b085c08000108782808000000b4101210620052903e00122045020052903e80122024200532002501b0d01200529038002221150200a29030022014200532001501b0d01024020052903a80220052903a002560d00411221070c030b200541086a200420022011200110948280800002402005290308200b290300844200510d00411121070c030b0240200d200285427f85200d200d20027c200c20047c2202200c54ad7c220185834200530d00200741016a21072002210c2001210d0c010b0b41c085c08000108c82808000000b410721070b2000200736020420002006360200200541e0026a2480808080000bfd0101017f23808080800041d0006b22052480808080002005200337031020052002370308200520043703180240200042ff01834204520d00200142ff01834204520d00200541206a200541cf006a200541086a10928180800020052802204101460d00200541386a290300210320052903302102200541206a200541cf006a200541106a10a68080800020052802204101460d0020052903282104200541206a200541cf006a200541186a10c18080800020052802204101460d0020052000422088a72001422088a7200220032004200529032810fb808080003602202005200541206a10d7808080002100200541d0006a24808080800020000f0b000bef0801047f23808080800041d0026b2206248080808000200620053703200240108d8180800022070d00200641cf026a10bd818080002006410136025020062000360254200641d0016a200641cf026a200641d0006a10af808080000240024020062d00f60122074102460d0020062802d0012108200641286a410472200641d0016a41047241221093828080001a20062008360228200620062d00f7013a004f200620073a004e02402007410171450d00410821070c030b4101210720062d004c0d0220062d004d0d02200641c8006a2208200629034010c88180800010ff8180800020014b0d010b410421070c010b024002402008200629034010c88180800010ff8180800020014d0d00200620082006290340200110fe8180800010c7818080003703b002200641d0016a2008200641b0026a10e08080800020062802a40222074105470d01000b41d085c08000108782808000000b200641e0006a200641d0016a41d4001093828080001a200620073602b401200620062903a8023703b8012006410036021c200641086a2002200320062903800120064188016a2903002006411c6a1091828080000240200628021c450d00410721070c010b02402006290308200629036085200641106a290300200629036885844200510d00411121070c010b2006200641cf026a20002001200641e0006a2002200320041090818080003703c801200641cf026a10bd81808000200641cf026a200641386a200641c8016a200641206a10b981808000200641cf026a10bd81808000200641023602b002200620003602b402200641d0016a200641cf026a200641b0026a10ae808080004109210720062903d801420020062802d0011b2004520d0002402004427f520d00410e21070c010b2006200442017c3703c002200641cf026a10bd81808000200641d0016a41086a2207200641b0026a41086a2209290300370300200620062903b0023703d001200641cf026a200641d0016a200641c0026a10b780808000200641cf026a10bd8180800020072009290300370300200620062903b0023703d001200641cf026a200641d0016a418087014180f6de0010a9808080002006200337037820062002370370200641013a00b801200641d0016a200641e0006a41e0001093828080001a200620082006290340200110fe818080002008200641d0016a10d28080800010c681808000370340200641cf026a10bd818080002007200641d0006a41086a2208290300370300200620062903503703d001200641cf026a200641d0016a200641286a10b380808000200641cf026a10bd8180800020072008290300370300200620062903503703d001200641cf026a200641d0016a418087014180f6de0010a980808000200620033703e801200620023703e001200620013602d401200620003602d0012006428ef2aef9a9c7033703b8022006428ef0b79ddd053703b002200641cf026a200641cf026a200641b0026a10d480808000200641cf026a200641d0016a10d08080800010c2818080001a410021070b200641d0026a24808080800020070b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710fd8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbb0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a10b281808000024020012d002c450d00410121020c020b200141013a002c200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428eeeaad6b9b6ca013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710ff8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbe0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a41086a10b281808000024020012d002d450d00410121020c020b200141013a002d200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428ed4e8d9b9f6ae013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b4b01017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a71081818080002001200110cf808080002100200141106a24808080800020000bd20a04047f017e047f057e23808080800041b0026b22022480808080000240024002400240108d8180800022030d00200241af026a10bd818080002002410136023020022001360234200241b0016a200241af026a200241306a10af8080800020022d00d60122034102460d0120022802b0012104200241086a410472200241b0016a41047241221093828080001a20022004360208200220022d00d7013a002f200220033a002e02402003410171450d00410821030c030b200241086a10b2818080000240200241086a200241106a10b481808000450d00410f21030c030b4105210320022d002c4101470d0220022d002d4101470d0241002104200241286a2203200229032010c88180800010ff818080002105024002400340024020052004470d002002200241af026a10aa818080003703402002200241af026a10c5818080002206370348200241d0006a210720024180016a210820024188016a2109410021042003200229032010c88180800010ff818080002105200241e8006a210a4200210b4200210c02400340024020052004470d0020022006370320200241af026a10bd81808000200241b0016a41086a2203200241306a41086a2204290300370300200220022903303703b001200241af026a200241b0016a200241086a10b380808000200241af026a10bd8180800020032004290300370300200220022903303703b001200241af026a200241b0016a418087014180f6de0010a9808080002007200229034810c88180800010ff8180800021032002200c3703b8012002200b3703b001200220033602c401200220013602c0012002428ee2e6d9bb053703582002428ef2b3d5ecb7d601370350200241af026a200241af026a200241d0006a10d480808000200241af026a200241b0016a10da8080800010c2818080001a20002002290348370308200041003602000c0a0b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c78180800037039802200241b0016a200320024198026a10e0808080002002280284024105460d05200241d0006a200241b0016a41d4001093828080001a200241033602a40120022002290388023703a8012002200241af026a200910d1818080003703b001200241b0016a200241c0006a2008200241d0006a10d2818080000240200c2002290358220685427f85200c200c20067c200b2002290350220d7c220e200b54ad7c220f85834200530d00200220022903603703e0012002200d3703b001200220013602d00120022002290388013703c80120022002290380013703c001200220063703b8012002200a2903003703e801200220043602d4012002428ed2aeb30d3703a0022002428ef2b3d5ecb7d60137039802200241af026a200241af026a20024198026a10d480808000200241af026a200241b0016a10d88080800010c2818080001a200241b0016a200241d0006a41e0001093828080001a2002200720022903482007200241b0016a10d28080800010c9818080002206370348200441016a2104200e210b200f210c0c010b0b41f085c08000108c828080000c040b41e085c08000108782808000000b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c781808000370350200241b0016a2003200241d0006a10e08080800020022802840222074105460d02024020074103460d00200441016a210420022d0088024101710d010b0b410d410620074103471b21030c040b418086c08000108782808000000b000b20004101360200200020033602040c020b410421030b20004101360200200020033602040b200241b0026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710838180800036020c20012001410c6a10d7808080002100200141106a24808080800020000b8b0d04087f017e017f027e23808080800041c0026b2201248080808000200141bf026a10bd818080002001410136023020012000360234200141d0016a200141bf026a200141306a10af808080000240024020012d00f60122024102460d0020012802d0012103200141086a410472200141d0016a41047241221093828080001a20012003360208200120012d00f7013a002f200120023a002e4108210320024101710d01200141086a10b28180800041002102200141286a2203200129032010c88180800010ff818080002104024002400340024020042002470d002001200141bf026a10aa8180800037034020014198016a2104410021052003200129032010c88180800010ff8180800021060240034020062005200620054b1b2107034020052108024020082007470d00200141013a002e2001200141bf026a10c58180800022093703c801200141d0016a2104410021022003200129032010c88180800010ff81808000210a03400240200a2002470d0020012009370320200141bf026a10bd81808000200141d0016a41086a200141306a41086a290300370300200120012903303703d001200141bf026a200141d0016a200141086a10b380808000200120003602602001428ee2aaf4ecc4023703d8012001428ef8f49b8ad7023703d001200141bf026a200141bf026a200141d0016a10d480808000200141e0006a200141bf026a10a78180800010c2818080001a410021030c0b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d08200141e0006a200141d0016a41d4001093828080001a20012903a8022109200120003602502001428ee2aaf4ecc4023703d8012001428ef2b3d5ecb7d6013703d00120012002360254200141bf026a200141bf026a200141d0016a10d480808000200141bf026a200141d0006a10d98080800010c2818080001a200141d0016a200141e0006a41d4001093828080001a200120093703a802200141043602a4022001200420012903c8012004200141d0016a10d28080800010c98180800022093703c801200241016a21020c010b0b419086c08000108782808000000b2003200129032010c88180800010ff8180800020084d0d02200120032001290320200810fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d05200841016a21052001200129038802370348410021020340024020082002470d002001420037035820014200370350410021022003200129032010c88180800010ff8180800021080340024020082002470d002001290350420052200129035822094200552009501b450d052001200141bf026a200141c8006a10d1818080003703d001200141d0016a200141c0006a200141086a200141d0006a10d2818080000c050b024002402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c7818080003703c801200141d0016a2003200141c8016a10e08080800020012802a402220a4105470d010c0a0b41b086c08000108782808000000b200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b80102402004200141c8006a10b481808000450d000240200129035822092001290368220b85427f8520092009200b7c2001290350220b20012903607c220c200b54ad7c220b85834200530d002001200c3703502001200b3703580c010b41c086c08000108c82808000000b200241016a21020c000b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370350200141d0016a2003200141d0006a10e08080800020012802a402220a4105460d07200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b801200241016a21022004200141c8006a10b481808000450d010c020b0b0b0b41d086c08000108782808000000b41a086c08000108782808000000b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e080808000200241016a210220012802a402417d6a0e03030102010b0b41e086c08000108782808000000b000b410621030c010b410421030b200141c0026a24808080800020030b4e01017f23808080800041306b22012480808080000240200042ff01834204510d00000b20012000422088a71085818080002001412f6a200110de808080002100200141306a24808080800020000b7a01017f23808080800041c0006b22022480808080002002413f6a10bd81808000200241013602282002200136022c20022002413f6a200241286a10af808080000240024020022d00264102470d00200041023a0026200041043602000c010b2000200241281093828080001a0b200241c0006a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710878180800036020c20012001410c6a10d7808080002100200141106a24808080800020000bd70201027f23808080800041306b22012480808080002001412f6a10bd8180800020014101360200200120003602044104210202402001412f6a200110b080808000450d002001412f6a10be8180800021022001412f6a10bd81808000200141106a41086a200141086a290300370300200120012903003703102001412f6a200141106a2002200210a9808080002001412f6a10bd8180800020014102360210200120003602142001412f6a200141106a2002200210a9808080002001412f6a10bd818080002001412f6a41b884c080002002200210a9808080002001412f6a10bd818080002001412f6a2002200210c18180800020012002360228200120003602242001428ee2f91c3703182001428ef8f49b8ad7023703102001412f6a2001412f6a200141106a10d4808080002001412f6a200141246a10d98080800010c2818080001a410021020b200141306a24808080800020020bcb0101017f23808080800041c0006b220424808080800020042003370308200420023703000240200042ff01834204520d00200142ff01834204520d00200441106a2004413f6a200410928180800020042802104101460d00200441286a290300210320042903202102200441106a2004413f6a200441086a10a68080800020042802104101460d00200441106a2000422088a72001422088a72002200320042903181089818080002004413f6a200441106a10dc808080002100200441c0006a24808080800020000f0b000b970301037f2380808080004180026b2206248080808000200641ff016a10bd81808000200641013602302006200136023420064190016a200641ff016a200641306a10af808080000240024020062d00b60122074102460d002006280290012108200641086a41047220064190016a41047241221093828080001a200620073a002e20062008360208200620062d00b7013a002f0240200641286a2207200629032010c88180800010ff8180800020024d0d00024002402007200629032010c88180800010ff8180800020024d0d00200620072006290320200210fe8180800010c78180800037033020064190016a2007200641306a10e08080800020062802e40122074105470d01000b41f086c08000108782808000000b200641306a20064190016a41d4001093828080001a2006200736028401200620062903e80137038801200641ff016a20012002200641306a200320042005109081808000210420004100360200200020043703080c020b20004281808080c0003703000c010b20004281808080c0003703000b20064180026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a7108b818080003703002001410f6a200110bb808080002100200141106a24808080800020000b6502017f017e23808080800041306b22012480808080002001412f6a10bd81808000200141023602082001200036020c200141186a2001412f6a200141086a10ae808080002001280218210020012903202102200141306a2480808080002002420020001b0b860102027f017e23808080800041206b22012480808080002001411f6a10bd81808000200141086a2001411f6a41d083c0800010bd80808000410121020240024020012802084101470d00200120012903102203370300200110b28180800020002003370308410021020c010b2000410a3602040b20002002360200200141206a2480808080000b4901027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000410b4100200141fd01711b0b7f01027f23808080800041206b22012480808080002001411f6a10bd818080004100210202402001411f6a41d083c0800010be80808000450d002001411f6a10bd818080002001410636020820012000290300370310410041102001411f6a200141086a10ab8080800041fd01711b21020b200141206a24808080800020020b4d02017f017e23808080800041106b2202248080808000200010bd8180800020022001290300200010cc808080003703002002410f6a200210b8818080002103200241106a24808080800020030b8b0f04017f017e087f017e23808080800041e0006b22072480808080002007200010cf818080002208370300200741086a21092007200920082009200810d08180800010ff8180800010fe81808000418184c08000410410ac81808000220837030020074180043b01382007200920082009200810d08180800010ff8180800010fe81808000200741386a410210ac818080003703002007200741df006a10bc81808000370330200741386a41186a220a4200370300200741386a41106a220b4200370300200741386a41086a220c420037030020074200370338200741306a41086a220d200741306a10d4818080004204200741386a412010ad81808000200741106a41186a220e200a290300370300200741106a41106a220f200b290300370300200741106a41086a2210200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac818080003703002007200010aa8180800037033020072000200741306a108f81808000370308200a4200370300200b4200370300200c420037030020074200370338200741086a41086a200741086a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341306a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341386a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800022083703002007200141187420014180fe03714108747220014108764180fe0371200141187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac8180800022083703002007200241187420024180fe03714108747220024108764180fe0371200241187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac81808000221137030020072003290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703402007200341086a290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703382007200920112009201110d08180800010ff8180800010fe81808000200741386a411010ac8180800022083703002007200442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703402007200542388620054280fe0383422886842005428080fc0783421886200542808080f80f834208868484200542088842808080f80f832005421888428080fc07838420054228884280fe038320054238888484843703382007200920082009200810d08180800010ff8180800010fe81808000200741386a411010ac81808000220537030020072003290340220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac81808000220537030020072003290348220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac8180800022043703002007200642388620064280fe0383422886842006428080fc0783421886200642808080f80f834208868484200642088842808080f80f832006421888428080fc07838420064228884280fe03832006423888848484370338200920042009200410d08180800010ff8180800010fe81808000200741386a410810ac818080002104200741e0006a24808080800020040b190020004200370300200020023502004220864204843703080b7c01027e024002400240024020022903002203a741ff0171220241c500460d002002410b470d02200041106a20031080828080000c010b2001200310e58180800021042001200310e481808000210320002004370318200020033703100b420021030c010b200010f981808000370308420121030b200020033703000b130020004200370300200020023100003703080b4602017f017e23808080800041106b2203248080808000200320012002109581808000200329030821042000200329030037030020002004370308200341106a2480808080000b6d02017f027e23808080800041106b2203248080808000200320022903002204200241086a29030022051082828080000240024020032802000d00200329030821040c010b20012005200410e38180800021040b2000420037030020002004370308200341106a2480808080000b4b00200041003602102000200436020c2000200336020820002002360204200020013602002000200220016b410376220236021820002002200420036b410376220420022004491b3602140b3901017f23808080800041106b22032480808080002003200229020037020820002001200341086a109881808000200341106a2480808080000b6a02027f017e23808080800041106b22032480808080002003200228020022042002280204220210fa818080000240024020032802000d00200329030821050c010b20012004200210d78180800021050b2000420037030020002005370308200341106a2480808080000b5202017f017e23808080800041106b2203248080808000200320022903083703082003200229030037030020012003410210da8180800021042000420037030020002004370308200341106a2480808080000b0e00200020012001109b818080000b7d02017f027e23808080800041106b2203248080808000024002402002290300220442ff018342c800520d0020032004370308420121050240200341106a200410f58180800010ff818080004120470d0020002003290308370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b2e01027e4201210302402001290300220442ff018342cd00520d0020002004370308420021030b200020033703000b0e002000200220011099818080000b130020004200370300200020012903003703080b5102017f017e23808080800041106b220324808080800020032001200210978180800042012104024020032802000d0020002003290308370308420021040b20002004370300200341106a2480808080000b130020004200370300200020012903003703080b130020004200370300200020012903003703080b1200200141bb87c08000410f108b828080000b0300000b02000b4502017f017e23808080800041106b2202248080808000200220002001109481808000024020022802004101470d00000b20022903082103200241106a24808080800020030b070020003100000b0d0020003502004220864204840b070020002903000b070020002903000b0a00200010df818080000b6001017f23808080800041106b22042480808080000240200020012903002002290300200310f28180800042ff01834202510d00419087c08000412b2004410f6a418087c0800041b088c08000108682808000000b200441106a2480808080000b12002000200120022003200410d5818080000b12002000200120022003200410d6818080000b12002000200120022003200410d8818080000b140020002001200220032004200510d9818080000b0e0020002001200210da818080000b2e01027e4201210302402002290300220442ff018342cd00520d0020002004370308420021030b200020033703000b1300200041086a200029030010f8818080001a0b5902017f017e23808080800041206b22032480808080002003200236020c20032001360208200341106a2000200341086a109781808000024020032802104101470d00000b20032903182104200341206a24808080800020040b11002000200110b58180800041ff0171450b2601017e417f200041086a2000290300200129030010db81808000220242005220024200531b0b2e01027e4201210302402002290300220442ff018342c800520d0020002004370308420021030b200020033703000b130020004200370300200020022903003703080b0f002000200129030010f6818080000b1a00200020012903002002290300200329030010f7818080001a0b1000200010dd8180800010ff818080000b1000200010e08180800010ff818080000b0a00200010de818080000b02000b6d01037f23808080800041106b22012480808080002001410f6a10ba818080002102024002402001410f6a10bb8180800022032002490d00200320026b41016a22020d0141a889c08000108c82808000000b41a889c08000108d82808000000b200141106a24808080800020020b140020002001200210ec8180800010fd818080000b0e0020002001200210ed818080000b1b002000200110fe81808000200210fe8180800010f1818080001a0b0e0020002001200210dc818080000b0c002000200110e1818080000b0c002000200110e2818080000b0a00200010e6818080000b1000200020012002200310e7818080000b0e0020002001200210e8818080000b0c002000200110e9818080000b0e0020002001200210ea818080000b1000200020012002200310eb818080000b0e0020002001200210ee818080000b0c002000200110ef818080000b12002000200120022003200410f0818080000b0c002000200110f3818080000b0a00200010f4818080000b0c002000200110f5818080000b070020012903000bdf0102027f027e23808080800041c0006b22042480808080002004200041086a220541b889c08000410810b381808000370308200129030021062002290300210720042005200310a5818080003703202004200737031820042006370310410021010340024020014118470d00410021010240034020014118460d01200441286a20016a200441106a20016a290300370300200141086a21010c000b0b20052000200441086a2005200441286a410310da8180800010ab81808000200441c0006a2480808080000f0b200441286a20016a4202370300200141086a21010c000b0b130020004200370300200020022903003703080b070020002903000b1e00200120022003ad4220864204842004ad4220864204841080808080000b1f00200120022003ad4220864204842004ad4220864204841081808080001a0b1a002001ad4220864204842002ad4220864204841082808080000b2e00024020022004460d00000b2001ad4220864204842003ad4220864204842002ad4220864204841083808080000b3000024020032005460d00000b20012002ad4220864204842004ad4220864204842003ad4220864204841084808080000b1a002001ad4220864204842002ad4220864204841085808080000b0c00200120021086808080000b0c00200120021087808080000b08001088808080000b08001089808080000b0800108a808080000b0800108b808080000b0a002001108c808080000b0a002001108d808080000b0c0020012002108e808080000b0a002001108f808080000b0a0020011090808080000b08001091808080000b0e002001200220031092808080000b0c00200120021093808080000b0a0020011094808080000b0c00200120021095808080000b0e002001200220031096808080000b0c00200120021097808080000b0c00200120021098808080000b0c00200120021099808080000b0a002001109a808080000b10002001200220032004109b808080000b0c0020012002109c808080000b0e00200120022003109d808080000b0a002001109e808080000b0800109f808080000b0a00200110a0808080000b0a00200110a1808080000b0e0020012002200310a2808080000b0a00200110a3808080000b0900428390808080010bb50102017f017e23808080800041106b220324808080800002400240200241094b0d00420021040340024020020d002000410036020020002004420886420e843703080c030b200341086a20012d000010fb81808000024020032d00084103460d0020002003290308370204200041013602000c030b200141016a21012002417f6a2102200442068620033100098421040c000b0b20002002360208200041003a0004200041013602000b200341106a2480808080000b820101017f410121020240200141ff017141df00460d000240200141506a41ff0171410a490d000240200141bf7f6a41ff0171411a490d0002402001419f7f6a41ff0171411a490d00200020013a0001200041013a00000f0b200141456a21020c020b2001414b6a21020c010b200141526a21020b200041033a0000200020023a00010b070020004208880b070020004201510b0b002000ad4220864204840b08002000422088a70b160020002001423f87370308200020014208873703000b3201017e420121020240200142ffffffffffffffff00560d0020002001420886420684370308420021020b200020023703000b5001017e42012103024020014280808080808080c0007c42ffffffffffffffff00560d0020012001852001423f87200285844200520d0020002001420886420b84370308420021030b200020033703000ba00601067f0240200028020022032000280208220472450d0002402004410171450d00200120026a210502400240200028020c22060d0041002107200121080c010b41002107200121080340200822042005460d020240024020042c00002208417f4c0d00200441016a21080c010b0240200841604f0d00200441026a21080c010b0240200841704f0d00200441036a21080c010b200441046a21080b200820046b20076a21072006417f6a22060d000b0b20082005460d00024020082c00002204417f4a0d0020044160491a0b024002402007450d00024020072002490d0020072002460d01410021040c020b200120076a2c000041404e0d00410021040c010b200121040b2007200220041b21022004200120041b21010b024020030d00200028021c20012002200028022028020c118080808000000f0b200028020421030240024020024110490d0020012002108a8280800021040c010b024020020d00410021040c010b2002410371210602400240200241044f0d0041002104410021070c010b2002410c712105410021044100210703402004200120076a22082c000041bf7f4a6a200841016a2c000041bf7f4a6a200841026a2c000041bf7f4a6a200841036a2c000041bf7f4a6a21042005200741046a2207470d000b0b2006450d00200120076a21080340200420082c000041bf7f4a6a2104200841016a21082006417f6a22060d000b0b02400240200320044d0d00200320046b2106024002400240410020002d0018220420044103461b22040e03020001020b20062104410021060c010b20064101762104200641016a41017621060b200441016a21042000280210210720002802202108200028021c210003402004417f6a2204450d0220002007200828021011818080800000450d000b41010f0b200028021c20012002200028022028020c118080808000000f0b0240200020012002200828020c11808080800000450d0041010f0b410021040340024020062004470d0020062006490f0b200441016a210420002007200828021011818080800000450d000b2004417f6a2006490f0b200028021c20012002200028022028020c118080808000000b4d01017f23808080800041206b22032480808080002003410036021020034101360204200342043702082003200136021c200320003602182003200341186a36020020032002108582808000000b3601017f23808080800041106b2202248080808000200241013b010c2002200136020820022000360204200241046a10a381808000000b8f0101017f23808080800041c0006b22052480808080002005200136020c2005200036020820052003360214200520023602102005410236021c200541c08ac08000360218200542023702242005418280808000ad422086200541106aad843703382005418380808000ad422086200541086aad843703302005200541306a360220200541186a2004108582808000000b130041908ac08000412b2000108482808000000b14002001200028020020002802041083828080000b180020002802002001200028020428020c118180808000000be90601087f024002402001200041036a417c71220220006b2203490d00200120036b22044104490d002004410371210541002106410021010240200220004622070d004100210102400240200020026b2208417c4d0d00410021090c010b4100210903402001200020096a22022c000041bf7f4a6a200241016a2c000041bf7f4a6a200241026a2c000041bf7f4a6a200241036a2c000041bf7f4a6a2101200941046a22090d000b0b20070d00200020096a21020340200120022c000041bf7f4a6a2101200241016a2102200841016a22080d000b0b200020036a210002402005450d0020002004417c716a22022c000041bf7f4a210620054101460d00200620022c000141bf7f4a6a210620054102460d00200620022c000241bf7f4a6a21060b20044102762108200620016a21030340200021042008450d02200841c001200841c001491b220641037121072006410274210541002102024020084104490d002004200541f007716a210941002102200421010340200128020c2200417f7341077620004106767241818284087120012802082200417f7341077620004106767241818284087120012802042200417f7341077620004106767241818284087120012802002200417f7341077620004106767241818284087120026a6a6a6a2102200141106a22012009470d000b0b200820066b2108200420056a2100200241087641ff81fc0771200241ff81fc07716a418180046c41107620036a21032007450d000b2004200641fc01714102746a22022802002201417f734107762001410676724181828408712101024020074101460d0020022802042200417f7341077620004106767241818284087120016a210120074102460d0020022802082202417f7341077620024106767241818284087120016a21010b200141087641ff811c71200141ff81fc07716a418180046c41107620036a0f0b024020010d0041000f0b2001410371210902400240200141044f0d0041002103410021020c010b2001417c712108410021034100210203402003200020026a22012c000041bf7f4a6a200141016a2c000041bf7f4a6a200141026a2c000041bf7f4a6a200141036a2c000041bf7f4a6a21032008200241046a2202470d000b0b2009450d00200020026a21010340200320012c000041bf7f4a6a2103200141016a21012009417f6a22090d000b0b20030b1a00200028021c20012002200028022028020c118080808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141dc89c0800036020820014204370210200141086a2000108582808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141888ac0800036020820014204370210200141086a2000108582808000000b5701017e02400240200341c000710d002003450d012002410020036b413f71ad8620012003413f71ad220488842101200220048821020c010b20022003413f71ad882101420021020b20002001370300200020023703080bf60804017f017e037f047e23808080800041b0016b2205248080808000420021060240024002400240024020047920037942c0007c20044200521ba7220720027920017942c0007c20024200521ba722084d0d002008413f4b0d01200741df004b0d02200720086b4120490d03200541a0016a2003200441e00020076b2209108e8280800020053502a00142017c210a4200210b420021060240024002400240034020054190016a2001200241c00020086b2208108e82808000200529039001210c0240200820094f0d00200541d0006a200320042008108e82808000024002402005290350220a50450d000c010b200c200a80210c0b200541c0006a200c420020032004109282808000024020012005290340220d5422082002200541c8006a290300220a542002200a511b0d002002200a7d2008ad7d21022001200d7d21012006200b200c7c220c200b54ad7c21060c0b0b200220047c200120037c2204200154ad7c200a7d2004200d54ad7d21022004200d7d21012006200c200b7c427f7c220c200b54ad7c21060c0a0b20054180016a200c200a80220c4200200820096b41ff00712208109082808000200541f0006a200c420020032004109282808000200541e0006a2005290370200541f0006a41086a290300200810908280800020054180016a41086a29030020067c2005290380012206200b7c220b200654ad7c210620072002200541e0006a41086a2903007d20012005290360220c54ad7d2202792001200c7d22017942c0007c20024200521ba722084d0d012008413f4d0d000b200350450d010c020b20012003542208200220045420022004511b450d02200b210c0c070b200120038021020b200120038221012006200b20027c220c200b54ad7c2106420021020c050b200220047d2008ad7d2102200120037d21012006200b42017c220c50ad7c21060c040b200220044200200120035a200220045a20022004511b22081b7d20012003420020081b220454ad7d2102200120047d21012008ad210c0c030b20012001200380220c20037e7d210142002106420021020c020b20022002200342ffffffff0f83220480220620037e7d4220862001422088220c842004802202422086200c200220037e7d422086200142ffffffff0f83842201200480220384210c2001200320047e7d210120024220882006842106420021020c010b200541306a2003200441c00020086b2208108e82808000200541206a200120022008108e8280800042002106200541106a200342002005290320200529033080220c4200109282808000200520044200200c42001092828080002005290310210a02400240200541086a290300200541106a41086a290300220d20052903007c220b200d54ad7c4200520d002001200a5422082002200b542002200b511b450d010b200420027c200320017c2201200354ad7c200b7d2001200a54ad7d2102200c427f7c210c2001200a7d21010c010b2002200b7d2008ad7d21022001200a7d2101420021060b200020013703102000200c3703002000200237031820002006370308200541b0016a2480808080000b5701017e02400240200341c000710d002003450d0120022003413f71ad2204862001410020036b413f71ad88842102200120048621010c010b20012003413f71ad862102420021010b20002001370300200020023703080bf50303017f027e027f23808080800041e0006b220624808080800042002107420021084100210902402001200284500d002003200484500d00420020037d2003200442005322091b2107420020017d20012002420053220a1b2108420020042003420052ad7c7d200420091b21032004200285210402400240420020022001420052ad7c7d2002200a1b2202500d0002402003500d00200641d0006a2007200320082002109282808000200641d8006a290300210141012109200629035021020c020b200641c0006a2008420020072003109282808000200641306a2002420020072003109282808000200641c0006a41086a290300220220062903307c2201200254200641306a41086a290300420052722109200629034021020c010b02402003500d00200641206a2007420020082002109282808000200641106a2003420020082002109282808000200641206a41086a290300220220062903107c2201200254200641106a41086a290300420052722109200629032021020c010b20062007200320082002109282808000200641086a290300210141002109200629030021020b420020027d20022004420053220a1b2108420020012002420052ad7c7d2001200a1b22072004854200590d00410121090b200520093602002000200737030820002008370300200641e0006a2480808080000b6e01067e2000200342ffffffff0f832205200142ffffffff0f8322067e22072003422088220820067e22062005200142208822097e7c22054220867c220a3703002000200820097e2005200654ad4220862005422088847c200a200754ad7c200420017e200320027e7c7c3703080ba50501087f02400240200241104f0d00200021030c010b02402000410020006b41037122046a220520004d0d002004417f6a2106200021032001210702402004450d002004210820002103200121070340200320072d00003a0000200741016a2107200341016a21032008417f6a22080d000b0b20064107490d000340200320072d00003a0000200341016a200741016a2d00003a0000200341026a200741026a2d00003a0000200341036a200741036a2d00003a0000200341046a200741046a2d00003a0000200341056a200741056a2d00003a0000200341066a200741066a2d00003a0000200341076a200741076a2d00003a0000200741086a2107200341086a22032005470d000b0b2005200220046b2208417c7122066a210302400240200120046a22074103710d00200520034f0d0120072101034020052001280200360200200141046a2101200541046a22052003490d000c020b0b200520034f0d002007410374220241187121042007417c71220941046a2101410020026b411871210a2009280200210203402005200220047620012802002202200a7472360200200141046a2101200541046a22052003490d000b0b20084103712102200720066a21010b02402003200320026a22054f0d002002417f6a2108024020024107712207450d000340200320012d00003a0000200141016a2101200341016a21032007417f6a22070d000b0b20084107490d000340200320012d00003a0000200341016a200141016a2d00003a0000200341026a200141026a2d00003a0000200341036a200141036a2d00003a0000200341046a200141046a2d00003a0000200341056a200141056a2d00003a0000200341066a200141066a2d00003a0000200341076a200141076a2d00003a0000200141086a2101200341086a22032005470d000b0b20000b4b01017f23808080800041206b220524808080800020052001200220032004108f82808000200529031021042000200541186a29030037030820002004370300200541206a2480808080000b0bda0a0100418080c0000bd00a7372632f6c69622e7273616d6f756e74656e645f64617465686f7572735f6c6f67676564696470726f6f665f7665726966696564726174655f7065725f686f757273746172745f64617465737461747573746f6b656e776f726b65720a001000060000001000100008000000180010000c0000002400100002000000260010000e000000340010000d000000410010000a0000004b001000060000005100100005000000560010000600000063616e63656c6c656466696e616e63655f617070726f76656466696e616e63655f617070726f7665726d616e616765726d616e616765725f617070726f7665646f7261636c655f7075626b65796f7261636c655f726f746174696f6e737061796d656e7473000000ac00100009000000b500100010000000c500100010000000d500100007000000dc00100010000000ec0010000d000000f9001000100000000901100008000000457363726f77436f756e7400540110000b000000457363726f77000068011000060000004e6f6e6365000000780110000500000041646d696e000000880110000500000050656e64696e6741646d696e980110000c0000005061757365640000ac011000060000004f7261636c654b6579000000bc011000090000000300000000000000000000000000000004000000000000000000000000000000050000000000000000000000000000000143465750000000000010000a000000fd01000009000000000010000a0000000302000030000000000010000a000000f70100002700000000000000000000000000000000000000000010000a000000500200001e0000000000000000000000000010000a000000b302000025000000000010000a0000005d0200002b000000000010000a0000006c02000029000000000010000a0000006e02000015000000000010000a0000006102000024000000000010000a0000003c02000025000000000010000a0000004b0200000d000000000010000a000000ea0200003b000000000010000a000000a503000030000000000010000a000000a80300000d000000000010000a000000950300002c000000000010000a0000001104000030000000000010000a000000ee03000032000000000010000a000000fd03000030000000000010000a000000ff03000015000000000010000a000000f20300002b000000000010000a000000e403000027000000000010000a0000008e040000370000000000000000000000010000000100000063616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c7565436f6e76657273696f6e4572726f722f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f656e762e7273000000ca03100063000000770100000e0000002f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f73746f726167652e72730040041000670000009a000000090000007472616e73666572617474656d707420746f206164642077697468206f766572666c6f77c00410001c000000617474656d707420746f2073756274726163742077697468206f766572666c6f77000000e40410002100000063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75653a2000000001000000000000003b05100002000000008f460e636f6e74726163747370656376300000000400000000000000000000000d436f6e74726163744572726f7200000000000016000000000000000f416c7265616479417070726f7665640000000001000000000000000c556e617574686f72697a6564000000020000000000000016496e76616c69644f7261636c655369676e61747572650000000000030000000000000010496e76616c69645061796d656e744964000000040000000000000015496e73756666696369656e74417070726f76616c730000000000000500000000000000175061796d656e74416c726561647946696e616c697a65640000000006000000000000000d496e76616c6964416d6f756e7400000000000007000000000000000f457363726f7743616e63656c6c65640000000008000000000000000c496e76616c69644e6f6e63650000000900000000000000084e6f7441646d696e0000000a000000000000000650617573656400000000000b000000000000000f41646d696e416c7265616479536574000000000c000000000000000c50726f6f664d697373696e670000000d000000000000000d4e6f6e63654f766572666c6f770000000000000e00000000000000125369676e6572734e6f7444697374696e637400000000000f0000003b546865206f7261636c65207075626c6963206b6579206973206e6f74206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000164f7261636c654b65794e6f745265676973746572656400000000001000000042417474657374656420686f757273207820726174655f7065725f686f757220646f6573206e6f7420657175616c2074686520657363726f77656420616d6f756e742e000000000013416d6f756e74486f7572734d69736d6174636800000000110000002a656e645f64617465206973206e6f74207374726963746c792061667465722073746172745f646174652e00000000000d496e76616c6964506572696f64000000000000120000002642617463682065786365656473204d41585f42415443485f53495a45207061796d656e74732e00000000000d4261746368546f6f4c61726765000000000000130000004454686973205741534d2070696e7320616e2065787065637465642061646d696e20616e642074686520737570706c6965642061646472657373206973206e6f742069742e0000000d41646d696e4d69736d6174636800000000000014000000464e6f2061646d696e207472616e736665722069732070656e64696e672c206f72207468652063616c6c6572206973206e6f74207468652070726f706f7365642061646d696e2e00000000000e4e6f50656e64696e6741646d696e000000000015000000336075706772616465602072657175697265732074686520636f6e747261637420746f206265207061757365642066697273742e00000000094e6f74506175736564000000000000160000000300000000000000000000000d5061796d656e7453746174757300000000000005000000000000000750656e64696e670000000000000000000000000f4d616e61676572417070726f7665640000000001000000000000000f46696e616e6365417070726f7665640000000002000000000000000946696e616c697a656400000000000003000000000000000943616e63656c6c6564000000000000040000000100000000000000000000000f5061796d656e745363686564756c65000000000a0000000000000006616d6f756e7400000000000b0000000000000008656e645f6461746500000006000000000000000c686f7572735f6c6f676765640000000b00000000000000026964000000000004000000815365742074727565206f6e6c7920627920607375626d69745f686f7572735f70726f6f666020616674657220612076616c69642045643235353139206f7261636c650a7369676e61747572652e20607061795f626174636860207265667573657320746f20736574746c652061207061796d656e7420776974686f75742069742e0000000000000e70726f6f665f7665726966696564000000000001000000000000000d726174655f7065725f686f75720000000000000b000000000000000a73746172745f6461746500000000000600000000000000067374617475730000000007d00000000d5061796d656e745374617475730000000000008c5065722d7061796565205374656c6c617220417373657420436f6e7472616374202853414329206164647265737320e2809420652e672e2074686520555344432053414320666f720a6f6e6520706179656520616e6420746865206e617469766520584c4d2053414320666f7220616e6f746865722077697468696e207468652073616d652062617463682e00000005746f6b656e000000000000130000000000000006776f726b65720000000000130000000100000000000000000000000e436f7265466c6f77457363726f77000000000008000000000000000963616e63656c6c656400000000000001000000000000001066696e616e63655f617070726f76656400000001000000000000001066696e616e63655f617070726f7665720000001300000000000000076d616e61676572000000001300000000000000106d616e616765725f617070726f76656400000001000000000000000d6f7261636c655f7075626b6579000000000003ee000000200000004354696d657320746865206f7261636c65206b657920686173206265656e20726f7461746564206f6e207468697320657363726f772028617564697420747261696c292e00000000106f7261636c655f726f746174696f6e730000000400000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000002000000000000000000000007446174614b6579000000000700000000000000000000000b457363726f77436f756e7400000000010000000000000006457363726f77000000000001000000040000000100000000000000054e6f6e6365000000000000010000000400000000000000000000000541646d696e000000000000000000003d50726f706f736564206e6578742061646d696e2c206177616974696e6720616363657074616e6365202874776f2d737465702068616e646f766572292e0000000000000c50656e64696e6741646d696e0000000000000000000000065061757365640000000000010000004a52656769737465726564206f7261636c65207369676e696e67206b6579732e2050726573656e6365203d3e20747275737465642062792074686520706c6174666f726d2061646d696e2e0000000000094f7261636c654b657900000000000001000003ee0000002000000000000000c85365742074686520636f6e74726163742061646d696e206f6e63652c20696d6d6564696174656c79206166746572206465706c6f792e204964656d706f74656e742d67756172643a0a6661696c7320696620616e2061646d696e20697320616c726561647920636f6e666967757265642e204966206e657665722063616c6c65642c2074686520636f6e74726163740a73696d706c7920686173206e6f2061646d696e20616e642063616e206e6576657220626520706175736564206f722075706772616465642e0000000a696e69745f61646d696e000000000001000000000000000561646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000f85468652061646d696e20616464726573732062616b656420696e746f2074686973205741534d206174206275696c642074696d652c20696620616e792e0a0a526561642d6f6e6c792c20736f20616e206f70657261746f7220286f7220616e2061756469746f72292063616e20636f6e6669726d206166746572206465706c6f7920746861740a7468652072756e6e696e6720636f64652069732070696e6e656420746f20746865206b65792074686579206578706563742c20726174686572207468616e207472757374696e670a7468617420746865206465706c6f7920736372697074207761732072756e20636f72726563746c792e0000000e65787065637465645f61646d696e00000000000000000001000003e800000013000000000000010d50726f706f73652061206e65772061646d696e202863757272656e742061646d696e206f6e6c79292e20537465702031206f6620322e0a0a48616e646f7665722069732074776f2d73746570206265636175736520612073696e676c652d73746570207472616e7366657220746f2061206d69737479706564206f720a756e636f6e74726f6c6c65642061646472657373207065726d616e656e746c792064657374726f797320746865206162696c69747920746f2070617573652c20757067726164652c0a6f72206d616e61676520746865206f7261636c652072656769737472792e205468652070726f706f736564206b6579206d7573742070726f76652069742063616e207369676e2e0000000000000d70726f706f73655f61646d696e0000000000000100000000000000096e65775f61646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000004341636365707420612070656e64696e672061646d696e2068616e646f766572202870726f706f7365642061646d696e206f6e6c79292e20537465702032206f6620322e000000000c6163636570745f61646d696e0000000000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000345468652063757272656e746c7920636f6e666967757265642061646d696e2c206966206f6e6520686173206265656e207365742e000000096765745f61646d696e0000000000000000000001000003e80000001300000000000000865061757365206f7220756e70617573652073746174652d6368616e67696e67206f7065726174696f6e73202861646d696e206f6e6c79292e206063616e63656c5f657363726f77600a737461797320617661696c61626c65207768696c652070617573656420736f2066756e64732063616e20616c7761797320626520726566756e6465642e00000000000a7365745f706175736564000000000001000000000000000670617573656400000000000100000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000000000000969735f7061757365640000000000000000000001000000010000000000000076557067726164652074686520636f6e7472616374205741534d202861646d696e206f6e6c79292e20456e61626c657320666978657320776974686f7574206368616e67696e670a74686520636f6e74726163742061646472657373206f72206d6967726174696e6720657363726f772066756e64732e000000000007757067726164650000000001000000000000000d6e65775f7761736d5f68617368000000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000001e4526567697374657220616e206f7261636c65207369676e696e67206b657920617320747275737465642062792074686520706c6174666f726d202861646d696e206f6e6c79292e0a0a57485920412052454749535452593a2070726576696f75736c7920746865206d616e616765722070617373656420616e7920606f7261636c655f7075626b65796020746865790a6c696b656420696e746f2060696e697469616c697a655f6d756c74695f7369675f657363726f77602c20736f2061206d616e6167657220636f756c6420696e7374616c6c0a7468656972206f776e206b657920616e64207369676e207468656972206f776e2022766572696669656420776f726b22206174746573746174696f6e732e205468650a70726f6f662d6f662d776f726b206761746520776173207468657265666f7265206d616e616765722d61747465737461626c65202d2d2070726f6365647572616c2c206e6f740a63727970746f677261706869632e20457363726f7773206d6179206e6f77206f6e6c79206e616d652061206b6579207468652061646d696e2068617320726567697374657265642c0a7768696368206d616b657320746865206f7261636c6520616e20696e646570656e64656e7420706172747920627920636f6e737472756374696f6e2e0000001372656769737465725f6f7261636c655f6b6579000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019b5265766f6b6520612070726576696f75736c792072656769737465726564206f7261636c65206b6579202861646d696e206f6e6c79292e0a0a4578697374696e6720657363726f777320616c7265616479206e616d696e672074686973206b6579206b6565702066756e6374696f6e696e67202d2d207265766f6b696e672069730a6e6f7420726574726f6163746976652c20626563617573652073696c656e746c7920696e76616c69646174696e6720696e2d666c69676874206174746573746174696f6e730a776f756c6420737472616e642066756e64656420657363726f77732e2049742073746f707320746865206b6579206265696e67206e616d6564206279204e455720657363726f77730a616e64204e455720726f746174696f6e732e20546f207265746972652061206b65792066726f6d2061206c69766520657363726f772c20746865206d616e616765722063616c6c730a60726f746174655f6f7261636c655f6b6579602c207768696368207265766f6b6573207468617420657363726f7727732076657269666965642070726f6f66732e00000000117265766f6b655f6f7261636c655f6b65790000000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000325472756520696620607075626b657960206973206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000001869735f6f7261636c655f6b65795f726567697374657265640000000100000000000000067075626b65790000000003ee00000020000000010000000100000000000000d3526f7461746520746865206f7261636c65207075626c6963206b657920666f7220616e20657363726f772e205369676e6174757265732070726f6475636564206279207468650a72657469726564206b65792073746f7020766572696679696e6720696d6d6564696174656c792c2073696e636520607665726966795f6f7261636c655f776f726b602072656164730a746869732073746f726564206b65792e204d616e616765722d617574686f72697a65643b2072656675736564206f6e63652066756e64732068617665206d6f7665642e0000000011726f746174655f6f7261636c655f6b6579000000000000020000000000000009657363726f775f696400000000000004000000000000000a6e65775f7075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000a2496e697469616c697a652061206d756c74692d7369676e617475726520657363726f772077697468207061796d656e74207363686564756c657320616e64206f7261636c65207075626c6963206b65792e0a546865206f7261636c655f7075626b657920697320616e2045643235353139207075626c6963206b6579207573656420746f2076657269667920776f726b2070726f6f66207369676e6174757265732e00000000001b696e697469616c697a655f6d756c74695f7369675f657363726f77000000000400000000000000076d616e616765720000000013000000000000001066696e616e63655f617070726f76657200000013000000000000000d6f7261636c655f7075626b6579000000000003ee0000002000000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000001000003e900000004000007d00000000d436f6e74726163744572726f7200000000000000000001595375626d697420686f7572732070726f6f6620766572696669656420627920616e2045643235353139206f7261636c65207369676e61747572652e0a0a546865206f7261636c65207369676e7320746865203139382d6279746520646f6d61696e2d73657061726174656420707265696d61676520646f63756d656e746564206f6e0a606275696c645f70726f6f665f6d657373616765602028736368656d61207632292e2054686520636f6e74726163742072656275696c6473207468617420707265696d6167650a66726f6d2073746f7265642073746174652c20766572696669657320697420616761696e73742074686520657363726f772773206f7261636c65207075626c6963206b65792c0a656e666f726365732060686f75727320782072617465203d3d20616d6f756e74602c20616e6420636f6e73756d657320746865206e657874206578706563746564206e6f6e63652e000000000000127375626d69745f686f7572735f70726f6f660000000000050000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f6964000000000004000000000000000c686f7572735f6c6f676765640000000b00000000000000056e6f6e63650000000000000600000000000000097369676e6174757265000000000003ee0000004000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e4d616e6167657220617070726f76616c206f66207061796d656e7428732900000000000f6d616e616765725f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e46696e616e636520617070726f76616c206f66207061796d656e7428732900000000000f66696e616e63655f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000bb46696e616c697a65207061796d656e74206f6e636520626f746820617070726f76616c7320617265206f627461696e65640a4465707265636174656420616c6961732072657461696e656420736f20746865204d61696e6e65742d6465706c6f7965642041424920616e6420746865206578697374696e670a64617368626f61726420636c69656e74206b65657020776f726b696e672e204e65772063616c6c6572732073686f756c642075736520607061795f6261746368602e000000001066696e616c697a655f7061796d656e74000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f7200000000000000000000b2536574746c65206576657279207061796d656e7420696e2074686520657363726f773a206f6e65207472616e73616374696f6e2c206f6e6520534143207472616e73666572207065720a70617965652c206561636820696e20746861742070617965652773206f776e2061737365742e20526571756972657320626f746820617070726f76616c7320414e4420610a7665726966696564206f7261636c652070726f6f66206f6e20657665727920726f772e0000000000097061795f6261746368000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f72000000000000000000006e43616e63656c20616e20657363726f77202864697370757465207265736f6c7574696f6e20e28094206d616e61676572206f6e6c79292e0a416c6c6f776564206576656e207768696c65207061757365642028656d657267656e6379207769746864726177616c2070617468292e00000000000d63616e63656c5f657363726f77000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f720000000000000000000017526574726965766520657363726f772064657461696c73000000000a6765745f657363726f770000000000010000000000000009657363726f775f69640000000000000400000001000003e9000007d00000000e436f7265466c6f77457363726f770000000007d00000000d436f6e74726163744572726f720000000000000000000121457874656e6420616e20657363726f7727732073746f72616765206c69666574696d652e20416e796f6e65206d61792063616c6c20746869732e0a0a50657273697374656e7420656e747269657320746861742072756e206f7574206f662072656e742061726520617263686976656420746f2074686520457870697265640a537461746520537461636b20616e642063616e20626520726573746f7265643b207468657920617265206e6f742064656c657465642e20546865206661696c75726520746869730a61766f69647320697320612066756e64656420657363726f77206265636f6d696e672074656d706f726172696c7920756e757361626c6520756e74696c20736f6d656f6e650a7061797320746f20726573746f72652069742e00000000000011657874656e645f657363726f775f74746c000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019552657475726e2074686520657861637420627974657320746865206f7261636c65206d757374207369676e20666f722074686973207061796d656e742e0a0a526561642d6f6e6c792e204578706f73696e672074686520707265696d616765206d616b65732074686520434f4e5452414354207468652073696e676c6520736f75726365206f660a747275746820666f7220746865206d65737361676520666f726d61743a20616e206f66662d636861696e207369676e65722063616e2073696d756c61746520746869732063616c6c0a616e64207369676e207468652072657475726e656420627974657320766572626174696d20696e7374656164206f66207265696d706c656d656e74696e6720746865206c61796f75740a616e6420686f70696e67207468652074776f2061677265652e20457665727920686973746f726963616c206d69736d61746368206265747765656e2061207369676e657220616e640a6120766572696669657220697320612062756720746869732072656d6f76657320627920636f6e737472756374696f6e2e0000000000000e70726f6f665f707265696d6167650000000000040000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f69640000000000040000000000000005686f7572730000000000000b00000000000000056e6f6e63650000000000000600000001000003e90000000e000007d00000000d436f6e74726163744572726f7200000000000000000000ac52657475726e20746865206e657874206578706563746564206f7261636c65206e6f6e636520666f7220616e20657363726f772e0a546865206f7261636c65206d757374207369676e20612070726f6f66207573696e6720746869732065786163742076616c756520287265706c61792070726f74656374696f6e292e0a52657475726e73203020666f7220616e20756e6b6e6f776e2f756e696e697469616c697a656420657363726f772e000000096765745f6e6f6e6365000000000000010000000000000009657363726f775f6964000000000000040000000100000006001e11636f6e7472616374656e766d6574617630000000000000001400000000006f0e636f6e74726163746d65746176300000000000000005727376657200000000000006312e38352e3000000000000000000008727373646b7665720000002f32302e352e30233965326333303232623433353562323234613761383134653133626135313736316565623134626200" + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "system", + "body": { + "v0": { + "topics": [ + { + "symbol": "executable_update" + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + ], + "data": { + "vec": [] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_impossible_without_an_admin.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_impossible_without_an_admin.1.json new file mode 100644 index 0000000..3ab0a19 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_impossible_without_an_admin.1.json @@ -0,0 +1,191 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "0101010101010101010101010101010101010101010101010101010101010101" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 10 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 10 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 10 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "bytes": "0101010101010101010101010101010101010101010101010101010101010101" + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_pause_is_mandatory_and_checked_first.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_pause_is_mandatory_and_checked_first.1.json new file mode 100644 index 0000000..9e135a4 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_pause_is_mandatory_and_checked_first.1.json @@ -0,0 +1,719 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": false + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da", + "code": "0061736d01000000019b022960037f7f7f017f60027f7f017f60047e7e7e7e017e60027e7e017e60037e7e7e017e6000017e60017e017e60037f7f7f0060027f7e0060047f7f7f7f0060057f7f7e7f7f0060027f7f017e60047f7f7f7e0060027e7f017e60017f017e60017e017f6000017f60017f0060017f017f60027f7e017f60057f7e7e7e7e0060057e7e7e7e7e017e60067f7f7e7e7e7e017f60027f7f0060067f7f7f7e7e7e0060077f7f7f7f7e7e7e017e60057f7f7f7f7f0060000060057f7e7e7f7f017e60057f7e7e7f7f0060057f7f7f7f7f017e60067f7e7f7f7f7f017e60037f7f7f017e60037f7e7e017f60037f7e7e017e60027f7e017e60047f7e7e7e017e60057f7e7e7e7e017e60037f7e7e0060047f7e7e7f0060067f7e7e7e7e7f0002d901240162013200020162013100020162016a0003016d01390004016d016100020176016700030178013000030178013100030178013300050178013600050178013700050178013800050169015f00060169013000060169013600030169013700060169013800060176015f0005017601300004017601310003017601330006017601360003016c015f0004016c01300003016c01310003016c01320003016c01360006016c01370002016c013800030164015f00040162015f00060162013400050162013800060163015f000601630130000401610130000603f301f1010707070708090a010b07070701070c070c070c070c0b0c0b010701070707070b07070707070707070d0e0b0b0b0b0b070b0b0b0b0b0b0b070b0b0b0107060f05060f0510051106120510060f060f060f060f0313021415160612061206170612061706120218060e1110120b1907070707071a070707070707070707070701111b0b0b0b0b0b0e0c1c1d1e1f20071120010107070b0912120e11122122072223230e2422232224222325230e230b09070e1c1d201e1f2022220e0e0e0e23232223230e242223222422222223252224230e23232423050717060f0e0f0808260007171a11010101001111271427281400140405017001040405030100110619037f01418080c0000b7f0041d08ac0000b7f0041d08ac0000b07ba031b066d656d6f727902000a696e69745f61646d696e00610e65787065637465645f61646d696e00630d70726f706f73655f61646d696e00640c6163636570745f61646d696e0066096765745f61646d696e00680a7365745f706175736564006a0969735f706175736564006c0775706772616465006e1372656769737465725f6f7261636c655f6b65790070117265766f6b655f6f7261636c655f6b657900721869735f6f7261636c655f6b65795f72656769737465726564007411726f746174655f6f7261636c655f6b657900761b696e697469616c697a655f6d756c74695f7369675f657363726f770078127375626d69745f686f7572735f70726f6f66007a0f6d616e616765725f617070726f7665007c0f66696e616e63655f617070726f7665007e1066696e616c697a655f7061796d656e740080010d63616e63656c5f657363726f770082010a6765745f657363726f7700840111657874656e645f657363726f775f74746c0086010e70726f6f665f707265696d616765008801096765745f6e6f6e6365008a01097061795f6261746368008001015f00a4010a5f5f646174615f656e6403010b5f5f686561705f626173650302090c010041010b03a201890288020adff901f1014602017f017e23808080800041106b220324808080800020032001200210a580808000200329030821042000200329030037030020002004370308200341106a2480808080000b6102017f017e23808080800041106b22032480808080002003200229030022041081828080000240024020032802000d00200329030821040c010b2001200410c38180800021040b2000420037030020002004370308200341106a2480808080000b6401027e02400240024020022903002203a741ff0171220241c000460d0020024106470d0142002104200310fc8180800021030c020b420021042001200310c48180800021030c010b4201210410f98180800021030b20002004370300200020033703080bf80304027f017e017f047e23808080800041d0006b22032480808080004100210402400340200441c000460d01200320046a4202370300200441086a21040c000b0b0240024002400240024002400240024002402002290300220542ff018342cc00520d0020012005419482c0800041082003410810af818080001a410120032d0000220441004741017420044101461b22044102460d01410120032d0008220241004741017420024101461b22024102460d02200341c0006a200341106a2001109c8180800020032802400d0320032903482105200341c0006a200341186a2001109c8180800020032802400d04410120032d0020220641004741017420064101461b22064102460d0520032903482107200341c0006a200341286a2001109a8180800020032802400d062003290330220842ff01834204520d0702402003290338220942ff018342cb00520d002003290348210a200020043a0026200020023a0025200020063a002420002008422088a7360220200020093703182000200a37031020002005370308200020073703000c090b200041023a00260c080b200041023a00260c070b200041023a00260c060b200041023a00260c050b200041023a00260c040b200041023a00260c030b200041023a00260c020b200041023a00260c010b200041023a00260b200341d0006a2480808080000b3b01017f23808080800041106b2202248080808000200220013703082000200241086a10d48180800010cc818080001a200241106a2480808080000b12002000200142012002200310aa808080000b270020002000200110ac808080002002200310fe81808000200410fe8180800010cd818080001a0b4d02017f017e41022102024020002000200110ac808080002203420110bf81808000450d00410121020240024020002003420110c081808000a741ff01710e020102000b000b410021020b20020bcb0502017f017e23808080800041306b220224808080800002400240024002400240024002400240024020012802000e0700010203040506000b200241206a200041e082c08000109f8180800020022802200d07200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c060b200241206a200041f082c08000109f8180800020022802200d0620022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d062002200229032837031020022003370308200241206a200241086a2000109d818080000c050b200241206a2000418083c08000109f8180800020022802200d0520022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d052002200229032837031020022003370308200241206a200241086a2000109d818080000c040b200241206a2000419083c08000109f8180800020022802200d04200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c030b200241206a200041a483c08000109f8180800020022802200d03200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c020b200241206a200041b483c08000109f8180800020022802200d02200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c010b200241206a200041c883c08000109f8180800020022802200d0120022002290328370318200241186a10d4818080002103200241206a200141086a200010a18180800020022802200d012002200229032837031020022003370308200241206a200241086a2000109d818080000b20022903282103200229032050450d00200241306a24808080800020030f0b000b5e01017e02400240024020012001200210ac808080002203420110bf818080000d00410021010c010b20012003420110c081808000220342ff01834204520d012003422088a72102410121010b20002002360204200020013602000f0b000b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200042003703000c010b200320012004420110c081808000370308200341106a2001200341086a10a68080800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b880102017f017e23808080800041306b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200041023a00260c010b200320012004420110c081808000370300200341086a2001200310a78080800020032d002e4102460d012000200341086a41281093828080001a0b200341306a2480808080000f0b000b160020002000200110ac80808000420110bf818080000b1000200020012002420110b2808080000b210020002000200110ac808080002002200010a781808000200310ca818080001a0b1000200020012002420110b4808080000b210020002000200110ac808080002000200210b980808000200310ca818080001a0b1000200020012002420110b6808080000b210020002000200110ac808080002002200010a681808000200310ca818080001a0b1000200020012002420110b8808080000b210020002000200110ac808080002000200210bb80808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110db80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b210020002000200110ac808080002002200010a881808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110a480808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4d02017f017e41022102024020002000200110ac808080002203420210bf81808000450d00410121020240024020002003420210c081808000a741ff01710e020102000b000b410021020b20020b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420210bf818080000d00200042003703000c010b200320012004420210c081808000370308200341106a2001200341086a10b18180800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b160020002000200110ac80808000420210bf818080000b1000200020012002420210b6808080000b1000200020012002420210ba808080000b850102017f027e23808080800041106b220324808080800020032001200210b6818080000240024020032802000d00200320032903082204370300420121050240200341086a200410d08180800010ff8180800041c000470d0020002003290300370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b950203017f017e027f23808080800041c0006b22032480808080002001200210c380808000210420032001200241086a10c38080800037030820032004370300410021020240034020024110460d01200341106a20026a4202370300200241086a21020c000b0b200341246a200341106a200341106a41106a2003200341106a109681808000410020032802382202200328023422056b2206200620024b1b21022003280224200541037422066a2105200328022c20066a2106024003402002450d0120052006200110a981808000370300200541086a2105200641086a21062002417f6a21020c000b0b2001200341106a410210b08180800021042000420037030020002004370308200341c0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110d381808000024020022802004101470d00000b20022903082103200241106a24808080800020030b7902017f027e23808080800041206b2203248080808000200341106a2002200110a0818080000240024020032802100d00200320032903183703082001200341086a410110b0818080002104420021050c010b10f9818080002104420121050b2000200537030020002004370308200341206a2480808080000ba30102017f017e23808080800041206b2203248080808000200341106a200120021091818080000240024020032802100d0020032903182104200341106a2001200241046a10918180800020032802100d00200320032903183703082003200437030020012003410210b081808000210420004200370300200020043703080c010b10f981808000210420004201370300200020043703080b200341206a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200210918180800002400240024020032802200d0020032903282104200341206a2001200241046a10918180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200241086a10918180800002400240024020032802200d0020032903282104200341206a20022001109e8180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd00102017f027e23808080800041306b2203248080808000200341206a2001200241106a10918180800002400240024020032802200d0020032903282104200341206a200120021094818080002003290328210520032802200d01200341206a2001200241146a10918180800020032802200d002003200329032837031820032005370310200320043703082001200341086a410310b081808000210520004200370300200020053703080c020b10f98180800021050b20004201370300200020053703080b200341306a2480808080000bd20202017f067e23808080800041c0006b2203248080808000200341306a2001200241206a10918180800002400240024020032802300d0020032903382104200341306a2001200241246a10918180800020032802300d0020032903382105200341306a200241106a2001109e8180800020032802300d0020032903382106200341306a200241186a2001109e8180800020032802300d0020032903382107200341306a200120021094818080002003290338210820032802300d01200341306a2001200241306a1094818080002003290338210902402003280230450d00200921080c020b20032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410610b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341c0006a2480808080000bbd0302017f087e23808080800041d0006b2203248080808000200341c0006a2001200241386a10918180800002400240024020032802400d0020032903482104200341c0006a20012002413c6a10918180800020032802400d0020032903482105200341c0006a200241206a2001109e8180800020032802400d0020032903482106200341c0006a200241286a2001109e8180800020032802400d0020032903482107200341c0006a200120021094818080002003290348210820032802400d01200341c0006a2001200241106a1094818080002003290348210902402003280240450d00200921080c020b200341c0006a2001200241306a10a4808080002003290348210a02402003280240450d00200a21080c020b200341c0006a2001200241c0006a10a4808080002003290348210b02402003280240450d00200b21080c020b2003200b3703382003200a37033020032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410810b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341d0006a2480808080000b2a00024020022802000d0020004200370300200042023703080f0b2000200241086a2001109e818080000b4001017f23808080800041106b2202248080808000200220003703082001200241086a200110a88180800010ce818080002100200241106a24808080800020000b15002000280200417f6aad4220864283808080107c0b4502017f017e23808080800041106b220224808080800020022000200110c780808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1d00024020012802000d0020012903080f0b200141046a10cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c680808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6902027f017e23808080800041106b2202248080808000200141046a21030240024020012802000d00200220002003109181808000024020022802000d00200229030821040c020b10f9818080001a000b200310cd8080800021040b200241106a24808080800020040b4502017f017e23808080800041106b220224808080800020022000200110d380808000024020022802004101470d00000b20022903082103200241106a24808080800020030bd00302017f097e23808080800041e0006b2203248080808000200341d0006a200120021094818080000240024020032802500d0020032903582104200341d0006a2001200241c8006a10a48080800020032802500d0020032903582105200341d0006a2001200241106a10948180800020032802500d0020032903582106200341d0006a2001200241d0006a10918180800020032802500d0020032903582107200341d0006a2001200241d8006a10938180800020032802500d0020032903582108200341d0006a2001200241206a10948180800020032802500d0020032903582109200341d0006a2001200241c0006a10a48080800020032802500d002003290358210a2002350254210b200341d0006a200241386a2001109e8180800020032802500d002003290358210c200341d0006a200241306a2001109e8180800020032802500d00200320032903583703482003200c3703402003200b4220864204843703382003200a370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200141dc80c08000410a2003410a10ae81808000210420004200370300200020043703080c010b200042013703000b200341e0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110c280808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110b781808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110ca80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1700024020012802000d0042020f0b200110cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c980808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c580808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c880808000024020022802004101470d00000b20022903082103200241106a24808080800020030bf20202017f067e23808080800041d0006b2203248080808000200341c0006a2001200241266a1093818080000240024020032802400d0020032903482104200341c0006a2001200241256a10938180800020032802400d0020032903482105200341c0006a200241086a2001109e8180800020032802400d0020032903482106200341c0006a20022001109e8180800020032802400d0020032903482107200341c0006a2001200241246a10938180800020032802400d0020032903482108200341c0006a200241106a200110a18180800020032802400d0020032903482109200341c0006a2001200241206a10918180800020032802400d0020032003290348370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200320022903183703382001419482c0800041082003410810ae81808000210420004200370300200020043703080c010b200042013703000b200341d0006a2480808080000b6802017f017e23808080800041106b22022480808080000240024020012802000d0020022000200141086a10d381808000024020022802000d00200229030821030c020b10f9818080001a000b200141046a10cd8080800021030b200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110cb80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6502017f017e23808080800041106b22022480808080000240024020012d00264102460d0020022000200110db80808000024020022802000d00200229030821030c020b10f9818080001a000b200110cd8080800021030b200241106a24808080800020030b2501017e20002903002202422088a72200410520004105491b4105200242ff01834204511b0b9e0502027f0b7e23808080800041f0006b22032480808080004100210402400340200441d000460d01200320046a4202370300200441086a21040c000b0b024002400240024002400240024002400240024002402002290300220542ff018342cc00520d002001200541dc80c08000410a2003410a10af818080001a200341d0006a2001200310928180800020032802500d01200341e8006a290300210520032903602106200341d0006a2001200341086a10a68080800020032802500d0220032903582107200341d0006a2001200341106a10928180800020032802500d032003290318220842ff01834204520d04410120032d0020220441004741017420044101461b22044102460d05200341e8006a29030021092003290360210a200341d0006a2001200341286a10928180800020032802500d06200341e8006a290300210b2003290360210c200341d0006a2001200341306a10a68080800020032802500d072003290358210d200341386a200410df8080800022024105460d08200341d0006a200341c0006a2001109c8180800020032802500d092003290358210e200341d0006a200341c8006a2001109c81808000024020032802500d002003290358210f2000200c3703202000200a37031020002006370300200020043a00582000200236025420002008422088a7360250200020073703482000200d3703402000200e3703382000200f3703302000200b37032820002009370318200020053703080c0b0b200041053602540c0a0b200041053602540c090b200041053602540c080b200041053602540c070b200041053602540c060b200041053602540c050b200041053602540c040b200041053602540c030b200041053602540c020b200041053602540c010b200041053602540b200341f0006a2480808080000b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e2808080003602082001200141086a10d7808080002100200141206a24808080800020000be90101027f23808080800041306b2201248080808000200120003703082001412f6a10bd81808000410c210202402001412f6a41d083c0800010be808080000d00200141086a10b2818080002001412f6a10bd818080002001412f6a41d083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ef2eed90b3703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020b200141306a24808080800020020b3d02017f017e23808080800041206b2200248080808000200042003703082000411f6a200041086a10dd808080002101200041206a24808080800020010b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e5808080003602082001200141086a10d7808080002100200141206a24808080800020000bdd0101027f23808080800041306b220124808080800020012000370308200141106a108c818080000240024020012802100d002001412f6a10bd818080002001412f6a41e083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ed4b8bacdbed7013703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020c010b200128021421020b200141306a24808080800020020b3e02017f017e23808080800041106b2200248080808000200010e78080800036020c20002000410c6a10d7808080002101200041106a24808080800020010bad0203017f017e017f23808080800041306b22002480808080002000412f6a10bd81808000200041106a2000412f6a41e083c0800010bd808080000240024020002802104101470d00200020002903182201370308200041086a10b2818080002000412f6a10bd818080002000412f6a41d083c08000200041086a10c0808080002000412f6a10bd818080002000412f6a2000412f6a41e083c0800010ac80808000420210cb818080001a2000412f6a10bd818080002000412f6a418087014180d21f10c181808000200020013703202000428ef2b5958ab5023703182000428ee6aeb9ea043703102000412f6a2000412f6a200041106a10d480808000200041206a2000412f6a10a88180800010c2818080001a410021020c010b411521020b200041306a24808080800020020b4102017f017e23808080800041206b2200248080808000200041086a10e9808080002000411f6a200041086a10dd808080002101200041206a24808080800020010b3e01017f23808080800041106b22012480808080002001410f6a10bd8180800020002001410f6a41d083c0800010bd80808000200141106a2480808080000b5c01027f23808080800041106b2201248080808000410121020240024002402000a741ff01710e020102000b000b410021020b2001200210eb8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bdc0101017f23808080800041206b2201248080808000200120003a0007200141086a108c818080000240024020012802080d002001411f6a10bd818080002001411f6a41f083c08000200141076a10bf808080002001411f6a10bd818080002001411f6a418087014180d21f10c181808000200120012d00073a001e2001428ed2aadceeac033703102001428ee6aeb9ea043703082001411f6a2001411f6a200141086a10d4808080002001411e6a2001411f6a10a68180800010c2818080001a410021000c010b200128020c21000b200141206a24808080800020000b4102017f017e23808080800041106b2200248080808000200010ed808080003a000e2000410e6a2000410f6a10a6818080002101200041106a24808080800020010b4401027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000200141fd01710b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010ef808080003602082001200141086a10d7808080002100200141206a24808080800020000bcd0101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd81808000411621022001411f6a41f083c0800010bc8080800041fd0171450d01200120003703102001428ed4a9f3cdadeb013703082001428ee6aeb9ea043703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a2001411f6a10bd818080002001411f6a200010a880808000410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f1808080003602082001200141086a10d7808080002100200141206a24808080800020000be60101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418084c0800010b5808080002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418087014180f6de0010a980808000200120003703102001428ed8ea1b3703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f3808080003602082001200141086a10d7808080002100200141206a24808080800020000bc20101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001411f6a200110ac80808000420110cb818080001a200120003703102001428ed4b0faaebd033703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6b01017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f5808080003a0008200141086a2001411f6a10a6818080002100200141206a24808080800020000b5101027f23808080800041206b22012480808080002001411f6a10bd8180800020014106360208200120003703102001411f6a200141086a10ab808080002102200141206a248080808000200241fd01710b7a01017f23808080800041206b2202248080808000200220013703000240200042ff01834204520d00200241086a2002411f6a2002109b8180800020022802084101460d0020022000422088a7200229031010f7808080003602082002200241086a10d7808080002100200241206a24808080800020000f0b000b990801087f2380808080004180036b2202248080808000200220013703000240108d8180800022030d00200241ff026a10bd81808000200241013602302002200036023420024190026a200241ff026a200241306a10af80808000024020022d00b60222034102460d002002280290022104200241086a41047220024190026a41047241221093828080001a200220033a002e20022004360208200220022d00b7023a002f200241086a10b2818080002002108e8180800022030d014108210320022d002e0d0141002103200241286a2204200229032010c88180800010ff818080002105024002400340024020052003470d00200220013703180240200228022841016a2203450d00200220033602282002200241ff026a10c5818080002201370340200241c8006a21062004200229032010c88180800010ff818080002107200241e9026a220841036a2109410021030340024020072003470d0020022001370320200241ff026a10bd8180800020024190026a41086a2203200241306a41086a22042903003703002002200229033037039002200241ff026a20024190026a200241086a10b380808000200241ff026a10bd81808000200320042903003703002002200229033037039002200241ff026a20024190026a418087014180f6de0010a980808000200220022802283602b401200220003602b0012002428ed4b9b3cebe03370398022002428ed4b1d4f9a60337039002200241ff026a200241ff026a20024190026a10d480808000200241ff026a200241b0016a10d98080800010c2818080001a410021030c080b4105210502402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e08080800020022802e40222054105460d06200241b0016a20024190026a41d4001093828080001a200220092800003600ab01200220082800003602a8010b200241d0006a200241b0016a41d4001093828080001a200220022800ab0136004b200220022802a801360248024020054105460d0020024190026a200241d0006a41d4001093828080001a2009200228004b36000020082002280248360000200241003a00e802200220053602e402200220062002290340200620024190026a10d28080800010c9818080002201370340200341016a21030c010b0b419884c08000108782808000000b418884c08000108c82808000000b02402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e080808000200341016a210320022802e402417d6a0e03020103010b0b41a884c08000108782808000000b410621030c020b000b410421030b20024180036a24808080800020030be70101017f23808080800041c0006b2204248080808000200420013703182004200037031020042002370320200441286a2004413f6a200441106a10b181808000024020042802284101460d0020042903302101200441286a2004413f6a200441186a10b18180800020042802284101460d0020042903302100200441286a2004413f6a200441206a109b8180800020042802284101460d00200342ff018342cb00520d00200441086a200120002004290330200310f980808000200420042903083702282004413f6a200441286a10d1808080002103200441c0006a24808080800020030f0b000bf01104077f027e037f017e23808080800041e0026b220524808080800020052002370320200520013703182005200337032820052004370330410121060240108d8180800022070d00200541186a10b2818080000240200541186a200541206a10b481808000450d00410f21070c010b0240200541386a2208200529033010c88180800010ff81808000450d0002402008200529033010c88180800010ff8180800041e4004d0d00411321070c020b200541286a108e8180800022070d01410021072008200529033010c88180800010ff81808000210920054188026a210a200541106a210b4200210c4200210d0340024002400240024020092007470d00200541df026a10bd818080002005200541df026a41b884c0800010ad808080004100210a02402005280204410020052802004101711b41016a220e450d002005200e36023c2005200541df026a10aa8180800037034020054188016a21062008200529033010c88180800010ff81808000210f02400340200f200a200f200a4b1b211003400240200a2010470d0041002107200541f3006a41003600002005410036027020052005290330370368200520033703602005200529032037035820052005290318370350200541df026a10bd81808000200541013602b8012005200e3602bc01200541df026a200541b8016a200541d0006a10b380808000200541df026a10bd81808000200541e0016a41086a2206200541b8016a41086a290300370300200520052903b8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541023602c8012005200e3602cc01200541df026a200541c8016a41d884c0800010b780808000200541df026a10bd818080002006200541c8016a41086a290300370300200520052903c8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541df026a41b884c080002005413c6a10b180808000200541df026a10bd81808000200541df026a41b884c08000418087014180f6de0010a9808080002005200d3703f8012005200c3703f0012005200528023c3602e801200520052903183703e0012005428ed2eadca9bda3013703c8022005428ef8f49b8ad7023703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10ce8080800010c2818080001a2008200529033010c88180800010ff81808000210620054188026a21090340024020062007470d00200528023c2107410021060c0d0b02402008200529033010c88180800010ff8180800020074d0d00200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b4024105460d0820052903a002210220052903a80221012005290390022104200529039802210320052903e001210d20052903e801210c2005290380022111200520092903003703f801200520113703f0012005200c3703e8012005200d3703e0012005200528023c3602980220052003370388022005200437038002200520013703a00220052002370390022005200736029c022005428ed2a9133703c8022005428ef2b3d5ecb7d6013703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10d68080800010c2818080001a200741016a21070c010b0b41e084c08000108782808000000b2008200529033010c88180800010ff81808000200a4d0d02200520082005290330200a10fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105460d05200a41016a210b200520052903980237034841002107024003400240200a2007470d00200542003703c802200542003703c002410021072008200529033010c88180800010ff81808000210a024003400240200a2007470d002005200541df026a200541c8006a10d1818080003703e001200541e0016a200541186a200541c0006a200541c0026a10d281808000200b210a0c070b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703d801200541e0016a2008200541d8016a10e08080800020052802b40222094105460d0a200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801024002402006200541c8006a10b481808000450d0020052903c80222022005290358220185427f852002200220017c20052903c002220120052903507c2204200154ad7c220185834200530d01200520043703c002200520013703c8020b200741016a21070c010b0b419085c08000108c82808000000b418085c08000108782808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b40222094105460d07200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801200741016a21072006200541c8006a10b481808000450d000b200b210a0c010b0b0b41a085c08000108782808000000b41f084c08000108782808000000b41c884c08000108c82808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105470d020b000b41b085c08000108782808000000b4101210620052903e00122045020052903e80122024200532002501b0d01200529038002221150200a29030022014200532001501b0d01024020052903a80220052903a002560d00411221070c030b200541086a200420022011200110948280800002402005290308200b290300844200510d00411121070c030b0240200d200285427f85200d200d20027c200c20047c2202200c54ad7c220185834200530d00200741016a21072002210c2001210d0c010b0b41c085c08000108c82808000000b410721070b2000200736020420002006360200200541e0026a2480808080000bfd0101017f23808080800041d0006b22052480808080002005200337031020052002370308200520043703180240200042ff01834204520d00200142ff01834204520d00200541206a200541cf006a200541086a10928180800020052802204101460d00200541386a290300210320052903302102200541206a200541cf006a200541106a10a68080800020052802204101460d0020052903282104200541206a200541cf006a200541186a10c18080800020052802204101460d0020052000422088a72001422088a7200220032004200529032810fb808080003602202005200541206a10d7808080002100200541d0006a24808080800020000f0b000bef0801047f23808080800041d0026b2206248080808000200620053703200240108d8180800022070d00200641cf026a10bd818080002006410136025020062000360254200641d0016a200641cf026a200641d0006a10af808080000240024020062d00f60122074102460d0020062802d0012108200641286a410472200641d0016a41047241221093828080001a20062008360228200620062d00f7013a004f200620073a004e02402007410171450d00410821070c030b4101210720062d004c0d0220062d004d0d02200641c8006a2208200629034010c88180800010ff8180800020014b0d010b410421070c010b024002402008200629034010c88180800010ff8180800020014d0d00200620082006290340200110fe8180800010c7818080003703b002200641d0016a2008200641b0026a10e08080800020062802a40222074105470d01000b41d085c08000108782808000000b200641e0006a200641d0016a41d4001093828080001a200620073602b401200620062903a8023703b8012006410036021c200641086a2002200320062903800120064188016a2903002006411c6a1091828080000240200628021c450d00410721070c010b02402006290308200629036085200641106a290300200629036885844200510d00411121070c010b2006200641cf026a20002001200641e0006a2002200320041090818080003703c801200641cf026a10bd81808000200641cf026a200641386a200641c8016a200641206a10b981808000200641cf026a10bd81808000200641023602b002200620003602b402200641d0016a200641cf026a200641b0026a10ae808080004109210720062903d801420020062802d0011b2004520d0002402004427f520d00410e21070c010b2006200442017c3703c002200641cf026a10bd81808000200641d0016a41086a2207200641b0026a41086a2209290300370300200620062903b0023703d001200641cf026a200641d0016a200641c0026a10b780808000200641cf026a10bd8180800020072009290300370300200620062903b0023703d001200641cf026a200641d0016a418087014180f6de0010a9808080002006200337037820062002370370200641013a00b801200641d0016a200641e0006a41e0001093828080001a200620082006290340200110fe818080002008200641d0016a10d28080800010c681808000370340200641cf026a10bd818080002007200641d0006a41086a2208290300370300200620062903503703d001200641cf026a200641d0016a200641286a10b380808000200641cf026a10bd8180800020072008290300370300200620062903503703d001200641cf026a200641d0016a418087014180f6de0010a980808000200620033703e801200620023703e001200620013602d401200620003602d0012006428ef2aef9a9c7033703b8022006428ef0b79ddd053703b002200641cf026a200641cf026a200641b0026a10d480808000200641cf026a200641d0016a10d08080800010c2818080001a410021070b200641d0026a24808080800020070b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710fd8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbb0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a10b281808000024020012d002c450d00410121020c020b200141013a002c200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428eeeaad6b9b6ca013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710ff8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbe0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a41086a10b281808000024020012d002d450d00410121020c020b200141013a002d200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428ed4e8d9b9f6ae013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b4b01017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a71081818080002001200110cf808080002100200141106a24808080800020000bd20a04047f017e047f057e23808080800041b0026b22022480808080000240024002400240108d8180800022030d00200241af026a10bd818080002002410136023020022001360234200241b0016a200241af026a200241306a10af8080800020022d00d60122034102460d0120022802b0012104200241086a410472200241b0016a41047241221093828080001a20022004360208200220022d00d7013a002f200220033a002e02402003410171450d00410821030c030b200241086a10b2818080000240200241086a200241106a10b481808000450d00410f21030c030b4105210320022d002c4101470d0220022d002d4101470d0241002104200241286a2203200229032010c88180800010ff818080002105024002400340024020052004470d002002200241af026a10aa818080003703402002200241af026a10c5818080002206370348200241d0006a210720024180016a210820024188016a2109410021042003200229032010c88180800010ff818080002105200241e8006a210a4200210b4200210c02400340024020052004470d0020022006370320200241af026a10bd81808000200241b0016a41086a2203200241306a41086a2204290300370300200220022903303703b001200241af026a200241b0016a200241086a10b380808000200241af026a10bd8180800020032004290300370300200220022903303703b001200241af026a200241b0016a418087014180f6de0010a9808080002007200229034810c88180800010ff8180800021032002200c3703b8012002200b3703b001200220033602c401200220013602c0012002428ee2e6d9bb053703582002428ef2b3d5ecb7d601370350200241af026a200241af026a200241d0006a10d480808000200241af026a200241b0016a10da8080800010c2818080001a20002002290348370308200041003602000c0a0b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c78180800037039802200241b0016a200320024198026a10e0808080002002280284024105460d05200241d0006a200241b0016a41d4001093828080001a200241033602a40120022002290388023703a8012002200241af026a200910d1818080003703b001200241b0016a200241c0006a2008200241d0006a10d2818080000240200c2002290358220685427f85200c200c20067c200b2002290350220d7c220e200b54ad7c220f85834200530d00200220022903603703e0012002200d3703b001200220013602d00120022002290388013703c80120022002290380013703c001200220063703b8012002200a2903003703e801200220043602d4012002428ed2aeb30d3703a0022002428ef2b3d5ecb7d60137039802200241af026a200241af026a20024198026a10d480808000200241af026a200241b0016a10d88080800010c2818080001a200241b0016a200241d0006a41e0001093828080001a2002200720022903482007200241b0016a10d28080800010c9818080002206370348200441016a2104200e210b200f210c0c010b0b41f085c08000108c828080000c040b41e085c08000108782808000000b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c781808000370350200241b0016a2003200241d0006a10e08080800020022802840222074105460d02024020074103460d00200441016a210420022d0088024101710d010b0b410d410620074103471b21030c040b418086c08000108782808000000b000b20004101360200200020033602040c020b410421030b20004101360200200020033602040b200241b0026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710838180800036020c20012001410c6a10d7808080002100200141106a24808080800020000b8b0d04087f017e017f027e23808080800041c0026b2201248080808000200141bf026a10bd818080002001410136023020012000360234200141d0016a200141bf026a200141306a10af808080000240024020012d00f60122024102460d0020012802d0012103200141086a410472200141d0016a41047241221093828080001a20012003360208200120012d00f7013a002f200120023a002e4108210320024101710d01200141086a10b28180800041002102200141286a2203200129032010c88180800010ff818080002104024002400340024020042002470d002001200141bf026a10aa8180800037034020014198016a2104410021052003200129032010c88180800010ff8180800021060240034020062005200620054b1b2107034020052108024020082007470d00200141013a002e2001200141bf026a10c58180800022093703c801200141d0016a2104410021022003200129032010c88180800010ff81808000210a03400240200a2002470d0020012009370320200141bf026a10bd81808000200141d0016a41086a200141306a41086a290300370300200120012903303703d001200141bf026a200141d0016a200141086a10b380808000200120003602602001428ee2aaf4ecc4023703d8012001428ef8f49b8ad7023703d001200141bf026a200141bf026a200141d0016a10d480808000200141e0006a200141bf026a10a78180800010c2818080001a410021030c0b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d08200141e0006a200141d0016a41d4001093828080001a20012903a8022109200120003602502001428ee2aaf4ecc4023703d8012001428ef2b3d5ecb7d6013703d00120012002360254200141bf026a200141bf026a200141d0016a10d480808000200141bf026a200141d0006a10d98080800010c2818080001a200141d0016a200141e0006a41d4001093828080001a200120093703a802200141043602a4022001200420012903c8012004200141d0016a10d28080800010c98180800022093703c801200241016a21020c010b0b419086c08000108782808000000b2003200129032010c88180800010ff8180800020084d0d02200120032001290320200810fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d05200841016a21052001200129038802370348410021020340024020082002470d002001420037035820014200370350410021022003200129032010c88180800010ff8180800021080340024020082002470d002001290350420052200129035822094200552009501b450d052001200141bf026a200141c8006a10d1818080003703d001200141d0016a200141c0006a200141086a200141d0006a10d2818080000c050b024002402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c7818080003703c801200141d0016a2003200141c8016a10e08080800020012802a402220a4105470d010c0a0b41b086c08000108782808000000b200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b80102402004200141c8006a10b481808000450d000240200129035822092001290368220b85427f8520092009200b7c2001290350220b20012903607c220c200b54ad7c220b85834200530d002001200c3703502001200b3703580c010b41c086c08000108c82808000000b200241016a21020c000b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370350200141d0016a2003200141d0006a10e08080800020012802a402220a4105460d07200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b801200241016a21022004200141c8006a10b481808000450d010c020b0b0b0b41d086c08000108782808000000b41a086c08000108782808000000b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e080808000200241016a210220012802a402417d6a0e03030102010b0b41e086c08000108782808000000b000b410621030c010b410421030b200141c0026a24808080800020030b4e01017f23808080800041306b22012480808080000240200042ff01834204510d00000b20012000422088a71085818080002001412f6a200110de808080002100200141306a24808080800020000b7a01017f23808080800041c0006b22022480808080002002413f6a10bd81808000200241013602282002200136022c20022002413f6a200241286a10af808080000240024020022d00264102470d00200041023a0026200041043602000c010b2000200241281093828080001a0b200241c0006a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710878180800036020c20012001410c6a10d7808080002100200141106a24808080800020000bd70201027f23808080800041306b22012480808080002001412f6a10bd8180800020014101360200200120003602044104210202402001412f6a200110b080808000450d002001412f6a10be8180800021022001412f6a10bd81808000200141106a41086a200141086a290300370300200120012903003703102001412f6a200141106a2002200210a9808080002001412f6a10bd8180800020014102360210200120003602142001412f6a200141106a2002200210a9808080002001412f6a10bd818080002001412f6a41b884c080002002200210a9808080002001412f6a10bd818080002001412f6a2002200210c18180800020012002360228200120003602242001428ee2f91c3703182001428ef8f49b8ad7023703102001412f6a2001412f6a200141106a10d4808080002001412f6a200141246a10d98080800010c2818080001a410021020b200141306a24808080800020020bcb0101017f23808080800041c0006b220424808080800020042003370308200420023703000240200042ff01834204520d00200142ff01834204520d00200441106a2004413f6a200410928180800020042802104101460d00200441286a290300210320042903202102200441106a2004413f6a200441086a10a68080800020042802104101460d00200441106a2000422088a72001422088a72002200320042903181089818080002004413f6a200441106a10dc808080002100200441c0006a24808080800020000f0b000b970301037f2380808080004180026b2206248080808000200641ff016a10bd81808000200641013602302006200136023420064190016a200641ff016a200641306a10af808080000240024020062d00b60122074102460d002006280290012108200641086a41047220064190016a41047241221093828080001a200620073a002e20062008360208200620062d00b7013a002f0240200641286a2207200629032010c88180800010ff8180800020024d0d00024002402007200629032010c88180800010ff8180800020024d0d00200620072006290320200210fe8180800010c78180800037033020064190016a2007200641306a10e08080800020062802e40122074105470d01000b41f086c08000108782808000000b200641306a20064190016a41d4001093828080001a2006200736028401200620062903e80137038801200641ff016a20012002200641306a200320042005109081808000210420004100360200200020043703080c020b20004281808080c0003703000c010b20004281808080c0003703000b20064180026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a7108b818080003703002001410f6a200110bb808080002100200141106a24808080800020000b6502017f017e23808080800041306b22012480808080002001412f6a10bd81808000200141023602082001200036020c200141186a2001412f6a200141086a10ae808080002001280218210020012903202102200141306a2480808080002002420020001b0b860102027f017e23808080800041206b22012480808080002001411f6a10bd81808000200141086a2001411f6a41d083c0800010bd80808000410121020240024020012802084101470d00200120012903102203370300200110b28180800020002003370308410021020c010b2000410a3602040b20002002360200200141206a2480808080000b4901027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000410b4100200141fd01711b0b7f01027f23808080800041206b22012480808080002001411f6a10bd818080004100210202402001411f6a41d083c0800010be80808000450d002001411f6a10bd818080002001410636020820012000290300370310410041102001411f6a200141086a10ab8080800041fd01711b21020b200141206a24808080800020020b4d02017f017e23808080800041106b2202248080808000200010bd8180800020022001290300200010cc808080003703002002410f6a200210b8818080002103200241106a24808080800020030b8b0f04017f017e087f017e23808080800041e0006b22072480808080002007200010cf818080002208370300200741086a21092007200920082009200810d08180800010ff8180800010fe81808000418184c08000410410ac81808000220837030020074180043b01382007200920082009200810d08180800010ff8180800010fe81808000200741386a410210ac818080003703002007200741df006a10bc81808000370330200741386a41186a220a4200370300200741386a41106a220b4200370300200741386a41086a220c420037030020074200370338200741306a41086a220d200741306a10d4818080004204200741386a412010ad81808000200741106a41186a220e200a290300370300200741106a41106a220f200b290300370300200741106a41086a2210200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac818080003703002007200010aa8180800037033020072000200741306a108f81808000370308200a4200370300200b4200370300200c420037030020074200370338200741086a41086a200741086a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341306a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341386a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800022083703002007200141187420014180fe03714108747220014108764180fe0371200141187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac8180800022083703002007200241187420024180fe03714108747220024108764180fe0371200241187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac81808000221137030020072003290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703402007200341086a290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703382007200920112009201110d08180800010ff8180800010fe81808000200741386a411010ac8180800022083703002007200442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703402007200542388620054280fe0383422886842005428080fc0783421886200542808080f80f834208868484200542088842808080f80f832005421888428080fc07838420054228884280fe038320054238888484843703382007200920082009200810d08180800010ff8180800010fe81808000200741386a411010ac81808000220537030020072003290340220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac81808000220537030020072003290348220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac8180800022043703002007200642388620064280fe0383422886842006428080fc0783421886200642808080f80f834208868484200642088842808080f80f832006421888428080fc07838420064228884280fe03832006423888848484370338200920042009200410d08180800010ff8180800010fe81808000200741386a410810ac818080002104200741e0006a24808080800020040b190020004200370300200020023502004220864204843703080b7c01027e024002400240024020022903002203a741ff0171220241c500460d002002410b470d02200041106a20031080828080000c010b2001200310e58180800021042001200310e481808000210320002004370318200020033703100b420021030c010b200010f981808000370308420121030b200020033703000b130020004200370300200020023100003703080b4602017f017e23808080800041106b2203248080808000200320012002109581808000200329030821042000200329030037030020002004370308200341106a2480808080000b6d02017f027e23808080800041106b2203248080808000200320022903002204200241086a29030022051082828080000240024020032802000d00200329030821040c010b20012005200410e38180800021040b2000420037030020002004370308200341106a2480808080000b4b00200041003602102000200436020c2000200336020820002002360204200020013602002000200220016b410376220236021820002002200420036b410376220420022004491b3602140b3901017f23808080800041106b22032480808080002003200229020037020820002001200341086a109881808000200341106a2480808080000b6a02027f017e23808080800041106b22032480808080002003200228020022042002280204220210fa818080000240024020032802000d00200329030821050c010b20012004200210d78180800021050b2000420037030020002005370308200341106a2480808080000b5202017f017e23808080800041106b2203248080808000200320022903083703082003200229030037030020012003410210da8180800021042000420037030020002004370308200341106a2480808080000b0e00200020012001109b818080000b7d02017f027e23808080800041106b2203248080808000024002402002290300220442ff018342c800520d0020032004370308420121050240200341106a200410f58180800010ff818080004120470d0020002003290308370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b2e01027e4201210302402001290300220442ff018342cd00520d0020002004370308420021030b200020033703000b0e002000200220011099818080000b130020004200370300200020012903003703080b5102017f017e23808080800041106b220324808080800020032001200210978180800042012104024020032802000d0020002003290308370308420021040b20002004370300200341106a2480808080000b130020004200370300200020012903003703080b130020004200370300200020012903003703080b1200200141bb87c08000410f108b828080000b0300000b02000b4502017f017e23808080800041106b2202248080808000200220002001109481808000024020022802004101470d00000b20022903082103200241106a24808080800020030b070020003100000b0d0020003502004220864204840b070020002903000b070020002903000b0a00200010df818080000b6001017f23808080800041106b22042480808080000240200020012903002002290300200310f28180800042ff01834202510d00419087c08000412b2004410f6a418087c0800041b088c08000108682808000000b200441106a2480808080000b12002000200120022003200410d5818080000b12002000200120022003200410d6818080000b12002000200120022003200410d8818080000b140020002001200220032004200510d9818080000b0e0020002001200210da818080000b2e01027e4201210302402002290300220442ff018342cd00520d0020002004370308420021030b200020033703000b1300200041086a200029030010f8818080001a0b5902017f017e23808080800041206b22032480808080002003200236020c20032001360208200341106a2000200341086a109781808000024020032802104101470d00000b20032903182104200341206a24808080800020040b11002000200110b58180800041ff0171450b2601017e417f200041086a2000290300200129030010db81808000220242005220024200531b0b2e01027e4201210302402002290300220442ff018342c800520d0020002004370308420021030b200020033703000b130020004200370300200020022903003703080b0f002000200129030010f6818080000b1a00200020012903002002290300200329030010f7818080001a0b1000200010dd8180800010ff818080000b1000200010e08180800010ff818080000b0a00200010de818080000b02000b6d01037f23808080800041106b22012480808080002001410f6a10ba818080002102024002402001410f6a10bb8180800022032002490d00200320026b41016a22020d0141a889c08000108c82808000000b41a889c08000108d82808000000b200141106a24808080800020020b140020002001200210ec8180800010fd818080000b0e0020002001200210ed818080000b1b002000200110fe81808000200210fe8180800010f1818080001a0b0e0020002001200210dc818080000b0c002000200110e1818080000b0c002000200110e2818080000b0a00200010e6818080000b1000200020012002200310e7818080000b0e0020002001200210e8818080000b0c002000200110e9818080000b0e0020002001200210ea818080000b1000200020012002200310eb818080000b0e0020002001200210ee818080000b0c002000200110ef818080000b12002000200120022003200410f0818080000b0c002000200110f3818080000b0a00200010f4818080000b0c002000200110f5818080000b070020012903000bdf0102027f027e23808080800041c0006b22042480808080002004200041086a220541b889c08000410810b381808000370308200129030021062002290300210720042005200310a5818080003703202004200737031820042006370310410021010340024020014118470d00410021010240034020014118460d01200441286a20016a200441106a20016a290300370300200141086a21010c000b0b20052000200441086a2005200441286a410310da8180800010ab81808000200441c0006a2480808080000f0b200441286a20016a4202370300200141086a21010c000b0b130020004200370300200020022903003703080b070020002903000b1e00200120022003ad4220864204842004ad4220864204841080808080000b1f00200120022003ad4220864204842004ad4220864204841081808080001a0b1a002001ad4220864204842002ad4220864204841082808080000b2e00024020022004460d00000b2001ad4220864204842003ad4220864204842002ad4220864204841083808080000b3000024020032005460d00000b20012002ad4220864204842004ad4220864204842003ad4220864204841084808080000b1a002001ad4220864204842002ad4220864204841085808080000b0c00200120021086808080000b0c00200120021087808080000b08001088808080000b08001089808080000b0800108a808080000b0800108b808080000b0a002001108c808080000b0a002001108d808080000b0c0020012002108e808080000b0a002001108f808080000b0a0020011090808080000b08001091808080000b0e002001200220031092808080000b0c00200120021093808080000b0a0020011094808080000b0c00200120021095808080000b0e002001200220031096808080000b0c00200120021097808080000b0c00200120021098808080000b0c00200120021099808080000b0a002001109a808080000b10002001200220032004109b808080000b0c0020012002109c808080000b0e00200120022003109d808080000b0a002001109e808080000b0800109f808080000b0a00200110a0808080000b0a00200110a1808080000b0e0020012002200310a2808080000b0a00200110a3808080000b0900428390808080010bb50102017f017e23808080800041106b220324808080800002400240200241094b0d00420021040340024020020d002000410036020020002004420886420e843703080c030b200341086a20012d000010fb81808000024020032d00084103460d0020002003290308370204200041013602000c030b200141016a21012002417f6a2102200442068620033100098421040c000b0b20002002360208200041003a0004200041013602000b200341106a2480808080000b820101017f410121020240200141ff017141df00460d000240200141506a41ff0171410a490d000240200141bf7f6a41ff0171411a490d0002402001419f7f6a41ff0171411a490d00200020013a0001200041013a00000f0b200141456a21020c020b2001414b6a21020c010b200141526a21020b200041033a0000200020023a00010b070020004208880b070020004201510b0b002000ad4220864204840b08002000422088a70b160020002001423f87370308200020014208873703000b3201017e420121020240200142ffffffffffffffff00560d0020002001420886420684370308420021020b200020023703000b5001017e42012103024020014280808080808080c0007c42ffffffffffffffff00560d0020012001852001423f87200285844200520d0020002001420886420b84370308420021030b200020033703000ba00601067f0240200028020022032000280208220472450d0002402004410171450d00200120026a210502400240200028020c22060d0041002107200121080c010b41002107200121080340200822042005460d020240024020042c00002208417f4c0d00200441016a21080c010b0240200841604f0d00200441026a21080c010b0240200841704f0d00200441036a21080c010b200441046a21080b200820046b20076a21072006417f6a22060d000b0b20082005460d00024020082c00002204417f4a0d0020044160491a0b024002402007450d00024020072002490d0020072002460d01410021040c020b200120076a2c000041404e0d00410021040c010b200121040b2007200220041b21022004200120041b21010b024020030d00200028021c20012002200028022028020c118080808000000f0b200028020421030240024020024110490d0020012002108a8280800021040c010b024020020d00410021040c010b2002410371210602400240200241044f0d0041002104410021070c010b2002410c712105410021044100210703402004200120076a22082c000041bf7f4a6a200841016a2c000041bf7f4a6a200841026a2c000041bf7f4a6a200841036a2c000041bf7f4a6a21042005200741046a2207470d000b0b2006450d00200120076a21080340200420082c000041bf7f4a6a2104200841016a21082006417f6a22060d000b0b02400240200320044d0d00200320046b2106024002400240410020002d0018220420044103461b22040e03020001020b20062104410021060c010b20064101762104200641016a41017621060b200441016a21042000280210210720002802202108200028021c210003402004417f6a2204450d0220002007200828021011818080800000450d000b41010f0b200028021c20012002200028022028020c118080808000000f0b0240200020012002200828020c11808080800000450d0041010f0b410021040340024020062004470d0020062006490f0b200441016a210420002007200828021011818080800000450d000b2004417f6a2006490f0b200028021c20012002200028022028020c118080808000000b4d01017f23808080800041206b22032480808080002003410036021020034101360204200342043702082003200136021c200320003602182003200341186a36020020032002108582808000000b3601017f23808080800041106b2202248080808000200241013b010c2002200136020820022000360204200241046a10a381808000000b8f0101017f23808080800041c0006b22052480808080002005200136020c2005200036020820052003360214200520023602102005410236021c200541c08ac08000360218200542023702242005418280808000ad422086200541106aad843703382005418380808000ad422086200541086aad843703302005200541306a360220200541186a2004108582808000000b130041908ac08000412b2000108482808000000b14002001200028020020002802041083828080000b180020002802002001200028020428020c118180808000000be90601087f024002402001200041036a417c71220220006b2203490d00200120036b22044104490d002004410371210541002106410021010240200220004622070d004100210102400240200020026b2208417c4d0d00410021090c010b4100210903402001200020096a22022c000041bf7f4a6a200241016a2c000041bf7f4a6a200241026a2c000041bf7f4a6a200241036a2c000041bf7f4a6a2101200941046a22090d000b0b20070d00200020096a21020340200120022c000041bf7f4a6a2101200241016a2102200841016a22080d000b0b200020036a210002402005450d0020002004417c716a22022c000041bf7f4a210620054101460d00200620022c000141bf7f4a6a210620054102460d00200620022c000241bf7f4a6a21060b20044102762108200620016a21030340200021042008450d02200841c001200841c001491b220641037121072006410274210541002102024020084104490d002004200541f007716a210941002102200421010340200128020c2200417f7341077620004106767241818284087120012802082200417f7341077620004106767241818284087120012802042200417f7341077620004106767241818284087120012802002200417f7341077620004106767241818284087120026a6a6a6a2102200141106a22012009470d000b0b200820066b2108200420056a2100200241087641ff81fc0771200241ff81fc07716a418180046c41107620036a21032007450d000b2004200641fc01714102746a22022802002201417f734107762001410676724181828408712101024020074101460d0020022802042200417f7341077620004106767241818284087120016a210120074102460d0020022802082202417f7341077620024106767241818284087120016a21010b200141087641ff811c71200141ff81fc07716a418180046c41107620036a0f0b024020010d0041000f0b2001410371210902400240200141044f0d0041002103410021020c010b2001417c712108410021034100210203402003200020026a22012c000041bf7f4a6a200141016a2c000041bf7f4a6a200141026a2c000041bf7f4a6a200141036a2c000041bf7f4a6a21032008200241046a2202470d000b0b2009450d00200020026a21010340200320012c000041bf7f4a6a2103200141016a21012009417f6a22090d000b0b20030b1a00200028021c20012002200028022028020c118080808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141dc89c0800036020820014204370210200141086a2000108582808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141888ac0800036020820014204370210200141086a2000108582808000000b5701017e02400240200341c000710d002003450d012002410020036b413f71ad8620012003413f71ad220488842101200220048821020c010b20022003413f71ad882101420021020b20002001370300200020023703080bf60804017f017e037f047e23808080800041b0016b2205248080808000420021060240024002400240024020047920037942c0007c20044200521ba7220720027920017942c0007c20024200521ba722084d0d002008413f4b0d01200741df004b0d02200720086b4120490d03200541a0016a2003200441e00020076b2209108e8280800020053502a00142017c210a4200210b420021060240024002400240034020054190016a2001200241c00020086b2208108e82808000200529039001210c0240200820094f0d00200541d0006a200320042008108e82808000024002402005290350220a50450d000c010b200c200a80210c0b200541c0006a200c420020032004109282808000024020012005290340220d5422082002200541c8006a290300220a542002200a511b0d002002200a7d2008ad7d21022001200d7d21012006200b200c7c220c200b54ad7c21060c0b0b200220047c200120037c2204200154ad7c200a7d2004200d54ad7d21022004200d7d21012006200c200b7c427f7c220c200b54ad7c21060c0a0b20054180016a200c200a80220c4200200820096b41ff00712208109082808000200541f0006a200c420020032004109282808000200541e0006a2005290370200541f0006a41086a290300200810908280800020054180016a41086a29030020067c2005290380012206200b7c220b200654ad7c210620072002200541e0006a41086a2903007d20012005290360220c54ad7d2202792001200c7d22017942c0007c20024200521ba722084d0d012008413f4d0d000b200350450d010c020b20012003542208200220045420022004511b450d02200b210c0c070b200120038021020b200120038221012006200b20027c220c200b54ad7c2106420021020c050b200220047d2008ad7d2102200120037d21012006200b42017c220c50ad7c21060c040b200220044200200120035a200220045a20022004511b22081b7d20012003420020081b220454ad7d2102200120047d21012008ad210c0c030b20012001200380220c20037e7d210142002106420021020c020b20022002200342ffffffff0f83220480220620037e7d4220862001422088220c842004802202422086200c200220037e7d422086200142ffffffff0f83842201200480220384210c2001200320047e7d210120024220882006842106420021020c010b200541306a2003200441c00020086b2208108e82808000200541206a200120022008108e8280800042002106200541106a200342002005290320200529033080220c4200109282808000200520044200200c42001092828080002005290310210a02400240200541086a290300200541106a41086a290300220d20052903007c220b200d54ad7c4200520d002001200a5422082002200b542002200b511b450d010b200420027c200320017c2201200354ad7c200b7d2001200a54ad7d2102200c427f7c210c2001200a7d21010c010b2002200b7d2008ad7d21022001200a7d2101420021060b200020013703102000200c3703002000200237031820002006370308200541b0016a2480808080000b5701017e02400240200341c000710d002003450d0120022003413f71ad2204862001410020036b413f71ad88842102200120048621010c010b20012003413f71ad862102420021010b20002001370300200020023703080bf50303017f027e027f23808080800041e0006b220624808080800042002107420021084100210902402001200284500d002003200484500d00420020037d2003200442005322091b2107420020017d20012002420053220a1b2108420020042003420052ad7c7d200420091b21032004200285210402400240420020022001420052ad7c7d2002200a1b2202500d0002402003500d00200641d0006a2007200320082002109282808000200641d8006a290300210141012109200629035021020c020b200641c0006a2008420020072003109282808000200641306a2002420020072003109282808000200641c0006a41086a290300220220062903307c2201200254200641306a41086a290300420052722109200629034021020c010b02402003500d00200641206a2007420020082002109282808000200641106a2003420020082002109282808000200641206a41086a290300220220062903107c2201200254200641106a41086a290300420052722109200629032021020c010b20062007200320082002109282808000200641086a290300210141002109200629030021020b420020027d20022004420053220a1b2108420020012002420052ad7c7d2001200a1b22072004854200590d00410121090b200520093602002000200737030820002008370300200641e0006a2480808080000b6e01067e2000200342ffffffff0f832205200142ffffffff0f8322067e22072003422088220820067e22062005200142208822097e7c22054220867c220a3703002000200820097e2005200654ad4220862005422088847c200a200754ad7c200420017e200320027e7c7c3703080ba50501087f02400240200241104f0d00200021030c010b02402000410020006b41037122046a220520004d0d002004417f6a2106200021032001210702402004450d002004210820002103200121070340200320072d00003a0000200741016a2107200341016a21032008417f6a22080d000b0b20064107490d000340200320072d00003a0000200341016a200741016a2d00003a0000200341026a200741026a2d00003a0000200341036a200741036a2d00003a0000200341046a200741046a2d00003a0000200341056a200741056a2d00003a0000200341066a200741066a2d00003a0000200341076a200741076a2d00003a0000200741086a2107200341086a22032005470d000b0b2005200220046b2208417c7122066a210302400240200120046a22074103710d00200520034f0d0120072101034020052001280200360200200141046a2101200541046a22052003490d000c020b0b200520034f0d002007410374220241187121042007417c71220941046a2101410020026b411871210a2009280200210203402005200220047620012802002202200a7472360200200141046a2101200541046a22052003490d000b0b20084103712102200720066a21010b02402003200320026a22054f0d002002417f6a2108024020024107712207450d000340200320012d00003a0000200141016a2101200341016a21032007417f6a22070d000b0b20084107490d000340200320012d00003a0000200341016a200141016a2d00003a0000200341026a200141026a2d00003a0000200341036a200141036a2d00003a0000200341046a200141046a2d00003a0000200341056a200141056a2d00003a0000200341066a200141066a2d00003a0000200341076a200141076a2d00003a0000200141086a2101200341086a22032005470d000b0b20000b4b01017f23808080800041206b220524808080800020052001200220032004108f82808000200529031021042000200541186a29030037030820002004370300200541206a2480808080000b0bda0a0100418080c0000bd00a7372632f6c69622e7273616d6f756e74656e645f64617465686f7572735f6c6f67676564696470726f6f665f7665726966696564726174655f7065725f686f757273746172745f64617465737461747573746f6b656e776f726b65720a001000060000001000100008000000180010000c0000002400100002000000260010000e000000340010000d000000410010000a0000004b001000060000005100100005000000560010000600000063616e63656c6c656466696e616e63655f617070726f76656466696e616e63655f617070726f7665726d616e616765726d616e616765725f617070726f7665646f7261636c655f7075626b65796f7261636c655f726f746174696f6e737061796d656e7473000000ac00100009000000b500100010000000c500100010000000d500100007000000dc00100010000000ec0010000d000000f9001000100000000901100008000000457363726f77436f756e7400540110000b000000457363726f77000068011000060000004e6f6e6365000000780110000500000041646d696e000000880110000500000050656e64696e6741646d696e980110000c0000005061757365640000ac011000060000004f7261636c654b6579000000bc011000090000000300000000000000000000000000000004000000000000000000000000000000050000000000000000000000000000000143465750000000000010000a000000fd01000009000000000010000a0000000302000030000000000010000a000000f70100002700000000000000000000000000000000000000000010000a000000500200001e0000000000000000000000000010000a000000b302000025000000000010000a0000005d0200002b000000000010000a0000006c02000029000000000010000a0000006e02000015000000000010000a0000006102000024000000000010000a0000003c02000025000000000010000a0000004b0200000d000000000010000a000000ea0200003b000000000010000a000000a503000030000000000010000a000000a80300000d000000000010000a000000950300002c000000000010000a0000001104000030000000000010000a000000ee03000032000000000010000a000000fd03000030000000000010000a000000ff03000015000000000010000a000000f20300002b000000000010000a000000e403000027000000000010000a0000008e040000370000000000000000000000010000000100000063616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c7565436f6e76657273696f6e4572726f722f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f656e762e7273000000ca03100063000000770100000e0000002f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f73746f726167652e72730040041000670000009a000000090000007472616e73666572617474656d707420746f206164642077697468206f766572666c6f77c00410001c000000617474656d707420746f2073756274726163742077697468206f766572666c6f77000000e40410002100000063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75653a2000000001000000000000003b05100002000000008f460e636f6e74726163747370656376300000000400000000000000000000000d436f6e74726163744572726f7200000000000016000000000000000f416c7265616479417070726f7665640000000001000000000000000c556e617574686f72697a6564000000020000000000000016496e76616c69644f7261636c655369676e61747572650000000000030000000000000010496e76616c69645061796d656e744964000000040000000000000015496e73756666696369656e74417070726f76616c730000000000000500000000000000175061796d656e74416c726561647946696e616c697a65640000000006000000000000000d496e76616c6964416d6f756e7400000000000007000000000000000f457363726f7743616e63656c6c65640000000008000000000000000c496e76616c69644e6f6e63650000000900000000000000084e6f7441646d696e0000000a000000000000000650617573656400000000000b000000000000000f41646d696e416c7265616479536574000000000c000000000000000c50726f6f664d697373696e670000000d000000000000000d4e6f6e63654f766572666c6f770000000000000e00000000000000125369676e6572734e6f7444697374696e637400000000000f0000003b546865206f7261636c65207075626c6963206b6579206973206e6f74206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000164f7261636c654b65794e6f745265676973746572656400000000001000000042417474657374656420686f757273207820726174655f7065725f686f757220646f6573206e6f7420657175616c2074686520657363726f77656420616d6f756e742e000000000013416d6f756e74486f7572734d69736d6174636800000000110000002a656e645f64617465206973206e6f74207374726963746c792061667465722073746172745f646174652e00000000000d496e76616c6964506572696f64000000000000120000002642617463682065786365656473204d41585f42415443485f53495a45207061796d656e74732e00000000000d4261746368546f6f4c61726765000000000000130000004454686973205741534d2070696e7320616e2065787065637465642061646d696e20616e642074686520737570706c6965642061646472657373206973206e6f742069742e0000000d41646d696e4d69736d6174636800000000000014000000464e6f2061646d696e207472616e736665722069732070656e64696e672c206f72207468652063616c6c6572206973206e6f74207468652070726f706f7365642061646d696e2e00000000000e4e6f50656e64696e6741646d696e000000000015000000336075706772616465602072657175697265732074686520636f6e747261637420746f206265207061757365642066697273742e00000000094e6f74506175736564000000000000160000000300000000000000000000000d5061796d656e7453746174757300000000000005000000000000000750656e64696e670000000000000000000000000f4d616e61676572417070726f7665640000000001000000000000000f46696e616e6365417070726f7665640000000002000000000000000946696e616c697a656400000000000003000000000000000943616e63656c6c6564000000000000040000000100000000000000000000000f5061796d656e745363686564756c65000000000a0000000000000006616d6f756e7400000000000b0000000000000008656e645f6461746500000006000000000000000c686f7572735f6c6f676765640000000b00000000000000026964000000000004000000815365742074727565206f6e6c7920627920607375626d69745f686f7572735f70726f6f666020616674657220612076616c69642045643235353139206f7261636c650a7369676e61747572652e20607061795f626174636860207265667573657320746f20736574746c652061207061796d656e7420776974686f75742069742e0000000000000e70726f6f665f7665726966696564000000000001000000000000000d726174655f7065725f686f75720000000000000b000000000000000a73746172745f6461746500000000000600000000000000067374617475730000000007d00000000d5061796d656e745374617475730000000000008c5065722d7061796565205374656c6c617220417373657420436f6e7472616374202853414329206164647265737320e2809420652e672e2074686520555344432053414320666f720a6f6e6520706179656520616e6420746865206e617469766520584c4d2053414320666f7220616e6f746865722077697468696e207468652073616d652062617463682e00000005746f6b656e000000000000130000000000000006776f726b65720000000000130000000100000000000000000000000e436f7265466c6f77457363726f77000000000008000000000000000963616e63656c6c656400000000000001000000000000001066696e616e63655f617070726f76656400000001000000000000001066696e616e63655f617070726f7665720000001300000000000000076d616e61676572000000001300000000000000106d616e616765725f617070726f76656400000001000000000000000d6f7261636c655f7075626b6579000000000003ee000000200000004354696d657320746865206f7261636c65206b657920686173206265656e20726f7461746564206f6e207468697320657363726f772028617564697420747261696c292e00000000106f7261636c655f726f746174696f6e730000000400000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000002000000000000000000000007446174614b6579000000000700000000000000000000000b457363726f77436f756e7400000000010000000000000006457363726f77000000000001000000040000000100000000000000054e6f6e6365000000000000010000000400000000000000000000000541646d696e000000000000000000003d50726f706f736564206e6578742061646d696e2c206177616974696e6720616363657074616e6365202874776f2d737465702068616e646f766572292e0000000000000c50656e64696e6741646d696e0000000000000000000000065061757365640000000000010000004a52656769737465726564206f7261636c65207369676e696e67206b6579732e2050726573656e6365203d3e20747275737465642062792074686520706c6174666f726d2061646d696e2e0000000000094f7261636c654b657900000000000001000003ee0000002000000000000000c85365742074686520636f6e74726163742061646d696e206f6e63652c20696d6d6564696174656c79206166746572206465706c6f792e204964656d706f74656e742d67756172643a0a6661696c7320696620616e2061646d696e20697320616c726561647920636f6e666967757265642e204966206e657665722063616c6c65642c2074686520636f6e74726163740a73696d706c7920686173206e6f2061646d696e20616e642063616e206e6576657220626520706175736564206f722075706772616465642e0000000a696e69745f61646d696e000000000001000000000000000561646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000f85468652061646d696e20616464726573732062616b656420696e746f2074686973205741534d206174206275696c642074696d652c20696620616e792e0a0a526561642d6f6e6c792c20736f20616e206f70657261746f7220286f7220616e2061756469746f72292063616e20636f6e6669726d206166746572206465706c6f7920746861740a7468652072756e6e696e6720636f64652069732070696e6e656420746f20746865206b65792074686579206578706563742c20726174686572207468616e207472757374696e670a7468617420746865206465706c6f7920736372697074207761732072756e20636f72726563746c792e0000000e65787065637465645f61646d696e00000000000000000001000003e800000013000000000000010d50726f706f73652061206e65772061646d696e202863757272656e742061646d696e206f6e6c79292e20537465702031206f6620322e0a0a48616e646f7665722069732074776f2d73746570206265636175736520612073696e676c652d73746570207472616e7366657220746f2061206d69737479706564206f720a756e636f6e74726f6c6c65642061646472657373207065726d616e656e746c792064657374726f797320746865206162696c69747920746f2070617573652c20757067726164652c0a6f72206d616e61676520746865206f7261636c652072656769737472792e205468652070726f706f736564206b6579206d7573742070726f76652069742063616e207369676e2e0000000000000d70726f706f73655f61646d696e0000000000000100000000000000096e65775f61646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000004341636365707420612070656e64696e672061646d696e2068616e646f766572202870726f706f7365642061646d696e206f6e6c79292e20537465702032206f6620322e000000000c6163636570745f61646d696e0000000000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000345468652063757272656e746c7920636f6e666967757265642061646d696e2c206966206f6e6520686173206265656e207365742e000000096765745f61646d696e0000000000000000000001000003e80000001300000000000000865061757365206f7220756e70617573652073746174652d6368616e67696e67206f7065726174696f6e73202861646d696e206f6e6c79292e206063616e63656c5f657363726f77600a737461797320617661696c61626c65207768696c652070617573656420736f2066756e64732063616e20616c7761797320626520726566756e6465642e00000000000a7365745f706175736564000000000001000000000000000670617573656400000000000100000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000000000000969735f7061757365640000000000000000000001000000010000000000000076557067726164652074686520636f6e7472616374205741534d202861646d696e206f6e6c79292e20456e61626c657320666978657320776974686f7574206368616e67696e670a74686520636f6e74726163742061646472657373206f72206d6967726174696e6720657363726f772066756e64732e000000000007757067726164650000000001000000000000000d6e65775f7761736d5f68617368000000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000001e4526567697374657220616e206f7261636c65207369676e696e67206b657920617320747275737465642062792074686520706c6174666f726d202861646d696e206f6e6c79292e0a0a57485920412052454749535452593a2070726576696f75736c7920746865206d616e616765722070617373656420616e7920606f7261636c655f7075626b65796020746865790a6c696b656420696e746f2060696e697469616c697a655f6d756c74695f7369675f657363726f77602c20736f2061206d616e6167657220636f756c6420696e7374616c6c0a7468656972206f776e206b657920616e64207369676e207468656972206f776e2022766572696669656420776f726b22206174746573746174696f6e732e205468650a70726f6f662d6f662d776f726b206761746520776173207468657265666f7265206d616e616765722d61747465737461626c65202d2d2070726f6365647572616c2c206e6f740a63727970746f677261706869632e20457363726f7773206d6179206e6f77206f6e6c79206e616d652061206b6579207468652061646d696e2068617320726567697374657265642c0a7768696368206d616b657320746865206f7261636c6520616e20696e646570656e64656e7420706172747920627920636f6e737472756374696f6e2e0000001372656769737465725f6f7261636c655f6b6579000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019b5265766f6b6520612070726576696f75736c792072656769737465726564206f7261636c65206b6579202861646d696e206f6e6c79292e0a0a4578697374696e6720657363726f777320616c7265616479206e616d696e672074686973206b6579206b6565702066756e6374696f6e696e67202d2d207265766f6b696e672069730a6e6f7420726574726f6163746976652c20626563617573652073696c656e746c7920696e76616c69646174696e6720696e2d666c69676874206174746573746174696f6e730a776f756c6420737472616e642066756e64656420657363726f77732e2049742073746f707320746865206b6579206265696e67206e616d6564206279204e455720657363726f77730a616e64204e455720726f746174696f6e732e20546f207265746972652061206b65792066726f6d2061206c69766520657363726f772c20746865206d616e616765722063616c6c730a60726f746174655f6f7261636c655f6b6579602c207768696368207265766f6b6573207468617420657363726f7727732076657269666965642070726f6f66732e00000000117265766f6b655f6f7261636c655f6b65790000000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000325472756520696620607075626b657960206973206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000001869735f6f7261636c655f6b65795f726567697374657265640000000100000000000000067075626b65790000000003ee00000020000000010000000100000000000000d3526f7461746520746865206f7261636c65207075626c6963206b657920666f7220616e20657363726f772e205369676e6174757265732070726f6475636564206279207468650a72657469726564206b65792073746f7020766572696679696e6720696d6d6564696174656c792c2073696e636520607665726966795f6f7261636c655f776f726b602072656164730a746869732073746f726564206b65792e204d616e616765722d617574686f72697a65643b2072656675736564206f6e63652066756e64732068617665206d6f7665642e0000000011726f746174655f6f7261636c655f6b6579000000000000020000000000000009657363726f775f696400000000000004000000000000000a6e65775f7075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000a2496e697469616c697a652061206d756c74692d7369676e617475726520657363726f772077697468207061796d656e74207363686564756c657320616e64206f7261636c65207075626c6963206b65792e0a546865206f7261636c655f7075626b657920697320616e2045643235353139207075626c6963206b6579207573656420746f2076657269667920776f726b2070726f6f66207369676e6174757265732e00000000001b696e697469616c697a655f6d756c74695f7369675f657363726f77000000000400000000000000076d616e616765720000000013000000000000001066696e616e63655f617070726f76657200000013000000000000000d6f7261636c655f7075626b6579000000000003ee0000002000000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000001000003e900000004000007d00000000d436f6e74726163744572726f7200000000000000000001595375626d697420686f7572732070726f6f6620766572696669656420627920616e2045643235353139206f7261636c65207369676e61747572652e0a0a546865206f7261636c65207369676e7320746865203139382d6279746520646f6d61696e2d73657061726174656420707265696d61676520646f63756d656e746564206f6e0a606275696c645f70726f6f665f6d657373616765602028736368656d61207632292e2054686520636f6e74726163742072656275696c6473207468617420707265696d6167650a66726f6d2073746f7265642073746174652c20766572696669657320697420616761696e73742074686520657363726f772773206f7261636c65207075626c6963206b65792c0a656e666f726365732060686f75727320782072617465203d3d20616d6f756e74602c20616e6420636f6e73756d657320746865206e657874206578706563746564206e6f6e63652e000000000000127375626d69745f686f7572735f70726f6f660000000000050000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f6964000000000004000000000000000c686f7572735f6c6f676765640000000b00000000000000056e6f6e63650000000000000600000000000000097369676e6174757265000000000003ee0000004000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e4d616e6167657220617070726f76616c206f66207061796d656e7428732900000000000f6d616e616765725f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e46696e616e636520617070726f76616c206f66207061796d656e7428732900000000000f66696e616e63655f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000bb46696e616c697a65207061796d656e74206f6e636520626f746820617070726f76616c7320617265206f627461696e65640a4465707265636174656420616c6961732072657461696e656420736f20746865204d61696e6e65742d6465706c6f7965642041424920616e6420746865206578697374696e670a64617368626f61726420636c69656e74206b65657020776f726b696e672e204e65772063616c6c6572732073686f756c642075736520607061795f6261746368602e000000001066696e616c697a655f7061796d656e74000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f7200000000000000000000b2536574746c65206576657279207061796d656e7420696e2074686520657363726f773a206f6e65207472616e73616374696f6e2c206f6e6520534143207472616e73666572207065720a70617965652c206561636820696e20746861742070617965652773206f776e2061737365742e20526571756972657320626f746820617070726f76616c7320414e4420610a7665726966696564206f7261636c652070726f6f66206f6e20657665727920726f772e0000000000097061795f6261746368000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f72000000000000000000006e43616e63656c20616e20657363726f77202864697370757465207265736f6c7574696f6e20e28094206d616e61676572206f6e6c79292e0a416c6c6f776564206576656e207768696c65207061757365642028656d657267656e6379207769746864726177616c2070617468292e00000000000d63616e63656c5f657363726f77000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f720000000000000000000017526574726965766520657363726f772064657461696c73000000000a6765745f657363726f770000000000010000000000000009657363726f775f69640000000000000400000001000003e9000007d00000000e436f7265466c6f77457363726f770000000007d00000000d436f6e74726163744572726f720000000000000000000121457874656e6420616e20657363726f7727732073746f72616765206c69666574696d652e20416e796f6e65206d61792063616c6c20746869732e0a0a50657273697374656e7420656e747269657320746861742072756e206f7574206f662072656e742061726520617263686976656420746f2074686520457870697265640a537461746520537461636b20616e642063616e20626520726573746f7265643b207468657920617265206e6f742064656c657465642e20546865206661696c75726520746869730a61766f69647320697320612066756e64656420657363726f77206265636f6d696e672074656d706f726172696c7920756e757361626c6520756e74696c20736f6d656f6e650a7061797320746f20726573746f72652069742e00000000000011657874656e645f657363726f775f74746c000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019552657475726e2074686520657861637420627974657320746865206f7261636c65206d757374207369676e20666f722074686973207061796d656e742e0a0a526561642d6f6e6c792e204578706f73696e672074686520707265696d616765206d616b65732074686520434f4e5452414354207468652073696e676c6520736f75726365206f660a747275746820666f7220746865206d65737361676520666f726d61743a20616e206f66662d636861696e207369676e65722063616e2073696d756c61746520746869732063616c6c0a616e64207369676e207468652072657475726e656420627974657320766572626174696d20696e7374656164206f66207265696d706c656d656e74696e6720746865206c61796f75740a616e6420686f70696e67207468652074776f2061677265652e20457665727920686973746f726963616c206d69736d61746368206265747765656e2061207369676e657220616e640a6120766572696669657220697320612062756720746869732072656d6f76657320627920636f6e737472756374696f6e2e0000000000000e70726f6f665f707265696d6167650000000000040000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f69640000000000040000000000000005686f7572730000000000000b00000000000000056e6f6e63650000000000000600000001000003e90000000e000007d00000000d436f6e74726163744572726f7200000000000000000000ac52657475726e20746865206e657874206578706563746564206f7261636c65206e6f6e636520666f7220616e20657363726f772e0a546865206f7261636c65206d757374207369676e20612070726f6f66207573696e6720746869732065786163742076616c756520287265706c61792070726f74656374696f6e292e0a52657475726e73203020666f7220616e20756e6b6e6f776e2f756e696e697469616c697a656420657363726f772e000000096765745f6e6f6e6365000000000000010000000000000009657363726f775f6964000000000000040000000100000006001e11636f6e7472616374656e766d6574617630000000000000001400000000006f0e636f6e74726163746d65746176300000000000000005727376657200000000000006312e38352e3000000000000000000008727373646b7665720000002f32302e352e30233965326333303232623433353562323234613761383134653133626135313736316565623134626200" + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 22 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": false + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": false + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 22 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_preserves_escrow_state_and_custody.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_preserves_escrow_state_and_custody.1.json new file mode 100644 index 0000000..de03583 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_preserves_escrow_state_and_custody.1.json @@ -0,0 +1,4104 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_oracle_key", + "args": [ + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_multi_sig_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "manager_approve", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "upgrade", + "args": [ + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": false + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "finance_approve", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "pay_batch", + "args": [ + { + "u32": 1 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "EscrowCount" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Nonce" + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "u64": 1 + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "OracleKey" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 1555200 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5806905060045992000 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5806905060045992000 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 115220454072064130 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 115220454072064130 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1194852393571756375 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1194852393571756375 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 990000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da", + "code": "0061736d01000000019b022960037f7f7f017f60027f7f017f60047e7e7e7e017e60027e7e017e60037e7e7e017e6000017e60017e017e60037f7f7f0060027f7e0060047f7f7f7f0060057f7f7e7f7f0060027f7f017e60047f7f7f7e0060027e7f017e60017f017e60017e017f6000017f60017f0060017f017f60027f7e017f60057f7e7e7e7e0060057e7e7e7e7e017e60067f7f7e7e7e7e017f60027f7f0060067f7f7f7e7e7e0060077f7f7f7f7e7e7e017e60057f7f7f7f7f0060000060057f7e7e7f7f017e60057f7e7e7f7f0060057f7f7f7f7f017e60067f7e7f7f7f7f017e60037f7f7f017e60037f7e7e017f60037f7e7e017e60027f7e017e60047f7e7e7e017e60057f7e7e7e7e017e60037f7e7e0060047f7e7e7f0060067f7e7e7e7e7f0002d901240162013200020162013100020162016a0003016d01390004016d016100020176016700030178013000030178013100030178013300050178013600050178013700050178013800050169015f00060169013000060169013600030169013700060169013800060176015f0005017601300004017601310003017601330006017601360003016c015f0004016c01300003016c01310003016c01320003016c01360006016c01370002016c013800030164015f00040162015f00060162013400050162013800060163015f000601630130000401610130000603f301f1010707070708090a010b07070701070c070c070c070c0b0c0b010701070707070b07070707070707070d0e0b0b0b0b0b070b0b0b0b0b0b0b070b0b0b0107060f05060f0510051106120510060f060f060f060f0313021415160612061206170612061706120218060e1110120b1907070707071a070707070707070707070701111b0b0b0b0b0b0e0c1c1d1e1f20071120010107070b0912120e11122122072223230e2422232224222325230e230b09070e1c1d201e1f2022220e0e0e0e23232223230e242223222422222223252224230e23232423050717060f0e0f0808260007171a11010101001111271427281400140405017001040405030100110619037f01418080c0000b7f0041d08ac0000b7f0041d08ac0000b07ba031b066d656d6f727902000a696e69745f61646d696e00610e65787065637465645f61646d696e00630d70726f706f73655f61646d696e00640c6163636570745f61646d696e0066096765745f61646d696e00680a7365745f706175736564006a0969735f706175736564006c0775706772616465006e1372656769737465725f6f7261636c655f6b65790070117265766f6b655f6f7261636c655f6b657900721869735f6f7261636c655f6b65795f72656769737465726564007411726f746174655f6f7261636c655f6b657900761b696e697469616c697a655f6d756c74695f7369675f657363726f770078127375626d69745f686f7572735f70726f6f66007a0f6d616e616765725f617070726f7665007c0f66696e616e63655f617070726f7665007e1066696e616c697a655f7061796d656e740080010d63616e63656c5f657363726f770082010a6765745f657363726f7700840111657874656e645f657363726f775f74746c0086010e70726f6f665f707265696d616765008801096765745f6e6f6e6365008a01097061795f6261746368008001015f00a4010a5f5f646174615f656e6403010b5f5f686561705f626173650302090c010041010b03a201890288020adff901f1014602017f017e23808080800041106b220324808080800020032001200210a580808000200329030821042000200329030037030020002004370308200341106a2480808080000b6102017f017e23808080800041106b22032480808080002003200229030022041081828080000240024020032802000d00200329030821040c010b2001200410c38180800021040b2000420037030020002004370308200341106a2480808080000b6401027e02400240024020022903002203a741ff0171220241c000460d0020024106470d0142002104200310fc8180800021030c020b420021042001200310c48180800021030c010b4201210410f98180800021030b20002004370300200020033703080bf80304027f017e017f047e23808080800041d0006b22032480808080004100210402400340200441c000460d01200320046a4202370300200441086a21040c000b0b0240024002400240024002400240024002402002290300220542ff018342cc00520d0020012005419482c0800041082003410810af818080001a410120032d0000220441004741017420044101461b22044102460d01410120032d0008220241004741017420024101461b22024102460d02200341c0006a200341106a2001109c8180800020032802400d0320032903482105200341c0006a200341186a2001109c8180800020032802400d04410120032d0020220641004741017420064101461b22064102460d0520032903482107200341c0006a200341286a2001109a8180800020032802400d062003290330220842ff01834204520d0702402003290338220942ff018342cb00520d002003290348210a200020043a0026200020023a0025200020063a002420002008422088a7360220200020093703182000200a37031020002005370308200020073703000c090b200041023a00260c080b200041023a00260c070b200041023a00260c060b200041023a00260c050b200041023a00260c040b200041023a00260c030b200041023a00260c020b200041023a00260c010b200041023a00260b200341d0006a2480808080000b3b01017f23808080800041106b2202248080808000200220013703082000200241086a10d48180800010cc818080001a200241106a2480808080000b12002000200142012002200310aa808080000b270020002000200110ac808080002002200310fe81808000200410fe8180800010cd818080001a0b4d02017f017e41022102024020002000200110ac808080002203420110bf81808000450d00410121020240024020002003420110c081808000a741ff01710e020102000b000b410021020b20020bcb0502017f017e23808080800041306b220224808080800002400240024002400240024002400240024020012802000e0700010203040506000b200241206a200041e082c08000109f8180800020022802200d07200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c060b200241206a200041f082c08000109f8180800020022802200d0620022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d062002200229032837031020022003370308200241206a200241086a2000109d818080000c050b200241206a2000418083c08000109f8180800020022802200d0520022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d052002200229032837031020022003370308200241206a200241086a2000109d818080000c040b200241206a2000419083c08000109f8180800020022802200d04200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c030b200241206a200041a483c08000109f8180800020022802200d03200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c020b200241206a200041b483c08000109f8180800020022802200d02200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c010b200241206a200041c883c08000109f8180800020022802200d0120022002290328370318200241186a10d4818080002103200241206a200141086a200010a18180800020022802200d012002200229032837031020022003370308200241206a200241086a2000109d818080000b20022903282103200229032050450d00200241306a24808080800020030f0b000b5e01017e02400240024020012001200210ac808080002203420110bf818080000d00410021010c010b20012003420110c081808000220342ff01834204520d012003422088a72102410121010b20002002360204200020013602000f0b000b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200042003703000c010b200320012004420110c081808000370308200341106a2001200341086a10a68080800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b880102017f017e23808080800041306b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200041023a00260c010b200320012004420110c081808000370300200341086a2001200310a78080800020032d002e4102460d012000200341086a41281093828080001a0b200341306a2480808080000f0b000b160020002000200110ac80808000420110bf818080000b1000200020012002420110b2808080000b210020002000200110ac808080002002200010a781808000200310ca818080001a0b1000200020012002420110b4808080000b210020002000200110ac808080002000200210b980808000200310ca818080001a0b1000200020012002420110b6808080000b210020002000200110ac808080002002200010a681808000200310ca818080001a0b1000200020012002420110b8808080000b210020002000200110ac808080002000200210bb80808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110db80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b210020002000200110ac808080002002200010a881808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110a480808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4d02017f017e41022102024020002000200110ac808080002203420210bf81808000450d00410121020240024020002003420210c081808000a741ff01710e020102000b000b410021020b20020b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420210bf818080000d00200042003703000c010b200320012004420210c081808000370308200341106a2001200341086a10b18180800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b160020002000200110ac80808000420210bf818080000b1000200020012002420210b6808080000b1000200020012002420210ba808080000b850102017f027e23808080800041106b220324808080800020032001200210b6818080000240024020032802000d00200320032903082204370300420121050240200341086a200410d08180800010ff8180800041c000470d0020002003290300370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b950203017f017e027f23808080800041c0006b22032480808080002001200210c380808000210420032001200241086a10c38080800037030820032004370300410021020240034020024110460d01200341106a20026a4202370300200241086a21020c000b0b200341246a200341106a200341106a41106a2003200341106a109681808000410020032802382202200328023422056b2206200620024b1b21022003280224200541037422066a2105200328022c20066a2106024003402002450d0120052006200110a981808000370300200541086a2105200641086a21062002417f6a21020c000b0b2001200341106a410210b08180800021042000420037030020002004370308200341c0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110d381808000024020022802004101470d00000b20022903082103200241106a24808080800020030b7902017f027e23808080800041206b2203248080808000200341106a2002200110a0818080000240024020032802100d00200320032903183703082001200341086a410110b0818080002104420021050c010b10f9818080002104420121050b2000200537030020002004370308200341206a2480808080000ba30102017f017e23808080800041206b2203248080808000200341106a200120021091818080000240024020032802100d0020032903182104200341106a2001200241046a10918180800020032802100d00200320032903183703082003200437030020012003410210b081808000210420004200370300200020043703080c010b10f981808000210420004201370300200020043703080b200341206a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200210918180800002400240024020032802200d0020032903282104200341206a2001200241046a10918180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200241086a10918180800002400240024020032802200d0020032903282104200341206a20022001109e8180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd00102017f027e23808080800041306b2203248080808000200341206a2001200241106a10918180800002400240024020032802200d0020032903282104200341206a200120021094818080002003290328210520032802200d01200341206a2001200241146a10918180800020032802200d002003200329032837031820032005370310200320043703082001200341086a410310b081808000210520004200370300200020053703080c020b10f98180800021050b20004201370300200020053703080b200341306a2480808080000bd20202017f067e23808080800041c0006b2203248080808000200341306a2001200241206a10918180800002400240024020032802300d0020032903382104200341306a2001200241246a10918180800020032802300d0020032903382105200341306a200241106a2001109e8180800020032802300d0020032903382106200341306a200241186a2001109e8180800020032802300d0020032903382107200341306a200120021094818080002003290338210820032802300d01200341306a2001200241306a1094818080002003290338210902402003280230450d00200921080c020b20032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410610b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341c0006a2480808080000bbd0302017f087e23808080800041d0006b2203248080808000200341c0006a2001200241386a10918180800002400240024020032802400d0020032903482104200341c0006a20012002413c6a10918180800020032802400d0020032903482105200341c0006a200241206a2001109e8180800020032802400d0020032903482106200341c0006a200241286a2001109e8180800020032802400d0020032903482107200341c0006a200120021094818080002003290348210820032802400d01200341c0006a2001200241106a1094818080002003290348210902402003280240450d00200921080c020b200341c0006a2001200241306a10a4808080002003290348210a02402003280240450d00200a21080c020b200341c0006a2001200241c0006a10a4808080002003290348210b02402003280240450d00200b21080c020b2003200b3703382003200a37033020032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410810b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341d0006a2480808080000b2a00024020022802000d0020004200370300200042023703080f0b2000200241086a2001109e818080000b4001017f23808080800041106b2202248080808000200220003703082001200241086a200110a88180800010ce818080002100200241106a24808080800020000b15002000280200417f6aad4220864283808080107c0b4502017f017e23808080800041106b220224808080800020022000200110c780808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1d00024020012802000d0020012903080f0b200141046a10cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c680808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6902027f017e23808080800041106b2202248080808000200141046a21030240024020012802000d00200220002003109181808000024020022802000d00200229030821040c020b10f9818080001a000b200310cd8080800021040b200241106a24808080800020040b4502017f017e23808080800041106b220224808080800020022000200110d380808000024020022802004101470d00000b20022903082103200241106a24808080800020030bd00302017f097e23808080800041e0006b2203248080808000200341d0006a200120021094818080000240024020032802500d0020032903582104200341d0006a2001200241c8006a10a48080800020032802500d0020032903582105200341d0006a2001200241106a10948180800020032802500d0020032903582106200341d0006a2001200241d0006a10918180800020032802500d0020032903582107200341d0006a2001200241d8006a10938180800020032802500d0020032903582108200341d0006a2001200241206a10948180800020032802500d0020032903582109200341d0006a2001200241c0006a10a48080800020032802500d002003290358210a2002350254210b200341d0006a200241386a2001109e8180800020032802500d002003290358210c200341d0006a200241306a2001109e8180800020032802500d00200320032903583703482003200c3703402003200b4220864204843703382003200a370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200141dc80c08000410a2003410a10ae81808000210420004200370300200020043703080c010b200042013703000b200341e0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110c280808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110b781808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110ca80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1700024020012802000d0042020f0b200110cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c980808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c580808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c880808000024020022802004101470d00000b20022903082103200241106a24808080800020030bf20202017f067e23808080800041d0006b2203248080808000200341c0006a2001200241266a1093818080000240024020032802400d0020032903482104200341c0006a2001200241256a10938180800020032802400d0020032903482105200341c0006a200241086a2001109e8180800020032802400d0020032903482106200341c0006a20022001109e8180800020032802400d0020032903482107200341c0006a2001200241246a10938180800020032802400d0020032903482108200341c0006a200241106a200110a18180800020032802400d0020032903482109200341c0006a2001200241206a10918180800020032802400d0020032003290348370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200320022903183703382001419482c0800041082003410810ae81808000210420004200370300200020043703080c010b200042013703000b200341d0006a2480808080000b6802017f017e23808080800041106b22022480808080000240024020012802000d0020022000200141086a10d381808000024020022802000d00200229030821030c020b10f9818080001a000b200141046a10cd8080800021030b200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110cb80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6502017f017e23808080800041106b22022480808080000240024020012d00264102460d0020022000200110db80808000024020022802000d00200229030821030c020b10f9818080001a000b200110cd8080800021030b200241106a24808080800020030b2501017e20002903002202422088a72200410520004105491b4105200242ff01834204511b0b9e0502027f0b7e23808080800041f0006b22032480808080004100210402400340200441d000460d01200320046a4202370300200441086a21040c000b0b024002400240024002400240024002400240024002402002290300220542ff018342cc00520d002001200541dc80c08000410a2003410a10af818080001a200341d0006a2001200310928180800020032802500d01200341e8006a290300210520032903602106200341d0006a2001200341086a10a68080800020032802500d0220032903582107200341d0006a2001200341106a10928180800020032802500d032003290318220842ff01834204520d04410120032d0020220441004741017420044101461b22044102460d05200341e8006a29030021092003290360210a200341d0006a2001200341286a10928180800020032802500d06200341e8006a290300210b2003290360210c200341d0006a2001200341306a10a68080800020032802500d072003290358210d200341386a200410df8080800022024105460d08200341d0006a200341c0006a2001109c8180800020032802500d092003290358210e200341d0006a200341c8006a2001109c81808000024020032802500d002003290358210f2000200c3703202000200a37031020002006370300200020043a00582000200236025420002008422088a7360250200020073703482000200d3703402000200e3703382000200f3703302000200b37032820002009370318200020053703080c0b0b200041053602540c0a0b200041053602540c090b200041053602540c080b200041053602540c070b200041053602540c060b200041053602540c050b200041053602540c040b200041053602540c030b200041053602540c020b200041053602540c010b200041053602540b200341f0006a2480808080000b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e2808080003602082001200141086a10d7808080002100200141206a24808080800020000be90101027f23808080800041306b2201248080808000200120003703082001412f6a10bd81808000410c210202402001412f6a41d083c0800010be808080000d00200141086a10b2818080002001412f6a10bd818080002001412f6a41d083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ef2eed90b3703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020b200141306a24808080800020020b3d02017f017e23808080800041206b2200248080808000200042003703082000411f6a200041086a10dd808080002101200041206a24808080800020010b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e5808080003602082001200141086a10d7808080002100200141206a24808080800020000bdd0101027f23808080800041306b220124808080800020012000370308200141106a108c818080000240024020012802100d002001412f6a10bd818080002001412f6a41e083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ed4b8bacdbed7013703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020c010b200128021421020b200141306a24808080800020020b3e02017f017e23808080800041106b2200248080808000200010e78080800036020c20002000410c6a10d7808080002101200041106a24808080800020010bad0203017f017e017f23808080800041306b22002480808080002000412f6a10bd81808000200041106a2000412f6a41e083c0800010bd808080000240024020002802104101470d00200020002903182201370308200041086a10b2818080002000412f6a10bd818080002000412f6a41d083c08000200041086a10c0808080002000412f6a10bd818080002000412f6a2000412f6a41e083c0800010ac80808000420210cb818080001a2000412f6a10bd818080002000412f6a418087014180d21f10c181808000200020013703202000428ef2b5958ab5023703182000428ee6aeb9ea043703102000412f6a2000412f6a200041106a10d480808000200041206a2000412f6a10a88180800010c2818080001a410021020c010b411521020b200041306a24808080800020020b4102017f017e23808080800041206b2200248080808000200041086a10e9808080002000411f6a200041086a10dd808080002101200041206a24808080800020010b3e01017f23808080800041106b22012480808080002001410f6a10bd8180800020002001410f6a41d083c0800010bd80808000200141106a2480808080000b5c01027f23808080800041106b2201248080808000410121020240024002402000a741ff01710e020102000b000b410021020b2001200210eb8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bdc0101017f23808080800041206b2201248080808000200120003a0007200141086a108c818080000240024020012802080d002001411f6a10bd818080002001411f6a41f083c08000200141076a10bf808080002001411f6a10bd818080002001411f6a418087014180d21f10c181808000200120012d00073a001e2001428ed2aadceeac033703102001428ee6aeb9ea043703082001411f6a2001411f6a200141086a10d4808080002001411e6a2001411f6a10a68180800010c2818080001a410021000c010b200128020c21000b200141206a24808080800020000b4102017f017e23808080800041106b2200248080808000200010ed808080003a000e2000410e6a2000410f6a10a6818080002101200041106a24808080800020010b4401027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000200141fd01710b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010ef808080003602082001200141086a10d7808080002100200141206a24808080800020000bcd0101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd81808000411621022001411f6a41f083c0800010bc8080800041fd0171450d01200120003703102001428ed4a9f3cdadeb013703082001428ee6aeb9ea043703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a2001411f6a10bd818080002001411f6a200010a880808000410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f1808080003602082001200141086a10d7808080002100200141206a24808080800020000be60101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418084c0800010b5808080002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418087014180f6de0010a980808000200120003703102001428ed8ea1b3703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f3808080003602082001200141086a10d7808080002100200141206a24808080800020000bc20101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001411f6a200110ac80808000420110cb818080001a200120003703102001428ed4b0faaebd033703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6b01017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f5808080003a0008200141086a2001411f6a10a6818080002100200141206a24808080800020000b5101027f23808080800041206b22012480808080002001411f6a10bd8180800020014106360208200120003703102001411f6a200141086a10ab808080002102200141206a248080808000200241fd01710b7a01017f23808080800041206b2202248080808000200220013703000240200042ff01834204520d00200241086a2002411f6a2002109b8180800020022802084101460d0020022000422088a7200229031010f7808080003602082002200241086a10d7808080002100200241206a24808080800020000f0b000b990801087f2380808080004180036b2202248080808000200220013703000240108d8180800022030d00200241ff026a10bd81808000200241013602302002200036023420024190026a200241ff026a200241306a10af80808000024020022d00b60222034102460d002002280290022104200241086a41047220024190026a41047241221093828080001a200220033a002e20022004360208200220022d00b7023a002f200241086a10b2818080002002108e8180800022030d014108210320022d002e0d0141002103200241286a2204200229032010c88180800010ff818080002105024002400340024020052003470d00200220013703180240200228022841016a2203450d00200220033602282002200241ff026a10c5818080002201370340200241c8006a21062004200229032010c88180800010ff818080002107200241e9026a220841036a2109410021030340024020072003470d0020022001370320200241ff026a10bd8180800020024190026a41086a2203200241306a41086a22042903003703002002200229033037039002200241ff026a20024190026a200241086a10b380808000200241ff026a10bd81808000200320042903003703002002200229033037039002200241ff026a20024190026a418087014180f6de0010a980808000200220022802283602b401200220003602b0012002428ed4b9b3cebe03370398022002428ed4b1d4f9a60337039002200241ff026a200241ff026a20024190026a10d480808000200241ff026a200241b0016a10d98080800010c2818080001a410021030c080b4105210502402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e08080800020022802e40222054105460d06200241b0016a20024190026a41d4001093828080001a200220092800003600ab01200220082800003602a8010b200241d0006a200241b0016a41d4001093828080001a200220022800ab0136004b200220022802a801360248024020054105460d0020024190026a200241d0006a41d4001093828080001a2009200228004b36000020082002280248360000200241003a00e802200220053602e402200220062002290340200620024190026a10d28080800010c9818080002201370340200341016a21030c010b0b419884c08000108782808000000b418884c08000108c82808000000b02402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e080808000200341016a210320022802e402417d6a0e03020103010b0b41a884c08000108782808000000b410621030c020b000b410421030b20024180036a24808080800020030be70101017f23808080800041c0006b2204248080808000200420013703182004200037031020042002370320200441286a2004413f6a200441106a10b181808000024020042802284101460d0020042903302101200441286a2004413f6a200441186a10b18180800020042802284101460d0020042903302100200441286a2004413f6a200441206a109b8180800020042802284101460d00200342ff018342cb00520d00200441086a200120002004290330200310f980808000200420042903083702282004413f6a200441286a10d1808080002103200441c0006a24808080800020030f0b000bf01104077f027e037f017e23808080800041e0026b220524808080800020052002370320200520013703182005200337032820052004370330410121060240108d8180800022070d00200541186a10b2818080000240200541186a200541206a10b481808000450d00410f21070c010b0240200541386a2208200529033010c88180800010ff81808000450d0002402008200529033010c88180800010ff8180800041e4004d0d00411321070c020b200541286a108e8180800022070d01410021072008200529033010c88180800010ff81808000210920054188026a210a200541106a210b4200210c4200210d0340024002400240024020092007470d00200541df026a10bd818080002005200541df026a41b884c0800010ad808080004100210a02402005280204410020052802004101711b41016a220e450d002005200e36023c2005200541df026a10aa8180800037034020054188016a21062008200529033010c88180800010ff81808000210f02400340200f200a200f200a4b1b211003400240200a2010470d0041002107200541f3006a41003600002005410036027020052005290330370368200520033703602005200529032037035820052005290318370350200541df026a10bd81808000200541013602b8012005200e3602bc01200541df026a200541b8016a200541d0006a10b380808000200541df026a10bd81808000200541e0016a41086a2206200541b8016a41086a290300370300200520052903b8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541023602c8012005200e3602cc01200541df026a200541c8016a41d884c0800010b780808000200541df026a10bd818080002006200541c8016a41086a290300370300200520052903c8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541df026a41b884c080002005413c6a10b180808000200541df026a10bd81808000200541df026a41b884c08000418087014180f6de0010a9808080002005200d3703f8012005200c3703f0012005200528023c3602e801200520052903183703e0012005428ed2eadca9bda3013703c8022005428ef8f49b8ad7023703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10ce8080800010c2818080001a2008200529033010c88180800010ff81808000210620054188026a21090340024020062007470d00200528023c2107410021060c0d0b02402008200529033010c88180800010ff8180800020074d0d00200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b4024105460d0820052903a002210220052903a80221012005290390022104200529039802210320052903e001210d20052903e801210c2005290380022111200520092903003703f801200520113703f0012005200c3703e8012005200d3703e0012005200528023c3602980220052003370388022005200437038002200520013703a00220052002370390022005200736029c022005428ed2a9133703c8022005428ef2b3d5ecb7d6013703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10d68080800010c2818080001a200741016a21070c010b0b41e084c08000108782808000000b2008200529033010c88180800010ff81808000200a4d0d02200520082005290330200a10fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105460d05200a41016a210b200520052903980237034841002107024003400240200a2007470d00200542003703c802200542003703c002410021072008200529033010c88180800010ff81808000210a024003400240200a2007470d002005200541df026a200541c8006a10d1818080003703e001200541e0016a200541186a200541c0006a200541c0026a10d281808000200b210a0c070b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703d801200541e0016a2008200541d8016a10e08080800020052802b40222094105460d0a200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801024002402006200541c8006a10b481808000450d0020052903c80222022005290358220185427f852002200220017c20052903c002220120052903507c2204200154ad7c220185834200530d01200520043703c002200520013703c8020b200741016a21070c010b0b419085c08000108c82808000000b418085c08000108782808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b40222094105460d07200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801200741016a21072006200541c8006a10b481808000450d000b200b210a0c010b0b0b41a085c08000108782808000000b41f084c08000108782808000000b41c884c08000108c82808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105470d020b000b41b085c08000108782808000000b4101210620052903e00122045020052903e80122024200532002501b0d01200529038002221150200a29030022014200532001501b0d01024020052903a80220052903a002560d00411221070c030b200541086a200420022011200110948280800002402005290308200b290300844200510d00411121070c030b0240200d200285427f85200d200d20027c200c20047c2202200c54ad7c220185834200530d00200741016a21072002210c2001210d0c010b0b41c085c08000108c82808000000b410721070b2000200736020420002006360200200541e0026a2480808080000bfd0101017f23808080800041d0006b22052480808080002005200337031020052002370308200520043703180240200042ff01834204520d00200142ff01834204520d00200541206a200541cf006a200541086a10928180800020052802204101460d00200541386a290300210320052903302102200541206a200541cf006a200541106a10a68080800020052802204101460d0020052903282104200541206a200541cf006a200541186a10c18080800020052802204101460d0020052000422088a72001422088a7200220032004200529032810fb808080003602202005200541206a10d7808080002100200541d0006a24808080800020000f0b000bef0801047f23808080800041d0026b2206248080808000200620053703200240108d8180800022070d00200641cf026a10bd818080002006410136025020062000360254200641d0016a200641cf026a200641d0006a10af808080000240024020062d00f60122074102460d0020062802d0012108200641286a410472200641d0016a41047241221093828080001a20062008360228200620062d00f7013a004f200620073a004e02402007410171450d00410821070c030b4101210720062d004c0d0220062d004d0d02200641c8006a2208200629034010c88180800010ff8180800020014b0d010b410421070c010b024002402008200629034010c88180800010ff8180800020014d0d00200620082006290340200110fe8180800010c7818080003703b002200641d0016a2008200641b0026a10e08080800020062802a40222074105470d01000b41d085c08000108782808000000b200641e0006a200641d0016a41d4001093828080001a200620073602b401200620062903a8023703b8012006410036021c200641086a2002200320062903800120064188016a2903002006411c6a1091828080000240200628021c450d00410721070c010b02402006290308200629036085200641106a290300200629036885844200510d00411121070c010b2006200641cf026a20002001200641e0006a2002200320041090818080003703c801200641cf026a10bd81808000200641cf026a200641386a200641c8016a200641206a10b981808000200641cf026a10bd81808000200641023602b002200620003602b402200641d0016a200641cf026a200641b0026a10ae808080004109210720062903d801420020062802d0011b2004520d0002402004427f520d00410e21070c010b2006200442017c3703c002200641cf026a10bd81808000200641d0016a41086a2207200641b0026a41086a2209290300370300200620062903b0023703d001200641cf026a200641d0016a200641c0026a10b780808000200641cf026a10bd8180800020072009290300370300200620062903b0023703d001200641cf026a200641d0016a418087014180f6de0010a9808080002006200337037820062002370370200641013a00b801200641d0016a200641e0006a41e0001093828080001a200620082006290340200110fe818080002008200641d0016a10d28080800010c681808000370340200641cf026a10bd818080002007200641d0006a41086a2208290300370300200620062903503703d001200641cf026a200641d0016a200641286a10b380808000200641cf026a10bd8180800020072008290300370300200620062903503703d001200641cf026a200641d0016a418087014180f6de0010a980808000200620033703e801200620023703e001200620013602d401200620003602d0012006428ef2aef9a9c7033703b8022006428ef0b79ddd053703b002200641cf026a200641cf026a200641b0026a10d480808000200641cf026a200641d0016a10d08080800010c2818080001a410021070b200641d0026a24808080800020070b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710fd8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbb0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a10b281808000024020012d002c450d00410121020c020b200141013a002c200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428eeeaad6b9b6ca013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710ff8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbe0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a41086a10b281808000024020012d002d450d00410121020c020b200141013a002d200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428ed4e8d9b9f6ae013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b4b01017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a71081818080002001200110cf808080002100200141106a24808080800020000bd20a04047f017e047f057e23808080800041b0026b22022480808080000240024002400240108d8180800022030d00200241af026a10bd818080002002410136023020022001360234200241b0016a200241af026a200241306a10af8080800020022d00d60122034102460d0120022802b0012104200241086a410472200241b0016a41047241221093828080001a20022004360208200220022d00d7013a002f200220033a002e02402003410171450d00410821030c030b200241086a10b2818080000240200241086a200241106a10b481808000450d00410f21030c030b4105210320022d002c4101470d0220022d002d4101470d0241002104200241286a2203200229032010c88180800010ff818080002105024002400340024020052004470d002002200241af026a10aa818080003703402002200241af026a10c5818080002206370348200241d0006a210720024180016a210820024188016a2109410021042003200229032010c88180800010ff818080002105200241e8006a210a4200210b4200210c02400340024020052004470d0020022006370320200241af026a10bd81808000200241b0016a41086a2203200241306a41086a2204290300370300200220022903303703b001200241af026a200241b0016a200241086a10b380808000200241af026a10bd8180800020032004290300370300200220022903303703b001200241af026a200241b0016a418087014180f6de0010a9808080002007200229034810c88180800010ff8180800021032002200c3703b8012002200b3703b001200220033602c401200220013602c0012002428ee2e6d9bb053703582002428ef2b3d5ecb7d601370350200241af026a200241af026a200241d0006a10d480808000200241af026a200241b0016a10da8080800010c2818080001a20002002290348370308200041003602000c0a0b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c78180800037039802200241b0016a200320024198026a10e0808080002002280284024105460d05200241d0006a200241b0016a41d4001093828080001a200241033602a40120022002290388023703a8012002200241af026a200910d1818080003703b001200241b0016a200241c0006a2008200241d0006a10d2818080000240200c2002290358220685427f85200c200c20067c200b2002290350220d7c220e200b54ad7c220f85834200530d00200220022903603703e0012002200d3703b001200220013602d00120022002290388013703c80120022002290380013703c001200220063703b8012002200a2903003703e801200220043602d4012002428ed2aeb30d3703a0022002428ef2b3d5ecb7d60137039802200241af026a200241af026a20024198026a10d480808000200241af026a200241b0016a10d88080800010c2818080001a200241b0016a200241d0006a41e0001093828080001a2002200720022903482007200241b0016a10d28080800010c9818080002206370348200441016a2104200e210b200f210c0c010b0b41f085c08000108c828080000c040b41e085c08000108782808000000b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c781808000370350200241b0016a2003200241d0006a10e08080800020022802840222074105460d02024020074103460d00200441016a210420022d0088024101710d010b0b410d410620074103471b21030c040b418086c08000108782808000000b000b20004101360200200020033602040c020b410421030b20004101360200200020033602040b200241b0026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710838180800036020c20012001410c6a10d7808080002100200141106a24808080800020000b8b0d04087f017e017f027e23808080800041c0026b2201248080808000200141bf026a10bd818080002001410136023020012000360234200141d0016a200141bf026a200141306a10af808080000240024020012d00f60122024102460d0020012802d0012103200141086a410472200141d0016a41047241221093828080001a20012003360208200120012d00f7013a002f200120023a002e4108210320024101710d01200141086a10b28180800041002102200141286a2203200129032010c88180800010ff818080002104024002400340024020042002470d002001200141bf026a10aa8180800037034020014198016a2104410021052003200129032010c88180800010ff8180800021060240034020062005200620054b1b2107034020052108024020082007470d00200141013a002e2001200141bf026a10c58180800022093703c801200141d0016a2104410021022003200129032010c88180800010ff81808000210a03400240200a2002470d0020012009370320200141bf026a10bd81808000200141d0016a41086a200141306a41086a290300370300200120012903303703d001200141bf026a200141d0016a200141086a10b380808000200120003602602001428ee2aaf4ecc4023703d8012001428ef8f49b8ad7023703d001200141bf026a200141bf026a200141d0016a10d480808000200141e0006a200141bf026a10a78180800010c2818080001a410021030c0b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d08200141e0006a200141d0016a41d4001093828080001a20012903a8022109200120003602502001428ee2aaf4ecc4023703d8012001428ef2b3d5ecb7d6013703d00120012002360254200141bf026a200141bf026a200141d0016a10d480808000200141bf026a200141d0006a10d98080800010c2818080001a200141d0016a200141e0006a41d4001093828080001a200120093703a802200141043602a4022001200420012903c8012004200141d0016a10d28080800010c98180800022093703c801200241016a21020c010b0b419086c08000108782808000000b2003200129032010c88180800010ff8180800020084d0d02200120032001290320200810fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d05200841016a21052001200129038802370348410021020340024020082002470d002001420037035820014200370350410021022003200129032010c88180800010ff8180800021080340024020082002470d002001290350420052200129035822094200552009501b450d052001200141bf026a200141c8006a10d1818080003703d001200141d0016a200141c0006a200141086a200141d0006a10d2818080000c050b024002402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c7818080003703c801200141d0016a2003200141c8016a10e08080800020012802a402220a4105470d010c0a0b41b086c08000108782808000000b200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b80102402004200141c8006a10b481808000450d000240200129035822092001290368220b85427f8520092009200b7c2001290350220b20012903607c220c200b54ad7c220b85834200530d002001200c3703502001200b3703580c010b41c086c08000108c82808000000b200241016a21020c000b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370350200141d0016a2003200141d0006a10e08080800020012802a402220a4105460d07200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b801200241016a21022004200141c8006a10b481808000450d010c020b0b0b0b41d086c08000108782808000000b41a086c08000108782808000000b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e080808000200241016a210220012802a402417d6a0e03030102010b0b41e086c08000108782808000000b000b410621030c010b410421030b200141c0026a24808080800020030b4e01017f23808080800041306b22012480808080000240200042ff01834204510d00000b20012000422088a71085818080002001412f6a200110de808080002100200141306a24808080800020000b7a01017f23808080800041c0006b22022480808080002002413f6a10bd81808000200241013602282002200136022c20022002413f6a200241286a10af808080000240024020022d00264102470d00200041023a0026200041043602000c010b2000200241281093828080001a0b200241c0006a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710878180800036020c20012001410c6a10d7808080002100200141106a24808080800020000bd70201027f23808080800041306b22012480808080002001412f6a10bd8180800020014101360200200120003602044104210202402001412f6a200110b080808000450d002001412f6a10be8180800021022001412f6a10bd81808000200141106a41086a200141086a290300370300200120012903003703102001412f6a200141106a2002200210a9808080002001412f6a10bd8180800020014102360210200120003602142001412f6a200141106a2002200210a9808080002001412f6a10bd818080002001412f6a41b884c080002002200210a9808080002001412f6a10bd818080002001412f6a2002200210c18180800020012002360228200120003602242001428ee2f91c3703182001428ef8f49b8ad7023703102001412f6a2001412f6a200141106a10d4808080002001412f6a200141246a10d98080800010c2818080001a410021020b200141306a24808080800020020bcb0101017f23808080800041c0006b220424808080800020042003370308200420023703000240200042ff01834204520d00200142ff01834204520d00200441106a2004413f6a200410928180800020042802104101460d00200441286a290300210320042903202102200441106a2004413f6a200441086a10a68080800020042802104101460d00200441106a2000422088a72001422088a72002200320042903181089818080002004413f6a200441106a10dc808080002100200441c0006a24808080800020000f0b000b970301037f2380808080004180026b2206248080808000200641ff016a10bd81808000200641013602302006200136023420064190016a200641ff016a200641306a10af808080000240024020062d00b60122074102460d002006280290012108200641086a41047220064190016a41047241221093828080001a200620073a002e20062008360208200620062d00b7013a002f0240200641286a2207200629032010c88180800010ff8180800020024d0d00024002402007200629032010c88180800010ff8180800020024d0d00200620072006290320200210fe8180800010c78180800037033020064190016a2007200641306a10e08080800020062802e40122074105470d01000b41f086c08000108782808000000b200641306a20064190016a41d4001093828080001a2006200736028401200620062903e80137038801200641ff016a20012002200641306a200320042005109081808000210420004100360200200020043703080c020b20004281808080c0003703000c010b20004281808080c0003703000b20064180026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a7108b818080003703002001410f6a200110bb808080002100200141106a24808080800020000b6502017f017e23808080800041306b22012480808080002001412f6a10bd81808000200141023602082001200036020c200141186a2001412f6a200141086a10ae808080002001280218210020012903202102200141306a2480808080002002420020001b0b860102027f017e23808080800041206b22012480808080002001411f6a10bd81808000200141086a2001411f6a41d083c0800010bd80808000410121020240024020012802084101470d00200120012903102203370300200110b28180800020002003370308410021020c010b2000410a3602040b20002002360200200141206a2480808080000b4901027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000410b4100200141fd01711b0b7f01027f23808080800041206b22012480808080002001411f6a10bd818080004100210202402001411f6a41d083c0800010be80808000450d002001411f6a10bd818080002001410636020820012000290300370310410041102001411f6a200141086a10ab8080800041fd01711b21020b200141206a24808080800020020b4d02017f017e23808080800041106b2202248080808000200010bd8180800020022001290300200010cc808080003703002002410f6a200210b8818080002103200241106a24808080800020030b8b0f04017f017e087f017e23808080800041e0006b22072480808080002007200010cf818080002208370300200741086a21092007200920082009200810d08180800010ff8180800010fe81808000418184c08000410410ac81808000220837030020074180043b01382007200920082009200810d08180800010ff8180800010fe81808000200741386a410210ac818080003703002007200741df006a10bc81808000370330200741386a41186a220a4200370300200741386a41106a220b4200370300200741386a41086a220c420037030020074200370338200741306a41086a220d200741306a10d4818080004204200741386a412010ad81808000200741106a41186a220e200a290300370300200741106a41106a220f200b290300370300200741106a41086a2210200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac818080003703002007200010aa8180800037033020072000200741306a108f81808000370308200a4200370300200b4200370300200c420037030020074200370338200741086a41086a200741086a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341306a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341386a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800022083703002007200141187420014180fe03714108747220014108764180fe0371200141187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac8180800022083703002007200241187420024180fe03714108747220024108764180fe0371200241187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac81808000221137030020072003290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703402007200341086a290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703382007200920112009201110d08180800010ff8180800010fe81808000200741386a411010ac8180800022083703002007200442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703402007200542388620054280fe0383422886842005428080fc0783421886200542808080f80f834208868484200542088842808080f80f832005421888428080fc07838420054228884280fe038320054238888484843703382007200920082009200810d08180800010ff8180800010fe81808000200741386a411010ac81808000220537030020072003290340220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac81808000220537030020072003290348220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac8180800022043703002007200642388620064280fe0383422886842006428080fc0783421886200642808080f80f834208868484200642088842808080f80f832006421888428080fc07838420064228884280fe03832006423888848484370338200920042009200410d08180800010ff8180800010fe81808000200741386a410810ac818080002104200741e0006a24808080800020040b190020004200370300200020023502004220864204843703080b7c01027e024002400240024020022903002203a741ff0171220241c500460d002002410b470d02200041106a20031080828080000c010b2001200310e58180800021042001200310e481808000210320002004370318200020033703100b420021030c010b200010f981808000370308420121030b200020033703000b130020004200370300200020023100003703080b4602017f017e23808080800041106b2203248080808000200320012002109581808000200329030821042000200329030037030020002004370308200341106a2480808080000b6d02017f027e23808080800041106b2203248080808000200320022903002204200241086a29030022051082828080000240024020032802000d00200329030821040c010b20012005200410e38180800021040b2000420037030020002004370308200341106a2480808080000b4b00200041003602102000200436020c2000200336020820002002360204200020013602002000200220016b410376220236021820002002200420036b410376220420022004491b3602140b3901017f23808080800041106b22032480808080002003200229020037020820002001200341086a109881808000200341106a2480808080000b6a02027f017e23808080800041106b22032480808080002003200228020022042002280204220210fa818080000240024020032802000d00200329030821050c010b20012004200210d78180800021050b2000420037030020002005370308200341106a2480808080000b5202017f017e23808080800041106b2203248080808000200320022903083703082003200229030037030020012003410210da8180800021042000420037030020002004370308200341106a2480808080000b0e00200020012001109b818080000b7d02017f027e23808080800041106b2203248080808000024002402002290300220442ff018342c800520d0020032004370308420121050240200341106a200410f58180800010ff818080004120470d0020002003290308370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b2e01027e4201210302402001290300220442ff018342cd00520d0020002004370308420021030b200020033703000b0e002000200220011099818080000b130020004200370300200020012903003703080b5102017f017e23808080800041106b220324808080800020032001200210978180800042012104024020032802000d0020002003290308370308420021040b20002004370300200341106a2480808080000b130020004200370300200020012903003703080b130020004200370300200020012903003703080b1200200141bb87c08000410f108b828080000b0300000b02000b4502017f017e23808080800041106b2202248080808000200220002001109481808000024020022802004101470d00000b20022903082103200241106a24808080800020030b070020003100000b0d0020003502004220864204840b070020002903000b070020002903000b0a00200010df818080000b6001017f23808080800041106b22042480808080000240200020012903002002290300200310f28180800042ff01834202510d00419087c08000412b2004410f6a418087c0800041b088c08000108682808000000b200441106a2480808080000b12002000200120022003200410d5818080000b12002000200120022003200410d6818080000b12002000200120022003200410d8818080000b140020002001200220032004200510d9818080000b0e0020002001200210da818080000b2e01027e4201210302402002290300220442ff018342cd00520d0020002004370308420021030b200020033703000b1300200041086a200029030010f8818080001a0b5902017f017e23808080800041206b22032480808080002003200236020c20032001360208200341106a2000200341086a109781808000024020032802104101470d00000b20032903182104200341206a24808080800020040b11002000200110b58180800041ff0171450b2601017e417f200041086a2000290300200129030010db81808000220242005220024200531b0b2e01027e4201210302402002290300220442ff018342c800520d0020002004370308420021030b200020033703000b130020004200370300200020022903003703080b0f002000200129030010f6818080000b1a00200020012903002002290300200329030010f7818080001a0b1000200010dd8180800010ff818080000b1000200010e08180800010ff818080000b0a00200010de818080000b02000b6d01037f23808080800041106b22012480808080002001410f6a10ba818080002102024002402001410f6a10bb8180800022032002490d00200320026b41016a22020d0141a889c08000108c82808000000b41a889c08000108d82808000000b200141106a24808080800020020b140020002001200210ec8180800010fd818080000b0e0020002001200210ed818080000b1b002000200110fe81808000200210fe8180800010f1818080001a0b0e0020002001200210dc818080000b0c002000200110e1818080000b0c002000200110e2818080000b0a00200010e6818080000b1000200020012002200310e7818080000b0e0020002001200210e8818080000b0c002000200110e9818080000b0e0020002001200210ea818080000b1000200020012002200310eb818080000b0e0020002001200210ee818080000b0c002000200110ef818080000b12002000200120022003200410f0818080000b0c002000200110f3818080000b0a00200010f4818080000b0c002000200110f5818080000b070020012903000bdf0102027f027e23808080800041c0006b22042480808080002004200041086a220541b889c08000410810b381808000370308200129030021062002290300210720042005200310a5818080003703202004200737031820042006370310410021010340024020014118470d00410021010240034020014118460d01200441286a20016a200441106a20016a290300370300200141086a21010c000b0b20052000200441086a2005200441286a410310da8180800010ab81808000200441c0006a2480808080000f0b200441286a20016a4202370300200141086a21010c000b0b130020004200370300200020022903003703080b070020002903000b1e00200120022003ad4220864204842004ad4220864204841080808080000b1f00200120022003ad4220864204842004ad4220864204841081808080001a0b1a002001ad4220864204842002ad4220864204841082808080000b2e00024020022004460d00000b2001ad4220864204842003ad4220864204842002ad4220864204841083808080000b3000024020032005460d00000b20012002ad4220864204842004ad4220864204842003ad4220864204841084808080000b1a002001ad4220864204842002ad4220864204841085808080000b0c00200120021086808080000b0c00200120021087808080000b08001088808080000b08001089808080000b0800108a808080000b0800108b808080000b0a002001108c808080000b0a002001108d808080000b0c0020012002108e808080000b0a002001108f808080000b0a0020011090808080000b08001091808080000b0e002001200220031092808080000b0c00200120021093808080000b0a0020011094808080000b0c00200120021095808080000b0e002001200220031096808080000b0c00200120021097808080000b0c00200120021098808080000b0c00200120021099808080000b0a002001109a808080000b10002001200220032004109b808080000b0c0020012002109c808080000b0e00200120022003109d808080000b0a002001109e808080000b0800109f808080000b0a00200110a0808080000b0a00200110a1808080000b0e0020012002200310a2808080000b0a00200110a3808080000b0900428390808080010bb50102017f017e23808080800041106b220324808080800002400240200241094b0d00420021040340024020020d002000410036020020002004420886420e843703080c030b200341086a20012d000010fb81808000024020032d00084103460d0020002003290308370204200041013602000c030b200141016a21012002417f6a2102200442068620033100098421040c000b0b20002002360208200041003a0004200041013602000b200341106a2480808080000b820101017f410121020240200141ff017141df00460d000240200141506a41ff0171410a490d000240200141bf7f6a41ff0171411a490d0002402001419f7f6a41ff0171411a490d00200020013a0001200041013a00000f0b200141456a21020c020b2001414b6a21020c010b200141526a21020b200041033a0000200020023a00010b070020004208880b070020004201510b0b002000ad4220864204840b08002000422088a70b160020002001423f87370308200020014208873703000b3201017e420121020240200142ffffffffffffffff00560d0020002001420886420684370308420021020b200020023703000b5001017e42012103024020014280808080808080c0007c42ffffffffffffffff00560d0020012001852001423f87200285844200520d0020002001420886420b84370308420021030b200020033703000ba00601067f0240200028020022032000280208220472450d0002402004410171450d00200120026a210502400240200028020c22060d0041002107200121080c010b41002107200121080340200822042005460d020240024020042c00002208417f4c0d00200441016a21080c010b0240200841604f0d00200441026a21080c010b0240200841704f0d00200441036a21080c010b200441046a21080b200820046b20076a21072006417f6a22060d000b0b20082005460d00024020082c00002204417f4a0d0020044160491a0b024002402007450d00024020072002490d0020072002460d01410021040c020b200120076a2c000041404e0d00410021040c010b200121040b2007200220041b21022004200120041b21010b024020030d00200028021c20012002200028022028020c118080808000000f0b200028020421030240024020024110490d0020012002108a8280800021040c010b024020020d00410021040c010b2002410371210602400240200241044f0d0041002104410021070c010b2002410c712105410021044100210703402004200120076a22082c000041bf7f4a6a200841016a2c000041bf7f4a6a200841026a2c000041bf7f4a6a200841036a2c000041bf7f4a6a21042005200741046a2207470d000b0b2006450d00200120076a21080340200420082c000041bf7f4a6a2104200841016a21082006417f6a22060d000b0b02400240200320044d0d00200320046b2106024002400240410020002d0018220420044103461b22040e03020001020b20062104410021060c010b20064101762104200641016a41017621060b200441016a21042000280210210720002802202108200028021c210003402004417f6a2204450d0220002007200828021011818080800000450d000b41010f0b200028021c20012002200028022028020c118080808000000f0b0240200020012002200828020c11808080800000450d0041010f0b410021040340024020062004470d0020062006490f0b200441016a210420002007200828021011818080800000450d000b2004417f6a2006490f0b200028021c20012002200028022028020c118080808000000b4d01017f23808080800041206b22032480808080002003410036021020034101360204200342043702082003200136021c200320003602182003200341186a36020020032002108582808000000b3601017f23808080800041106b2202248080808000200241013b010c2002200136020820022000360204200241046a10a381808000000b8f0101017f23808080800041c0006b22052480808080002005200136020c2005200036020820052003360214200520023602102005410236021c200541c08ac08000360218200542023702242005418280808000ad422086200541106aad843703382005418380808000ad422086200541086aad843703302005200541306a360220200541186a2004108582808000000b130041908ac08000412b2000108482808000000b14002001200028020020002802041083828080000b180020002802002001200028020428020c118180808000000be90601087f024002402001200041036a417c71220220006b2203490d00200120036b22044104490d002004410371210541002106410021010240200220004622070d004100210102400240200020026b2208417c4d0d00410021090c010b4100210903402001200020096a22022c000041bf7f4a6a200241016a2c000041bf7f4a6a200241026a2c000041bf7f4a6a200241036a2c000041bf7f4a6a2101200941046a22090d000b0b20070d00200020096a21020340200120022c000041bf7f4a6a2101200241016a2102200841016a22080d000b0b200020036a210002402005450d0020002004417c716a22022c000041bf7f4a210620054101460d00200620022c000141bf7f4a6a210620054102460d00200620022c000241bf7f4a6a21060b20044102762108200620016a21030340200021042008450d02200841c001200841c001491b220641037121072006410274210541002102024020084104490d002004200541f007716a210941002102200421010340200128020c2200417f7341077620004106767241818284087120012802082200417f7341077620004106767241818284087120012802042200417f7341077620004106767241818284087120012802002200417f7341077620004106767241818284087120026a6a6a6a2102200141106a22012009470d000b0b200820066b2108200420056a2100200241087641ff81fc0771200241ff81fc07716a418180046c41107620036a21032007450d000b2004200641fc01714102746a22022802002201417f734107762001410676724181828408712101024020074101460d0020022802042200417f7341077620004106767241818284087120016a210120074102460d0020022802082202417f7341077620024106767241818284087120016a21010b200141087641ff811c71200141ff81fc07716a418180046c41107620036a0f0b024020010d0041000f0b2001410371210902400240200141044f0d0041002103410021020c010b2001417c712108410021034100210203402003200020026a22012c000041bf7f4a6a200141016a2c000041bf7f4a6a200141026a2c000041bf7f4a6a200141036a2c000041bf7f4a6a21032008200241046a2202470d000b0b2009450d00200020026a21010340200320012c000041bf7f4a6a2103200141016a21012009417f6a22090d000b0b20030b1a00200028021c20012002200028022028020c118080808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141dc89c0800036020820014204370210200141086a2000108582808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141888ac0800036020820014204370210200141086a2000108582808000000b5701017e02400240200341c000710d002003450d012002410020036b413f71ad8620012003413f71ad220488842101200220048821020c010b20022003413f71ad882101420021020b20002001370300200020023703080bf60804017f017e037f047e23808080800041b0016b2205248080808000420021060240024002400240024020047920037942c0007c20044200521ba7220720027920017942c0007c20024200521ba722084d0d002008413f4b0d01200741df004b0d02200720086b4120490d03200541a0016a2003200441e00020076b2209108e8280800020053502a00142017c210a4200210b420021060240024002400240034020054190016a2001200241c00020086b2208108e82808000200529039001210c0240200820094f0d00200541d0006a200320042008108e82808000024002402005290350220a50450d000c010b200c200a80210c0b200541c0006a200c420020032004109282808000024020012005290340220d5422082002200541c8006a290300220a542002200a511b0d002002200a7d2008ad7d21022001200d7d21012006200b200c7c220c200b54ad7c21060c0b0b200220047c200120037c2204200154ad7c200a7d2004200d54ad7d21022004200d7d21012006200c200b7c427f7c220c200b54ad7c21060c0a0b20054180016a200c200a80220c4200200820096b41ff00712208109082808000200541f0006a200c420020032004109282808000200541e0006a2005290370200541f0006a41086a290300200810908280800020054180016a41086a29030020067c2005290380012206200b7c220b200654ad7c210620072002200541e0006a41086a2903007d20012005290360220c54ad7d2202792001200c7d22017942c0007c20024200521ba722084d0d012008413f4d0d000b200350450d010c020b20012003542208200220045420022004511b450d02200b210c0c070b200120038021020b200120038221012006200b20027c220c200b54ad7c2106420021020c050b200220047d2008ad7d2102200120037d21012006200b42017c220c50ad7c21060c040b200220044200200120035a200220045a20022004511b22081b7d20012003420020081b220454ad7d2102200120047d21012008ad210c0c030b20012001200380220c20037e7d210142002106420021020c020b20022002200342ffffffff0f83220480220620037e7d4220862001422088220c842004802202422086200c200220037e7d422086200142ffffffff0f83842201200480220384210c2001200320047e7d210120024220882006842106420021020c010b200541306a2003200441c00020086b2208108e82808000200541206a200120022008108e8280800042002106200541106a200342002005290320200529033080220c4200109282808000200520044200200c42001092828080002005290310210a02400240200541086a290300200541106a41086a290300220d20052903007c220b200d54ad7c4200520d002001200a5422082002200b542002200b511b450d010b200420027c200320017c2201200354ad7c200b7d2001200a54ad7d2102200c427f7c210c2001200a7d21010c010b2002200b7d2008ad7d21022001200a7d2101420021060b200020013703102000200c3703002000200237031820002006370308200541b0016a2480808080000b5701017e02400240200341c000710d002003450d0120022003413f71ad2204862001410020036b413f71ad88842102200120048621010c010b20012003413f71ad862102420021010b20002001370300200020023703080bf50303017f027e027f23808080800041e0006b220624808080800042002107420021084100210902402001200284500d002003200484500d00420020037d2003200442005322091b2107420020017d20012002420053220a1b2108420020042003420052ad7c7d200420091b21032004200285210402400240420020022001420052ad7c7d2002200a1b2202500d0002402003500d00200641d0006a2007200320082002109282808000200641d8006a290300210141012109200629035021020c020b200641c0006a2008420020072003109282808000200641306a2002420020072003109282808000200641c0006a41086a290300220220062903307c2201200254200641306a41086a290300420052722109200629034021020c010b02402003500d00200641206a2007420020082002109282808000200641106a2003420020082002109282808000200641206a41086a290300220220062903107c2201200254200641106a41086a290300420052722109200629032021020c010b20062007200320082002109282808000200641086a290300210141002109200629030021020b420020027d20022004420053220a1b2108420020012002420052ad7c7d2001200a1b22072004854200590d00410121090b200520093602002000200737030820002008370300200641e0006a2480808080000b6e01067e2000200342ffffffff0f832205200142ffffffff0f8322067e22072003422088220820067e22062005200142208822097e7c22054220867c220a3703002000200820097e2005200654ad4220862005422088847c200a200754ad7c200420017e200320027e7c7c3703080ba50501087f02400240200241104f0d00200021030c010b02402000410020006b41037122046a220520004d0d002004417f6a2106200021032001210702402004450d002004210820002103200121070340200320072d00003a0000200741016a2107200341016a21032008417f6a22080d000b0b20064107490d000340200320072d00003a0000200341016a200741016a2d00003a0000200341026a200741026a2d00003a0000200341036a200741036a2d00003a0000200341046a200741046a2d00003a0000200341056a200741056a2d00003a0000200341066a200741066a2d00003a0000200341076a200741076a2d00003a0000200741086a2107200341086a22032005470d000b0b2005200220046b2208417c7122066a210302400240200120046a22074103710d00200520034f0d0120072101034020052001280200360200200141046a2101200541046a22052003490d000c020b0b200520034f0d002007410374220241187121042007417c71220941046a2101410020026b411871210a2009280200210203402005200220047620012802002202200a7472360200200141046a2101200541046a22052003490d000b0b20084103712102200720066a21010b02402003200320026a22054f0d002002417f6a2108024020024107712207450d000340200320012d00003a0000200141016a2101200341016a21032007417f6a22070d000b0b20084107490d000340200320012d00003a0000200341016a200141016a2d00003a0000200341026a200141026a2d00003a0000200341036a200141036a2d00003a0000200341046a200141046a2d00003a0000200341056a200141056a2d00003a0000200341066a200141066a2d00003a0000200341076a200141076a2d00003a0000200141086a2101200341086a22032005470d000b0b20000b4b01017f23808080800041206b220524808080800020052001200220032004108f82808000200529031021042000200541186a29030037030820002004370300200541206a2480808080000b0bda0a0100418080c0000bd00a7372632f6c69622e7273616d6f756e74656e645f64617465686f7572735f6c6f67676564696470726f6f665f7665726966696564726174655f7065725f686f757273746172745f64617465737461747573746f6b656e776f726b65720a001000060000001000100008000000180010000c0000002400100002000000260010000e000000340010000d000000410010000a0000004b001000060000005100100005000000560010000600000063616e63656c6c656466696e616e63655f617070726f76656466696e616e63655f617070726f7665726d616e616765726d616e616765725f617070726f7665646f7261636c655f7075626b65796f7261636c655f726f746174696f6e737061796d656e7473000000ac00100009000000b500100010000000c500100010000000d500100007000000dc00100010000000ec0010000d000000f9001000100000000901100008000000457363726f77436f756e7400540110000b000000457363726f77000068011000060000004e6f6e6365000000780110000500000041646d696e000000880110000500000050656e64696e6741646d696e980110000c0000005061757365640000ac011000060000004f7261636c654b6579000000bc011000090000000300000000000000000000000000000004000000000000000000000000000000050000000000000000000000000000000143465750000000000010000a000000fd01000009000000000010000a0000000302000030000000000010000a000000f70100002700000000000000000000000000000000000000000010000a000000500200001e0000000000000000000000000010000a000000b302000025000000000010000a0000005d0200002b000000000010000a0000006c02000029000000000010000a0000006e02000015000000000010000a0000006102000024000000000010000a0000003c02000025000000000010000a0000004b0200000d000000000010000a000000ea0200003b000000000010000a000000a503000030000000000010000a000000a80300000d000000000010000a000000950300002c000000000010000a0000001104000030000000000010000a000000ee03000032000000000010000a000000fd03000030000000000010000a000000ff03000015000000000010000a000000f20300002b000000000010000a000000e403000027000000000010000a0000008e040000370000000000000000000000010000000100000063616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c7565436f6e76657273696f6e4572726f722f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f656e762e7273000000ca03100063000000770100000e0000002f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f73746f726167652e72730040041000670000009a000000090000007472616e73666572617474656d707420746f206164642077697468206f766572666c6f77c00410001c000000617474656d707420746f2073756274726163742077697468206f766572666c6f77000000e40410002100000063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75653a2000000001000000000000003b05100002000000008f460e636f6e74726163747370656376300000000400000000000000000000000d436f6e74726163744572726f7200000000000016000000000000000f416c7265616479417070726f7665640000000001000000000000000c556e617574686f72697a6564000000020000000000000016496e76616c69644f7261636c655369676e61747572650000000000030000000000000010496e76616c69645061796d656e744964000000040000000000000015496e73756666696369656e74417070726f76616c730000000000000500000000000000175061796d656e74416c726561647946696e616c697a65640000000006000000000000000d496e76616c6964416d6f756e7400000000000007000000000000000f457363726f7743616e63656c6c65640000000008000000000000000c496e76616c69644e6f6e63650000000900000000000000084e6f7441646d696e0000000a000000000000000650617573656400000000000b000000000000000f41646d696e416c7265616479536574000000000c000000000000000c50726f6f664d697373696e670000000d000000000000000d4e6f6e63654f766572666c6f770000000000000e00000000000000125369676e6572734e6f7444697374696e637400000000000f0000003b546865206f7261636c65207075626c6963206b6579206973206e6f74206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000164f7261636c654b65794e6f745265676973746572656400000000001000000042417474657374656420686f757273207820726174655f7065725f686f757220646f6573206e6f7420657175616c2074686520657363726f77656420616d6f756e742e000000000013416d6f756e74486f7572734d69736d6174636800000000110000002a656e645f64617465206973206e6f74207374726963746c792061667465722073746172745f646174652e00000000000d496e76616c6964506572696f64000000000000120000002642617463682065786365656473204d41585f42415443485f53495a45207061796d656e74732e00000000000d4261746368546f6f4c61726765000000000000130000004454686973205741534d2070696e7320616e2065787065637465642061646d696e20616e642074686520737570706c6965642061646472657373206973206e6f742069742e0000000d41646d696e4d69736d6174636800000000000014000000464e6f2061646d696e207472616e736665722069732070656e64696e672c206f72207468652063616c6c6572206973206e6f74207468652070726f706f7365642061646d696e2e00000000000e4e6f50656e64696e6741646d696e000000000015000000336075706772616465602072657175697265732074686520636f6e747261637420746f206265207061757365642066697273742e00000000094e6f74506175736564000000000000160000000300000000000000000000000d5061796d656e7453746174757300000000000005000000000000000750656e64696e670000000000000000000000000f4d616e61676572417070726f7665640000000001000000000000000f46696e616e6365417070726f7665640000000002000000000000000946696e616c697a656400000000000003000000000000000943616e63656c6c6564000000000000040000000100000000000000000000000f5061796d656e745363686564756c65000000000a0000000000000006616d6f756e7400000000000b0000000000000008656e645f6461746500000006000000000000000c686f7572735f6c6f676765640000000b00000000000000026964000000000004000000815365742074727565206f6e6c7920627920607375626d69745f686f7572735f70726f6f666020616674657220612076616c69642045643235353139206f7261636c650a7369676e61747572652e20607061795f626174636860207265667573657320746f20736574746c652061207061796d656e7420776974686f75742069742e0000000000000e70726f6f665f7665726966696564000000000001000000000000000d726174655f7065725f686f75720000000000000b000000000000000a73746172745f6461746500000000000600000000000000067374617475730000000007d00000000d5061796d656e745374617475730000000000008c5065722d7061796565205374656c6c617220417373657420436f6e7472616374202853414329206164647265737320e2809420652e672e2074686520555344432053414320666f720a6f6e6520706179656520616e6420746865206e617469766520584c4d2053414320666f7220616e6f746865722077697468696e207468652073616d652062617463682e00000005746f6b656e000000000000130000000000000006776f726b65720000000000130000000100000000000000000000000e436f7265466c6f77457363726f77000000000008000000000000000963616e63656c6c656400000000000001000000000000001066696e616e63655f617070726f76656400000001000000000000001066696e616e63655f617070726f7665720000001300000000000000076d616e61676572000000001300000000000000106d616e616765725f617070726f76656400000001000000000000000d6f7261636c655f7075626b6579000000000003ee000000200000004354696d657320746865206f7261636c65206b657920686173206265656e20726f7461746564206f6e207468697320657363726f772028617564697420747261696c292e00000000106f7261636c655f726f746174696f6e730000000400000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000002000000000000000000000007446174614b6579000000000700000000000000000000000b457363726f77436f756e7400000000010000000000000006457363726f77000000000001000000040000000100000000000000054e6f6e6365000000000000010000000400000000000000000000000541646d696e000000000000000000003d50726f706f736564206e6578742061646d696e2c206177616974696e6720616363657074616e6365202874776f2d737465702068616e646f766572292e0000000000000c50656e64696e6741646d696e0000000000000000000000065061757365640000000000010000004a52656769737465726564206f7261636c65207369676e696e67206b6579732e2050726573656e6365203d3e20747275737465642062792074686520706c6174666f726d2061646d696e2e0000000000094f7261636c654b657900000000000001000003ee0000002000000000000000c85365742074686520636f6e74726163742061646d696e206f6e63652c20696d6d6564696174656c79206166746572206465706c6f792e204964656d706f74656e742d67756172643a0a6661696c7320696620616e2061646d696e20697320616c726561647920636f6e666967757265642e204966206e657665722063616c6c65642c2074686520636f6e74726163740a73696d706c7920686173206e6f2061646d696e20616e642063616e206e6576657220626520706175736564206f722075706772616465642e0000000a696e69745f61646d696e000000000001000000000000000561646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000f85468652061646d696e20616464726573732062616b656420696e746f2074686973205741534d206174206275696c642074696d652c20696620616e792e0a0a526561642d6f6e6c792c20736f20616e206f70657261746f7220286f7220616e2061756469746f72292063616e20636f6e6669726d206166746572206465706c6f7920746861740a7468652072756e6e696e6720636f64652069732070696e6e656420746f20746865206b65792074686579206578706563742c20726174686572207468616e207472757374696e670a7468617420746865206465706c6f7920736372697074207761732072756e20636f72726563746c792e0000000e65787065637465645f61646d696e00000000000000000001000003e800000013000000000000010d50726f706f73652061206e65772061646d696e202863757272656e742061646d696e206f6e6c79292e20537465702031206f6620322e0a0a48616e646f7665722069732074776f2d73746570206265636175736520612073696e676c652d73746570207472616e7366657220746f2061206d69737479706564206f720a756e636f6e74726f6c6c65642061646472657373207065726d616e656e746c792064657374726f797320746865206162696c69747920746f2070617573652c20757067726164652c0a6f72206d616e61676520746865206f7261636c652072656769737472792e205468652070726f706f736564206b6579206d7573742070726f76652069742063616e207369676e2e0000000000000d70726f706f73655f61646d696e0000000000000100000000000000096e65775f61646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000004341636365707420612070656e64696e672061646d696e2068616e646f766572202870726f706f7365642061646d696e206f6e6c79292e20537465702032206f6620322e000000000c6163636570745f61646d696e0000000000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000345468652063757272656e746c7920636f6e666967757265642061646d696e2c206966206f6e6520686173206265656e207365742e000000096765745f61646d696e0000000000000000000001000003e80000001300000000000000865061757365206f7220756e70617573652073746174652d6368616e67696e67206f7065726174696f6e73202861646d696e206f6e6c79292e206063616e63656c5f657363726f77600a737461797320617661696c61626c65207768696c652070617573656420736f2066756e64732063616e20616c7761797320626520726566756e6465642e00000000000a7365745f706175736564000000000001000000000000000670617573656400000000000100000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000000000000969735f7061757365640000000000000000000001000000010000000000000076557067726164652074686520636f6e7472616374205741534d202861646d696e206f6e6c79292e20456e61626c657320666978657320776974686f7574206368616e67696e670a74686520636f6e74726163742061646472657373206f72206d6967726174696e6720657363726f772066756e64732e000000000007757067726164650000000001000000000000000d6e65775f7761736d5f68617368000000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000001e4526567697374657220616e206f7261636c65207369676e696e67206b657920617320747275737465642062792074686520706c6174666f726d202861646d696e206f6e6c79292e0a0a57485920412052454749535452593a2070726576696f75736c7920746865206d616e616765722070617373656420616e7920606f7261636c655f7075626b65796020746865790a6c696b656420696e746f2060696e697469616c697a655f6d756c74695f7369675f657363726f77602c20736f2061206d616e6167657220636f756c6420696e7374616c6c0a7468656972206f776e206b657920616e64207369676e207468656972206f776e2022766572696669656420776f726b22206174746573746174696f6e732e205468650a70726f6f662d6f662d776f726b206761746520776173207468657265666f7265206d616e616765722d61747465737461626c65202d2d2070726f6365647572616c2c206e6f740a63727970746f677261706869632e20457363726f7773206d6179206e6f77206f6e6c79206e616d652061206b6579207468652061646d696e2068617320726567697374657265642c0a7768696368206d616b657320746865206f7261636c6520616e20696e646570656e64656e7420706172747920627920636f6e737472756374696f6e2e0000001372656769737465725f6f7261636c655f6b6579000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019b5265766f6b6520612070726576696f75736c792072656769737465726564206f7261636c65206b6579202861646d696e206f6e6c79292e0a0a4578697374696e6720657363726f777320616c7265616479206e616d696e672074686973206b6579206b6565702066756e6374696f6e696e67202d2d207265766f6b696e672069730a6e6f7420726574726f6163746976652c20626563617573652073696c656e746c7920696e76616c69646174696e6720696e2d666c69676874206174746573746174696f6e730a776f756c6420737472616e642066756e64656420657363726f77732e2049742073746f707320746865206b6579206265696e67206e616d6564206279204e455720657363726f77730a616e64204e455720726f746174696f6e732e20546f207265746972652061206b65792066726f6d2061206c69766520657363726f772c20746865206d616e616765722063616c6c730a60726f746174655f6f7261636c655f6b6579602c207768696368207265766f6b6573207468617420657363726f7727732076657269666965642070726f6f66732e00000000117265766f6b655f6f7261636c655f6b65790000000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000325472756520696620607075626b657960206973206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000001869735f6f7261636c655f6b65795f726567697374657265640000000100000000000000067075626b65790000000003ee00000020000000010000000100000000000000d3526f7461746520746865206f7261636c65207075626c6963206b657920666f7220616e20657363726f772e205369676e6174757265732070726f6475636564206279207468650a72657469726564206b65792073746f7020766572696679696e6720696d6d6564696174656c792c2073696e636520607665726966795f6f7261636c655f776f726b602072656164730a746869732073746f726564206b65792e204d616e616765722d617574686f72697a65643b2072656675736564206f6e63652066756e64732068617665206d6f7665642e0000000011726f746174655f6f7261636c655f6b6579000000000000020000000000000009657363726f775f696400000000000004000000000000000a6e65775f7075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000a2496e697469616c697a652061206d756c74692d7369676e617475726520657363726f772077697468207061796d656e74207363686564756c657320616e64206f7261636c65207075626c6963206b65792e0a546865206f7261636c655f7075626b657920697320616e2045643235353139207075626c6963206b6579207573656420746f2076657269667920776f726b2070726f6f66207369676e6174757265732e00000000001b696e697469616c697a655f6d756c74695f7369675f657363726f77000000000400000000000000076d616e616765720000000013000000000000001066696e616e63655f617070726f76657200000013000000000000000d6f7261636c655f7075626b6579000000000003ee0000002000000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000001000003e900000004000007d00000000d436f6e74726163744572726f7200000000000000000001595375626d697420686f7572732070726f6f6620766572696669656420627920616e2045643235353139206f7261636c65207369676e61747572652e0a0a546865206f7261636c65207369676e7320746865203139382d6279746520646f6d61696e2d73657061726174656420707265696d61676520646f63756d656e746564206f6e0a606275696c645f70726f6f665f6d657373616765602028736368656d61207632292e2054686520636f6e74726163742072656275696c6473207468617420707265696d6167650a66726f6d2073746f7265642073746174652c20766572696669657320697420616761696e73742074686520657363726f772773206f7261636c65207075626c6963206b65792c0a656e666f726365732060686f75727320782072617465203d3d20616d6f756e74602c20616e6420636f6e73756d657320746865206e657874206578706563746564206e6f6e63652e000000000000127375626d69745f686f7572735f70726f6f660000000000050000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f6964000000000004000000000000000c686f7572735f6c6f676765640000000b00000000000000056e6f6e63650000000000000600000000000000097369676e6174757265000000000003ee0000004000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e4d616e6167657220617070726f76616c206f66207061796d656e7428732900000000000f6d616e616765725f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e46696e616e636520617070726f76616c206f66207061796d656e7428732900000000000f66696e616e63655f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000bb46696e616c697a65207061796d656e74206f6e636520626f746820617070726f76616c7320617265206f627461696e65640a4465707265636174656420616c6961732072657461696e656420736f20746865204d61696e6e65742d6465706c6f7965642041424920616e6420746865206578697374696e670a64617368626f61726420636c69656e74206b65657020776f726b696e672e204e65772063616c6c6572732073686f756c642075736520607061795f6261746368602e000000001066696e616c697a655f7061796d656e74000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f7200000000000000000000b2536574746c65206576657279207061796d656e7420696e2074686520657363726f773a206f6e65207472616e73616374696f6e2c206f6e6520534143207472616e73666572207065720a70617965652c206561636820696e20746861742070617965652773206f776e2061737365742e20526571756972657320626f746820617070726f76616c7320414e4420610a7665726966696564206f7261636c652070726f6f66206f6e20657665727920726f772e0000000000097061795f6261746368000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f72000000000000000000006e43616e63656c20616e20657363726f77202864697370757465207265736f6c7574696f6e20e28094206d616e61676572206f6e6c79292e0a416c6c6f776564206576656e207768696c65207061757365642028656d657267656e6379207769746864726177616c2070617468292e00000000000d63616e63656c5f657363726f77000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f720000000000000000000017526574726965766520657363726f772064657461696c73000000000a6765745f657363726f770000000000010000000000000009657363726f775f69640000000000000400000001000003e9000007d00000000e436f7265466c6f77457363726f770000000007d00000000d436f6e74726163744572726f720000000000000000000121457874656e6420616e20657363726f7727732073746f72616765206c69666574696d652e20416e796f6e65206d61792063616c6c20746869732e0a0a50657273697374656e7420656e747269657320746861742072756e206f7574206f662072656e742061726520617263686976656420746f2074686520457870697265640a537461746520537461636b20616e642063616e20626520726573746f7265643b207468657920617265206e6f742064656c657465642e20546865206661696c75726520746869730a61766f69647320697320612066756e64656420657363726f77206265636f6d696e672074656d706f726172696c7920756e757361626c6520756e74696c20736f6d656f6e650a7061797320746f20726573746f72652069742e00000000000011657874656e645f657363726f775f74746c000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019552657475726e2074686520657861637420627974657320746865206f7261636c65206d757374207369676e20666f722074686973207061796d656e742e0a0a526561642d6f6e6c792e204578706f73696e672074686520707265696d616765206d616b65732074686520434f4e5452414354207468652073696e676c6520736f75726365206f660a747275746820666f7220746865206d65737361676520666f726d61743a20616e206f66662d636861696e207369676e65722063616e2073696d756c61746520746869732063616c6c0a616e64207369676e207468652072657475726e656420627974657320766572626174696d20696e7374656164206f66207265696d706c656d656e74696e6720746865206c61796f75740a616e6420686f70696e67207468652074776f2061677265652e20457665727920686973746f726963616c206d69736d61746368206265747765656e2061207369676e657220616e640a6120766572696669657220697320612062756720746869732072656d6f76657320627920636f6e737472756374696f6e2e0000000000000e70726f6f665f707265696d6167650000000000040000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f69640000000000040000000000000005686f7572730000000000000b00000000000000056e6f6e63650000000000000600000001000003e90000000e000007d00000000d436f6e74726163744572726f7200000000000000000000ac52657475726e20746865206e657874206578706563746564206f7261636c65206e6f6e636520666f7220616e20657363726f772e0a546865206f7261636c65206d757374207369676e20612070726f6f66207573696e6720746869732065786163742076616c756520287265706c61792070726f74656374696f6e292e0a52657475726e73203020666f7220616e20756e6b6e6f776e2f756e696e697469616c697a656420657363726f772e000000096765745f6e6f6e6365000000000000010000000000000009657363726f775f6964000000000000040000000100000006001e11636f6e7472616374656e766d6574617630000000000000001400000000006f0e636f6e74726163746d65746176300000000000000005727376657200000000000006312e38352e3000000000000000000008727373646b7665720000002f32302e352e30233965326333303232623433353562323234613761383134653133626135313736316565623134626200" + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000007" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "oracle" + }, + { + "symbol": "reg" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "register_oracle_key" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow" + }, + { + "symbol": "created" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "add" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 250 + } + }, + { + "u64": 1000 + }, + { + "u64": 2000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize_multi_sig_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + }, + { + "u64": 0 + }, + { + "bytes": "bf478bdf94bc5ba3ecaa3cb75154a392a634cba815c16c605d6d1afa56687a4281408c3c06f6c006da673ab7964d1ed613b65d5c2f39f39fd0b59fd012033609" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "hours" + }, + { + "symbol": "submit" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_hours_proof" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "manager_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "manager" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "manager_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "system", + "body": { + "v0": { + "topics": [ + { + "symbol": "executable_update" + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + ], + "data": { + "vec": [] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": false + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": false + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_nonce" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "cancelled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approved" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finance_approver" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "manager" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "manager_approved" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "oracle_pubkey" + }, + "val": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + }, + { + "key": { + "symbol": "oracle_rotations" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "payments" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bytes": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "is_oracle_key_registered" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "finance_approve" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "symbol": "finance" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "finance_approve" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "pay_batch" + } + ], + "data": { + "u32": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPP4V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "paid" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "u32": 0 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "i128": { + "hi": 0, + "lo": 40 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "payment" + }, + { + "symbol": "final" + } + ], + "data": { + "vec": [ + { + "u32": 1 + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + }, + { + "u32": 1 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "pay_batch" + } + ], + "data": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "end_date" + }, + "val": { + "u64": 2000 + } + }, + { + "key": { + "symbol": "hours_logged" + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "proof_verified" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "rate_per_hour" + }, + "val": { + "i128": { + "hi": 0, + "lo": 250 + } + } + }, + { + "key": { + "symbol": "start_date" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDS3FDGQ4JA2V3F26Y4BMWWJEC5TT26RJBN7KIQKUMVO2MAOCMDTSZ7A" + } + }, + { + "key": { + "symbol": "worker" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739" + }, + { + "symbol": "balance" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "e5b28cd0e241aaecbaf638165ac920bb39ebd1485bf5220aa32aed300e130739", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "balance" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_admin_authorization.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_admin_authorization.1.json new file mode 100644 index 0000000..ede2a46 --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_admin_authorization.1.json @@ -0,0 +1,527 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_paused", + "args": [ + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "upgrade", + "args": [ + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": true + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da", + "code": "0061736d01000000019b022960037f7f7f017f60027f7f017f60047e7e7e7e017e60027e7e017e60037e7e7e017e6000017e60017e017e60037f7f7f0060027f7e0060047f7f7f7f0060057f7f7e7f7f0060027f7f017e60047f7f7f7e0060027e7f017e60017f017e60017e017f6000017f60017f0060017f017f60027f7e017f60057f7e7e7e7e0060057e7e7e7e7e017e60067f7f7e7e7e7e017f60027f7f0060067f7f7f7e7e7e0060077f7f7f7f7e7e7e017e60057f7f7f7f7f0060000060057f7e7e7f7f017e60057f7e7e7f7f0060057f7f7f7f7f017e60067f7e7f7f7f7f017e60037f7f7f017e60037f7e7e017f60037f7e7e017e60027f7e017e60047f7e7e7e017e60057f7e7e7e7e017e60037f7e7e0060047f7e7e7f0060067f7e7e7e7e7f0002d901240162013200020162013100020162016a0003016d01390004016d016100020176016700030178013000030178013100030178013300050178013600050178013700050178013800050169015f00060169013000060169013600030169013700060169013800060176015f0005017601300004017601310003017601330006017601360003016c015f0004016c01300003016c01310003016c01320003016c01360006016c01370002016c013800030164015f00040162015f00060162013400050162013800060163015f000601630130000401610130000603f301f1010707070708090a010b07070701070c070c070c070c0b0c0b010701070707070b07070707070707070d0e0b0b0b0b0b070b0b0b0b0b0b0b070b0b0b0107060f05060f0510051106120510060f060f060f060f0313021415160612061206170612061706120218060e1110120b1907070707071a070707070707070707070701111b0b0b0b0b0b0e0c1c1d1e1f20071120010107070b0912120e11122122072223230e2422232224222325230e230b09070e1c1d201e1f2022220e0e0e0e23232223230e242223222422222223252224230e23232423050717060f0e0f0808260007171a11010101001111271427281400140405017001040405030100110619037f01418080c0000b7f0041d08ac0000b7f0041d08ac0000b07ba031b066d656d6f727902000a696e69745f61646d696e00610e65787065637465645f61646d696e00630d70726f706f73655f61646d696e00640c6163636570745f61646d696e0066096765745f61646d696e00680a7365745f706175736564006a0969735f706175736564006c0775706772616465006e1372656769737465725f6f7261636c655f6b65790070117265766f6b655f6f7261636c655f6b657900721869735f6f7261636c655f6b65795f72656769737465726564007411726f746174655f6f7261636c655f6b657900761b696e697469616c697a655f6d756c74695f7369675f657363726f770078127375626d69745f686f7572735f70726f6f66007a0f6d616e616765725f617070726f7665007c0f66696e616e63655f617070726f7665007e1066696e616c697a655f7061796d656e740080010d63616e63656c5f657363726f770082010a6765745f657363726f7700840111657874656e645f657363726f775f74746c0086010e70726f6f665f707265696d616765008801096765745f6e6f6e6365008a01097061795f6261746368008001015f00a4010a5f5f646174615f656e6403010b5f5f686561705f626173650302090c010041010b03a201890288020adff901f1014602017f017e23808080800041106b220324808080800020032001200210a580808000200329030821042000200329030037030020002004370308200341106a2480808080000b6102017f017e23808080800041106b22032480808080002003200229030022041081828080000240024020032802000d00200329030821040c010b2001200410c38180800021040b2000420037030020002004370308200341106a2480808080000b6401027e02400240024020022903002203a741ff0171220241c000460d0020024106470d0142002104200310fc8180800021030c020b420021042001200310c48180800021030c010b4201210410f98180800021030b20002004370300200020033703080bf80304027f017e017f047e23808080800041d0006b22032480808080004100210402400340200441c000460d01200320046a4202370300200441086a21040c000b0b0240024002400240024002400240024002402002290300220542ff018342cc00520d0020012005419482c0800041082003410810af818080001a410120032d0000220441004741017420044101461b22044102460d01410120032d0008220241004741017420024101461b22024102460d02200341c0006a200341106a2001109c8180800020032802400d0320032903482105200341c0006a200341186a2001109c8180800020032802400d04410120032d0020220641004741017420064101461b22064102460d0520032903482107200341c0006a200341286a2001109a8180800020032802400d062003290330220842ff01834204520d0702402003290338220942ff018342cb00520d002003290348210a200020043a0026200020023a0025200020063a002420002008422088a7360220200020093703182000200a37031020002005370308200020073703000c090b200041023a00260c080b200041023a00260c070b200041023a00260c060b200041023a00260c050b200041023a00260c040b200041023a00260c030b200041023a00260c020b200041023a00260c010b200041023a00260b200341d0006a2480808080000b3b01017f23808080800041106b2202248080808000200220013703082000200241086a10d48180800010cc818080001a200241106a2480808080000b12002000200142012002200310aa808080000b270020002000200110ac808080002002200310fe81808000200410fe8180800010cd818080001a0b4d02017f017e41022102024020002000200110ac808080002203420110bf81808000450d00410121020240024020002003420110c081808000a741ff01710e020102000b000b410021020b20020bcb0502017f017e23808080800041306b220224808080800002400240024002400240024002400240024020012802000e0700010203040506000b200241206a200041e082c08000109f8180800020022802200d07200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c060b200241206a200041f082c08000109f8180800020022802200d0620022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d062002200229032837031020022003370308200241206a200241086a2000109d818080000c050b200241206a2000418083c08000109f8180800020022802200d0520022002290328370318200241186a10d4818080002103200241206a2000200141046a10918180800020022802200d052002200229032837031020022003370308200241206a200241086a2000109d818080000c040b200241206a2000419083c08000109f8180800020022802200d04200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c030b200241206a200041a483c08000109f8180800020022802200d03200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c020b200241206a200041b483c08000109f8180800020022802200d02200220022903283703082002200241086a10d481808000370318200241206a2000200241186a10c4808080000c010b200241206a200041c883c08000109f8180800020022802200d0120022002290328370318200241186a10d4818080002103200241206a200141086a200010a18180800020022802200d012002200229032837031020022003370308200241206a200241086a2000109d818080000b20022903282103200229032050450d00200241306a24808080800020030f0b000b5e01017e02400240024020012001200210ac808080002203420110bf818080000d00410021010c010b20012003420110c081808000220342ff01834204520d012003422088a72102410121010b20002002360204200020013602000f0b000b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200042003703000c010b200320012004420110c081808000370308200341106a2001200341086a10a68080800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b880102017f017e23808080800041306b220324808080800002400240024020012001200210ac808080002204420110bf818080000d00200041023a00260c010b200320012004420110c081808000370300200341086a2001200310a78080800020032d002e4102460d012000200341086a41281093828080001a0b200341306a2480808080000f0b000b160020002000200110ac80808000420110bf818080000b1000200020012002420110b2808080000b210020002000200110ac808080002002200010a781808000200310ca818080001a0b1000200020012002420110b4808080000b210020002000200110ac808080002000200210b980808000200310ca818080001a0b1000200020012002420110b6808080000b210020002000200110ac808080002002200010a681808000200310ca818080001a0b1000200020012002420110b8808080000b210020002000200110ac808080002000200210bb80808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110db80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b210020002000200110ac808080002002200010a881808000200310ca818080001a0b4502017f017e23808080800041106b220224808080800020022000200110a480808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4d02017f017e41022102024020002000200110ac808080002203420210bf81808000450d00410121020240024020002003420210c081808000a741ff01710e020102000b000b410021020b20020b900102017f017e23808080800041206b220324808080800002400240024020012001200210ac808080002204420210bf818080000d00200042003703000c010b200320012004420210c081808000370308200341106a2001200341086a10b18180800020032802104101460d012003290318210420004201370300200020043703080b200341206a2480808080000f0b000b160020002000200110ac80808000420210bf818080000b1000200020012002420210b6808080000b1000200020012002420210ba808080000b850102017f027e23808080800041106b220324808080800020032001200210b6818080000240024020032802000d00200320032903082204370300420121050240200341086a200410d08180800010ff8180800041c000470d0020002003290300370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b950203017f017e027f23808080800041c0006b22032480808080002001200210c380808000210420032001200241086a10c38080800037030820032004370300410021020240034020024110460d01200341106a20026a4202370300200241086a21020c000b0b200341246a200341106a200341106a41106a2003200341106a109681808000410020032802382202200328023422056b2206200620024b1b21022003280224200541037422066a2105200328022c20066a2106024003402002450d0120052006200110a981808000370300200541086a2105200641086a21062002417f6a21020c000b0b2001200341106a410210b08180800021042000420037030020002004370308200341c0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110d381808000024020022802004101470d00000b20022903082103200241106a24808080800020030b7902017f027e23808080800041206b2203248080808000200341106a2002200110a0818080000240024020032802100d00200320032903183703082001200341086a410110b0818080002104420021050c010b10f9818080002104420121050b2000200537030020002004370308200341206a2480808080000ba30102017f017e23808080800041206b2203248080808000200341106a200120021091818080000240024020032802100d0020032903182104200341106a2001200241046a10918180800020032802100d00200320032903183703082003200437030020012003410210b081808000210420004200370300200020043703080c010b10f981808000210420004201370300200020043703080b200341206a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200210918180800002400240024020032802200d0020032903282104200341206a2001200241046a10918180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd40102017f037e23808080800041306b2203248080808000200341206a2001200241086a10918180800002400240024020032802200d0020032903282104200341206a20022001109e8180800020032802200d0020032903282105200341206a2001200241106a1094818080002003290328210620032802200d012003200637031820032005370310200320043703082001200341086a410310b081808000210620004200370300200020063703080c020b10f98180800021060b20004201370300200020063703080b200341306a2480808080000bd00102017f027e23808080800041306b2203248080808000200341206a2001200241106a10918180800002400240024020032802200d0020032903282104200341206a200120021094818080002003290328210520032802200d01200341206a2001200241146a10918180800020032802200d002003200329032837031820032005370310200320043703082001200341086a410310b081808000210520004200370300200020053703080c020b10f98180800021050b20004201370300200020053703080b200341306a2480808080000bd20202017f067e23808080800041c0006b2203248080808000200341306a2001200241206a10918180800002400240024020032802300d0020032903382104200341306a2001200241246a10918180800020032802300d0020032903382105200341306a200241106a2001109e8180800020032802300d0020032903382106200341306a200241186a2001109e8180800020032802300d0020032903382107200341306a200120021094818080002003290338210820032802300d01200341306a2001200241306a1094818080002003290338210902402003280230450d00200921080c020b20032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410610b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341c0006a2480808080000bbd0302017f087e23808080800041d0006b2203248080808000200341c0006a2001200241386a10918180800002400240024020032802400d0020032903482104200341c0006a20012002413c6a10918180800020032802400d0020032903482105200341c0006a200241206a2001109e8180800020032802400d0020032903482106200341c0006a200241286a2001109e8180800020032802400d0020032903482107200341c0006a200120021094818080002003290348210820032802400d01200341c0006a2001200241106a1094818080002003290348210902402003280240450d00200921080c020b200341c0006a2001200241306a10a4808080002003290348210a02402003280240450d00200a21080c020b200341c0006a2001200241c0006a10a4808080002003290348210b02402003280240450d00200b21080c020b2003200b3703382003200a37033020032009370328200320083703202003200737031820032006370310200320053703082003200437030020012003410810b081808000210820004200370300200020083703080c020b10f98180800021080b20004201370300200020083703080b200341d0006a2480808080000b2a00024020022802000d0020004200370300200042023703080f0b2000200241086a2001109e818080000b4001017f23808080800041106b2202248080808000200220003703082001200241086a200110a88180800010ce818080002100200241106a24808080800020000b15002000280200417f6aad4220864283808080107c0b4502017f017e23808080800041106b220224808080800020022000200110c780808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1d00024020012802000d0020012903080f0b200141046a10cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c680808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6902027f017e23808080800041106b2202248080808000200141046a21030240024020012802000d00200220002003109181808000024020022802000d00200229030821040c020b10f9818080001a000b200310cd8080800021040b200241106a24808080800020040b4502017f017e23808080800041106b220224808080800020022000200110d380808000024020022802004101470d00000b20022903082103200241106a24808080800020030bd00302017f097e23808080800041e0006b2203248080808000200341d0006a200120021094818080000240024020032802500d0020032903582104200341d0006a2001200241c8006a10a48080800020032802500d0020032903582105200341d0006a2001200241106a10948180800020032802500d0020032903582106200341d0006a2001200241d0006a10918180800020032802500d0020032903582107200341d0006a2001200241d8006a10938180800020032802500d0020032903582108200341d0006a2001200241206a10948180800020032802500d0020032903582109200341d0006a2001200241c0006a10a48080800020032802500d002003290358210a2002350254210b200341d0006a200241386a2001109e8180800020032802500d002003290358210c200341d0006a200241306a2001109e8180800020032802500d00200320032903583703482003200c3703402003200b4220864204843703382003200a370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200141dc80c08000410a2003410a10ae81808000210420004200370300200020043703080c010b200042013703000b200341e0006a2480808080000b4502017f017e23808080800041106b220224808080800020022000200110c280808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110b781808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110ca80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b1700024020012802000d0042020f0b200110cd808080000b4502017f017e23808080800041106b220224808080800020022000200110c980808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c580808000024020022802004101470d00000b20022903082103200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110c880808000024020022802004101470d00000b20022903082103200241106a24808080800020030bf20202017f067e23808080800041d0006b2203248080808000200341c0006a2001200241266a1093818080000240024020032802400d0020032903482104200341c0006a2001200241256a10938180800020032802400d0020032903482105200341c0006a200241086a2001109e8180800020032802400d0020032903482106200341c0006a20022001109e8180800020032802400d0020032903482107200341c0006a2001200241246a10938180800020032802400d0020032903482108200341c0006a200241106a200110a18180800020032802400d0020032903482109200341c0006a2001200241206a10918180800020032802400d0020032003290348370330200320093703282003200837032020032007370318200320063703102003200537030820032004370300200320022903183703382001419482c0800041082003410810ae81808000210420004200370300200020043703080c010b200042013703000b200341d0006a2480808080000b6802017f017e23808080800041106b22022480808080000240024020012802000d0020022000200141086a10d381808000024020022802000d00200229030821030c020b10f9818080001a000b200141046a10cd8080800021030b200241106a24808080800020030b4502017f017e23808080800041106b220224808080800020022000200110cb80808000024020022802004101470d00000b20022903082103200241106a24808080800020030b6502017f017e23808080800041106b22022480808080000240024020012d00264102460d0020022000200110db80808000024020022802000d00200229030821030c020b10f9818080001a000b200110cd8080800021030b200241106a24808080800020030b2501017e20002903002202422088a72200410520004105491b4105200242ff01834204511b0b9e0502027f0b7e23808080800041f0006b22032480808080004100210402400340200441d000460d01200320046a4202370300200441086a21040c000b0b024002400240024002400240024002400240024002402002290300220542ff018342cc00520d002001200541dc80c08000410a2003410a10af818080001a200341d0006a2001200310928180800020032802500d01200341e8006a290300210520032903602106200341d0006a2001200341086a10a68080800020032802500d0220032903582107200341d0006a2001200341106a10928180800020032802500d032003290318220842ff01834204520d04410120032d0020220441004741017420044101461b22044102460d05200341e8006a29030021092003290360210a200341d0006a2001200341286a10928180800020032802500d06200341e8006a290300210b2003290360210c200341d0006a2001200341306a10a68080800020032802500d072003290358210d200341386a200410df8080800022024105460d08200341d0006a200341c0006a2001109c8180800020032802500d092003290358210e200341d0006a200341c8006a2001109c81808000024020032802500d002003290358210f2000200c3703202000200a37031020002006370300200020043a00582000200236025420002008422088a7360250200020073703482000200d3703402000200e3703382000200f3703302000200b37032820002009370318200020053703080c0b0b200041053602540c0a0b200041053602540c090b200041053602540c080b200041053602540c070b200041053602540c060b200041053602540c050b200041053602540c040b200041053602540c030b200041053602540c020b200041053602540c010b200041053602540b200341f0006a2480808080000b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e2808080003602082001200141086a10d7808080002100200141206a24808080800020000be90101027f23808080800041306b2201248080808000200120003703082001412f6a10bd81808000410c210202402001412f6a41d083c0800010be808080000d00200141086a10b2818080002001412f6a10bd818080002001412f6a41d083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ef2eed90b3703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020b200141306a24808080800020020b3d02017f017e23808080800041206b2200248080808000200042003703082000411f6a200041086a10dd808080002101200041206a24808080800020010b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a200110b181808000024020012802084101470d00000b2001200129031010e5808080003602082001200141086a10d7808080002100200141206a24808080800020000bdd0101027f23808080800041306b220124808080800020012000370308200141106a108c818080000240024020012802100d002001412f6a10bd818080002001412f6a41e083c08000200141086a10c0808080002001412f6a10bd818080002001412f6a418087014180d21f10c181808000200120012903083703202001428ed4b8bacdbed7013703182001428ee6aeb9ea043703102001412f6a2001412f6a200141106a10d480808000200141206a2001412f6a10a88180800010c2818080001a410021020c010b200128021421020b200141306a24808080800020020b3e02017f017e23808080800041106b2200248080808000200010e78080800036020c20002000410c6a10d7808080002101200041106a24808080800020010bad0203017f017e017f23808080800041306b22002480808080002000412f6a10bd81808000200041106a2000412f6a41e083c0800010bd808080000240024020002802104101470d00200020002903182201370308200041086a10b2818080002000412f6a10bd818080002000412f6a41d083c08000200041086a10c0808080002000412f6a10bd818080002000412f6a2000412f6a41e083c0800010ac80808000420210cb818080001a2000412f6a10bd818080002000412f6a418087014180d21f10c181808000200020013703202000428ef2b5958ab5023703182000428ee6aeb9ea043703102000412f6a2000412f6a200041106a10d480808000200041206a2000412f6a10a88180800010c2818080001a410021020c010b411521020b200041306a24808080800020020b4102017f017e23808080800041206b2200248080808000200041086a10e9808080002000411f6a200041086a10dd808080002101200041206a24808080800020010b3e01017f23808080800041106b22012480808080002001410f6a10bd8180800020002001410f6a41d083c0800010bd80808000200141106a2480808080000b5c01027f23808080800041106b2201248080808000410121020240024002402000a741ff01710e020102000b000b410021020b2001200210eb8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bdc0101017f23808080800041206b2201248080808000200120003a0007200141086a108c818080000240024020012802080d002001411f6a10bd818080002001411f6a41f083c08000200141076a10bf808080002001411f6a10bd818080002001411f6a418087014180d21f10c181808000200120012d00073a001e2001428ed2aadceeac033703102001428ee6aeb9ea043703082001411f6a2001411f6a200141086a10d4808080002001411e6a2001411f6a10a68180800010c2818080001a410021000c010b200128020c21000b200141206a24808080800020000b4102017f017e23808080800041106b2200248080808000200010ed808080003a000e2000410e6a2000410f6a10a6818080002101200041106a24808080800020010b4401027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000200141fd01710b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010ef808080003602082001200141086a10d7808080002100200141206a24808080800020000bcd0101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd81808000411621022001411f6a41f083c0800010bc8080800041fd0171450d01200120003703102001428ed4a9f3cdadeb013703082001428ee6aeb9ea043703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a2001411f6a10bd818080002001411f6a200010a880808000410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f1808080003602082001200141086a10d7808080002100200141206a24808080800020000be60101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418084c0800010b5808080002001411f6a10bd8180800020014106360200200120003703082001411f6a2001418087014180f6de0010a980808000200120003703102001428ed8ea1b3703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6801017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f3808080003602082001200141086a10d7808080002100200141206a24808080800020000bc20101027f23808080800041206b22012480808080002001108c818080000240024020012802000d002001411f6a10bd8180800020014106360200200120003703082001411f6a2001411f6a200110ac80808000420110cb818080001a200120003703102001428ed4b0faaebd033703082001428ed4b1d4f9a6033703002001411f6a2001411f6a200110d4808080002001411f6a200141106a10d58080800010c2818080001a410021020c010b200128020421020b200141206a24808080800020020b6b01017f23808080800041206b220124808080800020012000370300200141086a2001411f6a2001109b81808000024020012802084101470d00000b2001200129031010f5808080003a0008200141086a2001411f6a10a6818080002100200141206a24808080800020000b5101027f23808080800041206b22012480808080002001411f6a10bd8180800020014106360208200120003703102001411f6a200141086a10ab808080002102200141206a248080808000200241fd01710b7a01017f23808080800041206b2202248080808000200220013703000240200042ff01834204520d00200241086a2002411f6a2002109b8180800020022802084101460d0020022000422088a7200229031010f7808080003602082002200241086a10d7808080002100200241206a24808080800020000f0b000b990801087f2380808080004180036b2202248080808000200220013703000240108d8180800022030d00200241ff026a10bd81808000200241013602302002200036023420024190026a200241ff026a200241306a10af80808000024020022d00b60222034102460d002002280290022104200241086a41047220024190026a41047241221093828080001a200220033a002e20022004360208200220022d00b7023a002f200241086a10b2818080002002108e8180800022030d014108210320022d002e0d0141002103200241286a2204200229032010c88180800010ff818080002105024002400340024020052003470d00200220013703180240200228022841016a2203450d00200220033602282002200241ff026a10c5818080002201370340200241c8006a21062004200229032010c88180800010ff818080002107200241e9026a220841036a2109410021030340024020072003470d0020022001370320200241ff026a10bd8180800020024190026a41086a2203200241306a41086a22042903003703002002200229033037039002200241ff026a20024190026a200241086a10b380808000200241ff026a10bd81808000200320042903003703002002200229033037039002200241ff026a20024190026a418087014180f6de0010a980808000200220022802283602b401200220003602b0012002428ed4b9b3cebe03370398022002428ed4b1d4f9a60337039002200241ff026a200241ff026a20024190026a10d480808000200241ff026a200241b0016a10d98080800010c2818080001a410021030c080b4105210502402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e08080800020022802e40222054105460d06200241b0016a20024190026a41d4001093828080001a200220092800003600ab01200220082800003602a8010b200241d0006a200241b0016a41d4001093828080001a200220022800ab0136004b200220022802a801360248024020054105460d0020024190026a200241d0006a41d4001093828080001a2009200228004b36000020082002280248360000200241003a00e802200220053602e402200220062002290340200620024190026a10d28080800010c9818080002201370340200341016a21030c010b0b419884c08000108782808000000b418884c08000108c82808000000b02402004200229032010c88180800010ff8180800020034d0d00200220042002290320200310fe8180800010c7818080003703b00120024190026a2004200241b0016a10e080808000200341016a210320022802e402417d6a0e03020103010b0b41a884c08000108782808000000b410621030c020b000b410421030b20024180036a24808080800020030be70101017f23808080800041c0006b2204248080808000200420013703182004200037031020042002370320200441286a2004413f6a200441106a10b181808000024020042802284101460d0020042903302101200441286a2004413f6a200441186a10b18180800020042802284101460d0020042903302100200441286a2004413f6a200441206a109b8180800020042802284101460d00200342ff018342cb00520d00200441086a200120002004290330200310f980808000200420042903083702282004413f6a200441286a10d1808080002103200441c0006a24808080800020030f0b000bf01104077f027e037f017e23808080800041e0026b220524808080800020052002370320200520013703182005200337032820052004370330410121060240108d8180800022070d00200541186a10b2818080000240200541186a200541206a10b481808000450d00410f21070c010b0240200541386a2208200529033010c88180800010ff81808000450d0002402008200529033010c88180800010ff8180800041e4004d0d00411321070c020b200541286a108e8180800022070d01410021072008200529033010c88180800010ff81808000210920054188026a210a200541106a210b4200210c4200210d0340024002400240024020092007470d00200541df026a10bd818080002005200541df026a41b884c0800010ad808080004100210a02402005280204410020052802004101711b41016a220e450d002005200e36023c2005200541df026a10aa8180800037034020054188016a21062008200529033010c88180800010ff81808000210f02400340200f200a200f200a4b1b211003400240200a2010470d0041002107200541f3006a41003600002005410036027020052005290330370368200520033703602005200529032037035820052005290318370350200541df026a10bd81808000200541013602b8012005200e3602bc01200541df026a200541b8016a200541d0006a10b380808000200541df026a10bd81808000200541e0016a41086a2206200541b8016a41086a290300370300200520052903b8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541023602c8012005200e3602cc01200541df026a200541c8016a41d884c0800010b780808000200541df026a10bd818080002006200541c8016a41086a290300370300200520052903c8013703e001200541df026a200541e0016a418087014180f6de0010a980808000200541df026a10bd81808000200541df026a41b884c080002005413c6a10b180808000200541df026a10bd81808000200541df026a41b884c08000418087014180f6de0010a9808080002005200d3703f8012005200c3703f0012005200528023c3602e801200520052903183703e0012005428ed2eadca9bda3013703c8022005428ef8f49b8ad7023703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10ce8080800010c2818080001a2008200529033010c88180800010ff81808000210620054188026a21090340024020062007470d00200528023c2107410021060c0d0b02402008200529033010c88180800010ff8180800020074d0d00200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b4024105460d0820052903a002210220052903a80221012005290390022104200529039802210320052903e001210d20052903e801210c2005290380022111200520092903003703f801200520113703f0012005200c3703e8012005200d3703e0012005200528023c3602980220052003370388022005200437038002200520013703a00220052002370390022005200736029c022005428ed2a9133703c8022005428ef2b3d5ecb7d6013703c002200541df026a200541df026a200541c0026a10d480808000200541df026a200541e0016a10d68080800010c2818080001a200741016a21070c010b0b41e084c08000108782808000000b2008200529033010c88180800010ff81808000200a4d0d02200520082005290330200a10fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105460d05200a41016a210b200520052903980237034841002107024003400240200a2007470d00200542003703c802200542003703c002410021072008200529033010c88180800010ff81808000210a024003400240200a2007470d002005200541df026a200541c8006a10d1818080003703e001200541e0016a200541186a200541c0006a200541c0026a10d281808000200b210a0c070b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703d801200541e0016a2008200541d8016a10e08080800020052802b40222094105460d0a200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801024002402006200541c8006a10b481808000450d0020052903c80222022005290358220185427f852002200220017c20052903c002220120052903507c2204200154ad7c220185834200530d01200520043703c002200520013703c8020b200741016a21070c010b0b419085c08000108c82808000000b418085c08000108782808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c7818080003703c002200541e0016a2008200541c0026a10e08080800020052802b40222094105460d07200541d0006a200541e0016a41d4001093828080001a200520093602a401200520052903b8023703a801200741016a21072006200541c8006a10b481808000450d000b200b210a0c010b0b0b41a085c08000108782808000000b41f084c08000108782808000000b41c884c08000108c82808000000b2008200529033010c88180800010ff8180800020074d0d01200520082005290330200710fe8180800010c781808000370350200541e0016a2008200541d0006a10e08080800020052802b4024105470d020b000b41b085c08000108782808000000b4101210620052903e00122045020052903e80122024200532002501b0d01200529038002221150200a29030022014200532001501b0d01024020052903a80220052903a002560d00411221070c030b200541086a200420022011200110948280800002402005290308200b290300844200510d00411121070c030b0240200d200285427f85200d200d20027c200c20047c2202200c54ad7c220185834200530d00200741016a21072002210c2001210d0c010b0b41c085c08000108c82808000000b410721070b2000200736020420002006360200200541e0026a2480808080000bfd0101017f23808080800041d0006b22052480808080002005200337031020052002370308200520043703180240200042ff01834204520d00200142ff01834204520d00200541206a200541cf006a200541086a10928180800020052802204101460d00200541386a290300210320052903302102200541206a200541cf006a200541106a10a68080800020052802204101460d0020052903282104200541206a200541cf006a200541186a10c18080800020052802204101460d0020052000422088a72001422088a7200220032004200529032810fb808080003602202005200541206a10d7808080002100200541d0006a24808080800020000f0b000bef0801047f23808080800041d0026b2206248080808000200620053703200240108d8180800022070d00200641cf026a10bd818080002006410136025020062000360254200641d0016a200641cf026a200641d0006a10af808080000240024020062d00f60122074102460d0020062802d0012108200641286a410472200641d0016a41047241221093828080001a20062008360228200620062d00f7013a004f200620073a004e02402007410171450d00410821070c030b4101210720062d004c0d0220062d004d0d02200641c8006a2208200629034010c88180800010ff8180800020014b0d010b410421070c010b024002402008200629034010c88180800010ff8180800020014d0d00200620082006290340200110fe8180800010c7818080003703b002200641d0016a2008200641b0026a10e08080800020062802a40222074105470d01000b41d085c08000108782808000000b200641e0006a200641d0016a41d4001093828080001a200620073602b401200620062903a8023703b8012006410036021c200641086a2002200320062903800120064188016a2903002006411c6a1091828080000240200628021c450d00410721070c010b02402006290308200629036085200641106a290300200629036885844200510d00411121070c010b2006200641cf026a20002001200641e0006a2002200320041090818080003703c801200641cf026a10bd81808000200641cf026a200641386a200641c8016a200641206a10b981808000200641cf026a10bd81808000200641023602b002200620003602b402200641d0016a200641cf026a200641b0026a10ae808080004109210720062903d801420020062802d0011b2004520d0002402004427f520d00410e21070c010b2006200442017c3703c002200641cf026a10bd81808000200641d0016a41086a2207200641b0026a41086a2209290300370300200620062903b0023703d001200641cf026a200641d0016a200641c0026a10b780808000200641cf026a10bd8180800020072009290300370300200620062903b0023703d001200641cf026a200641d0016a418087014180f6de0010a9808080002006200337037820062002370370200641013a00b801200641d0016a200641e0006a41e0001093828080001a200620082006290340200110fe818080002008200641d0016a10d28080800010c681808000370340200641cf026a10bd818080002007200641d0006a41086a2208290300370300200620062903503703d001200641cf026a200641d0016a200641286a10b380808000200641cf026a10bd8180800020072008290300370300200620062903503703d001200641cf026a200641d0016a418087014180f6de0010a980808000200620033703e801200620023703e001200620013602d401200620003602d0012006428ef2aef9a9c7033703b8022006428ef0b79ddd053703b002200641cf026a200641cf026a200641b0026a10d480808000200641cf026a200641d0016a10d08080800010c2818080001a410021070b200641d0026a24808080800020070b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710fd8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbb0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a10b281808000024020012d002c450d00410121020c020b200141013a002c200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428eeeaad6b9b6ca013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710ff8080800036020c20012001410c6a10d7808080002100200141106a24808080800020000bbe0301037f23808080800041f0006b22012480808080000240108d8180800022020d00200141ef006a10bd81808000200141013602582001200036025c200141306a200141ef006a200141d8006a10af80808000024020012d005622024102460d0020012802302103200141086a410472200141306a41047241221093828080001a20012003360208200120012d00573a002f200120023a002e02402002410171450d00410821020c020b200141086a41086a10b281808000024020012d002d450d00410121020c020b200141013a002d200141ef006a10bd81808000200141306a41086a2202200141d8006a41086a220329030037030020012001290358370330200141ef006a200141306a200141086a10b380808000200141ef006a10bd818080002002200329030037030020012001290358370330200141ef006a200141306a418087014180f6de0010a980808000200120003602682001428ed4e8d9b9f6ae013703382001428ed4bbfaddae9b01370330200141ef006a200141ef006a200141306a10d480808000200141e8006a200141ef006a10a78180800010c2818080001a410021020c010b410421020b200141f0006a24808080800020020b4b01017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a71081818080002001200110cf808080002100200141106a24808080800020000bd20a04047f017e047f057e23808080800041b0026b22022480808080000240024002400240108d8180800022030d00200241af026a10bd818080002002410136023020022001360234200241b0016a200241af026a200241306a10af8080800020022d00d60122034102460d0120022802b0012104200241086a410472200241b0016a41047241221093828080001a20022004360208200220022d00d7013a002f200220033a002e02402003410171450d00410821030c030b200241086a10b2818080000240200241086a200241106a10b481808000450d00410f21030c030b4105210320022d002c4101470d0220022d002d4101470d0241002104200241286a2203200229032010c88180800010ff818080002105024002400340024020052004470d002002200241af026a10aa818080003703402002200241af026a10c5818080002206370348200241d0006a210720024180016a210820024188016a2109410021042003200229032010c88180800010ff818080002105200241e8006a210a4200210b4200210c02400340024020052004470d0020022006370320200241af026a10bd81808000200241b0016a41086a2203200241306a41086a2204290300370300200220022903303703b001200241af026a200241b0016a200241086a10b380808000200241af026a10bd8180800020032004290300370300200220022903303703b001200241af026a200241b0016a418087014180f6de0010a9808080002007200229034810c88180800010ff8180800021032002200c3703b8012002200b3703b001200220033602c401200220013602c0012002428ee2e6d9bb053703582002428ef2b3d5ecb7d601370350200241af026a200241af026a200241d0006a10d480808000200241af026a200241b0016a10da8080800010c2818080001a20002002290348370308200041003602000c0a0b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c78180800037039802200241b0016a200320024198026a10e0808080002002280284024105460d05200241d0006a200241b0016a41d4001093828080001a200241033602a40120022002290388023703a8012002200241af026a200910d1818080003703b001200241b0016a200241c0006a2008200241d0006a10d2818080000240200c2002290358220685427f85200c200c20067c200b2002290350220d7c220e200b54ad7c220f85834200530d00200220022903603703e0012002200d3703b001200220013602d00120022002290388013703c80120022002290380013703c001200220063703b8012002200a2903003703e801200220043602d4012002428ed2aeb30d3703a0022002428ef2b3d5ecb7d60137039802200241af026a200241af026a20024198026a10d480808000200241af026a200241b0016a10d88080800010c2818080001a200241b0016a200241d0006a41e0001093828080001a2002200720022903482007200241b0016a10d28080800010c9818080002206370348200441016a2104200e210b200f210c0c010b0b41f085c08000108c828080000c040b41e085c08000108782808000000b2003200229032010c88180800010ff8180800020044d0d01200220032002290320200410fe8180800010c781808000370350200241b0016a2003200241d0006a10e08080800020022802840222074105460d02024020074103460d00200441016a210420022d0088024101710d010b0b410d410620074103471b21030c040b418086c08000108782808000000b000b20004101360200200020033602040c020b410421030b20004101360200200020033602040b200241b0026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710838180800036020c20012001410c6a10d7808080002100200141106a24808080800020000b8b0d04087f017e017f027e23808080800041c0026b2201248080808000200141bf026a10bd818080002001410136023020012000360234200141d0016a200141bf026a200141306a10af808080000240024020012d00f60122024102460d0020012802d0012103200141086a410472200141d0016a41047241221093828080001a20012003360208200120012d00f7013a002f200120023a002e4108210320024101710d01200141086a10b28180800041002102200141286a2203200129032010c88180800010ff818080002104024002400340024020042002470d002001200141bf026a10aa8180800037034020014198016a2104410021052003200129032010c88180800010ff8180800021060240034020062005200620054b1b2107034020052108024020082007470d00200141013a002e2001200141bf026a10c58180800022093703c801200141d0016a2104410021022003200129032010c88180800010ff81808000210a03400240200a2002470d0020012009370320200141bf026a10bd81808000200141d0016a41086a200141306a41086a290300370300200120012903303703d001200141bf026a200141d0016a200141086a10b380808000200120003602602001428ee2aaf4ecc4023703d8012001428ef8f49b8ad7023703d001200141bf026a200141bf026a200141d0016a10d480808000200141e0006a200141bf026a10a78180800010c2818080001a410021030c0b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d08200141e0006a200141d0016a41d4001093828080001a20012903a8022109200120003602502001428ee2aaf4ecc4023703d8012001428ef2b3d5ecb7d6013703d00120012002360254200141bf026a200141bf026a200141d0016a10d480808000200141bf026a200141d0006a10d98080800010c2818080001a200141d0016a200141e0006a41d4001093828080001a200120093703a802200141043602a4022001200420012903c8012004200141d0016a10d28080800010c98180800022093703c801200241016a21020c010b0b419086c08000108782808000000b2003200129032010c88180800010ff8180800020084d0d02200120032001290320200810fe8180800010c781808000370360200141d0016a2003200141e0006a10e08080800020012802a4024105460d05200841016a21052001200129038802370348410021020340024020082002470d002001420037035820014200370350410021022003200129032010c88180800010ff8180800021080340024020082002470d002001290350420052200129035822094200552009501b450d052001200141bf026a200141c8006a10d1818080003703d001200141d0016a200141c0006a200141086a200141d0006a10d2818080000c050b024002402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c7818080003703c801200141d0016a2003200141c8016a10e08080800020012802a402220a4105470d010c0a0b41b086c08000108782808000000b200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b80102402004200141c8006a10b481808000450d000240200129035822092001290368220b85427f8520092009200b7c2001290350220b20012903607c220c200b54ad7c220b85834200530d002001200c3703502001200b3703580c010b41c086c08000108c82808000000b200241016a21020c000b0b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370350200141d0016a2003200141d0006a10e08080800020012802a402220a4105460d07200141e0006a200141d0016a41d4001093828080001a2001200a3602b401200120012903a8023703b801200241016a21022004200141c8006a10b481808000450d010c020b0b0b0b41d086c08000108782808000000b41a086c08000108782808000000b02402003200129032010c88180800010ff8180800020024d0d00200120032001290320200210fe8180800010c781808000370360200141d0016a2003200141e0006a10e080808000200241016a210220012802a402417d6a0e03030102010b0b41e086c08000108782808000000b000b410621030c010b410421030b200141c0026a24808080800020030b4e01017f23808080800041306b22012480808080000240200042ff01834204510d00000b20012000422088a71085818080002001412f6a200110de808080002100200141306a24808080800020000b7a01017f23808080800041c0006b22022480808080002002413f6a10bd81808000200241013602282002200136022c20022002413f6a200241286a10af808080000240024020022d00264102470d00200041023a0026200041043602000c010b2000200241281093828080001a0b200241c0006a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a710878180800036020c20012001410c6a10d7808080002100200141106a24808080800020000bd70201027f23808080800041306b22012480808080002001412f6a10bd8180800020014101360200200120003602044104210202402001412f6a200110b080808000450d002001412f6a10be8180800021022001412f6a10bd81808000200141106a41086a200141086a290300370300200120012903003703102001412f6a200141106a2002200210a9808080002001412f6a10bd8180800020014102360210200120003602142001412f6a200141106a2002200210a9808080002001412f6a10bd818080002001412f6a41b884c080002002200210a9808080002001412f6a10bd818080002001412f6a2002200210c18180800020012002360228200120003602242001428ee2f91c3703182001428ef8f49b8ad7023703102001412f6a2001412f6a200141106a10d4808080002001412f6a200141246a10d98080800010c2818080001a410021020b200141306a24808080800020020bcb0101017f23808080800041c0006b220424808080800020042003370308200420023703000240200042ff01834204520d00200142ff01834204520d00200441106a2004413f6a200410928180800020042802104101460d00200441286a290300210320042903202102200441106a2004413f6a200441086a10a68080800020042802104101460d00200441106a2000422088a72001422088a72002200320042903181089818080002004413f6a200441106a10dc808080002100200441c0006a24808080800020000f0b000b970301037f2380808080004180026b2206248080808000200641ff016a10bd81808000200641013602302006200136023420064190016a200641ff016a200641306a10af808080000240024020062d00b60122074102460d002006280290012108200641086a41047220064190016a41047241221093828080001a200620073a002e20062008360208200620062d00b7013a002f0240200641286a2207200629032010c88180800010ff8180800020024d0d00024002402007200629032010c88180800010ff8180800020024d0d00200620072006290320200210fe8180800010c78180800037033020064190016a2007200641306a10e08080800020062802e40122074105470d01000b41f086c08000108782808000000b200641306a20064190016a41d4001093828080001a2006200736028401200620062903e80137038801200641ff016a20012002200641306a200320042005109081808000210420004100360200200020043703080c020b20004281808080c0003703000c010b20004281808080c0003703000b20064180026a2480808080000b5101017f23808080800041106b22012480808080000240200042ff01834204510d00000b20012000422088a7108b818080003703002001410f6a200110bb808080002100200141106a24808080800020000b6502017f017e23808080800041306b22012480808080002001412f6a10bd81808000200141023602082001200036020c200141186a2001412f6a200141086a10ae808080002001280218210020012903202102200141306a2480808080002002420020001b0b860102027f017e23808080800041206b22012480808080002001411f6a10bd81808000200141086a2001411f6a41d083c0800010bd80808000410121020240024020012802084101470d00200120012903102203370300200110b28180800020002003370308410021020c010b2000410a3602040b20002002360200200141206a2480808080000b4901027f23808080800041106b22002480808080002000410f6a10bd818080002000410f6a41f083c0800010bc808080002101200041106a248080808000410b4100200141fd01711b0b7f01027f23808080800041206b22012480808080002001411f6a10bd818080004100210202402001411f6a41d083c0800010be80808000450d002001411f6a10bd818080002001410636020820012000290300370310410041102001411f6a200141086a10ab8080800041fd01711b21020b200141206a24808080800020020b4d02017f017e23808080800041106b2202248080808000200010bd8180800020022001290300200010cc808080003703002002410f6a200210b8818080002103200241106a24808080800020030b8b0f04017f017e087f017e23808080800041e0006b22072480808080002007200010cf818080002208370300200741086a21092007200920082009200810d08180800010ff8180800010fe81808000418184c08000410410ac81808000220837030020074180043b01382007200920082009200810d08180800010ff8180800010fe81808000200741386a410210ac818080003703002007200741df006a10bc81808000370330200741386a41186a220a4200370300200741386a41106a220b4200370300200741386a41086a220c420037030020074200370338200741306a41086a220d200741306a10d4818080004204200741386a412010ad81808000200741106a41186a220e200a290300370300200741106a41106a220f200b290300370300200741106a41086a2210200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac818080003703002007200010aa8180800037033020072000200741306a108f81808000370308200a4200370300200b4200370300200c420037030020074200370338200741086a41086a200741086a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341306a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800037030020072000200341386a108f81808000370330200a4200370300200b4200370300200c420037030020074200370338200d200741306a10d4818080004204200741386a412010ad81808000200e200a290300370300200f200b2903003703002010200c29030037030020072007290338370310200729030021082007200920082009200810d08180800010ff8180800010fe81808000200741106a412010ac8180800022083703002007200141187420014180fe03714108747220014108764180fe0371200141187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac8180800022083703002007200241187420024180fe03714108747220024108764180fe0371200241187672723602382007200920082009200810d08180800010ff8180800010fe81808000200741386a410410ac81808000221137030020072003290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703402007200341086a290300220842388620084280fe0383422886842008428080fc0783421886200842808080f80f834208868484200842088842808080f80f832008421888428080fc07838420084228884280fe038320084238888484843703382007200920112009201110d08180800010ff8180800010fe81808000200741386a411010ac8180800022083703002007200442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703402007200542388620054280fe0383422886842005428080fc0783421886200542808080f80f834208868484200542088842808080f80f832005421888428080fc07838420054228884280fe038320054238888484843703382007200920082009200810d08180800010ff8180800010fe81808000200741386a411010ac81808000220537030020072003290340220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac81808000220537030020072003290348220442388620044280fe0383422886842004428080fc0783421886200442808080f80f834208868484200442088842808080f80f832004421888428080fc07838420044228884280fe038320044238888484843703382007200920052009200510d08180800010ff8180800010fe81808000200741386a410810ac8180800022043703002007200642388620064280fe0383422886842006428080fc0783421886200642808080f80f834208868484200642088842808080f80f832006421888428080fc07838420064228884280fe03832006423888848484370338200920042009200410d08180800010ff8180800010fe81808000200741386a410810ac818080002104200741e0006a24808080800020040b190020004200370300200020023502004220864204843703080b7c01027e024002400240024020022903002203a741ff0171220241c500460d002002410b470d02200041106a20031080828080000c010b2001200310e58180800021042001200310e481808000210320002004370318200020033703100b420021030c010b200010f981808000370308420121030b200020033703000b130020004200370300200020023100003703080b4602017f017e23808080800041106b2203248080808000200320012002109581808000200329030821042000200329030037030020002004370308200341106a2480808080000b6d02017f027e23808080800041106b2203248080808000200320022903002204200241086a29030022051082828080000240024020032802000d00200329030821040c010b20012005200410e38180800021040b2000420037030020002004370308200341106a2480808080000b4b00200041003602102000200436020c2000200336020820002002360204200020013602002000200220016b410376220236021820002002200420036b410376220420022004491b3602140b3901017f23808080800041106b22032480808080002003200229020037020820002001200341086a109881808000200341106a2480808080000b6a02027f017e23808080800041106b22032480808080002003200228020022042002280204220210fa818080000240024020032802000d00200329030821050c010b20012004200210d78180800021050b2000420037030020002005370308200341106a2480808080000b5202017f017e23808080800041106b2203248080808000200320022903083703082003200229030037030020012003410210da8180800021042000420037030020002004370308200341106a2480808080000b0e00200020012001109b818080000b7d02017f027e23808080800041106b2203248080808000024002402002290300220442ff018342c800520d0020032004370308420121050240200341106a200410f58180800010ff818080004120470d0020002003290308370308420021050b200020053703000c010b200042013703000b200341106a2480808080000b2e01027e4201210302402001290300220442ff018342cd00520d0020002004370308420021030b200020033703000b0e002000200220011099818080000b130020004200370300200020012903003703080b5102017f017e23808080800041106b220324808080800020032001200210978180800042012104024020032802000d0020002003290308370308420021040b20002004370300200341106a2480808080000b130020004200370300200020012903003703080b130020004200370300200020012903003703080b1200200141bb87c08000410f108b828080000b0300000b02000b4502017f017e23808080800041106b2202248080808000200220002001109481808000024020022802004101470d00000b20022903082103200241106a24808080800020030b070020003100000b0d0020003502004220864204840b070020002903000b070020002903000b0a00200010df818080000b6001017f23808080800041106b22042480808080000240200020012903002002290300200310f28180800042ff01834202510d00419087c08000412b2004410f6a418087c0800041b088c08000108682808000000b200441106a2480808080000b12002000200120022003200410d5818080000b12002000200120022003200410d6818080000b12002000200120022003200410d8818080000b140020002001200220032004200510d9818080000b0e0020002001200210da818080000b2e01027e4201210302402002290300220442ff018342cd00520d0020002004370308420021030b200020033703000b1300200041086a200029030010f8818080001a0b5902017f017e23808080800041206b22032480808080002003200236020c20032001360208200341106a2000200341086a109781808000024020032802104101470d00000b20032903182104200341206a24808080800020040b11002000200110b58180800041ff0171450b2601017e417f200041086a2000290300200129030010db81808000220242005220024200531b0b2e01027e4201210302402002290300220442ff018342c800520d0020002004370308420021030b200020033703000b130020004200370300200020022903003703080b0f002000200129030010f6818080000b1a00200020012903002002290300200329030010f7818080001a0b1000200010dd8180800010ff818080000b1000200010e08180800010ff818080000b0a00200010de818080000b02000b6d01037f23808080800041106b22012480808080002001410f6a10ba818080002102024002402001410f6a10bb8180800022032002490d00200320026b41016a22020d0141a889c08000108c82808000000b41a889c08000108d82808000000b200141106a24808080800020020b140020002001200210ec8180800010fd818080000b0e0020002001200210ed818080000b1b002000200110fe81808000200210fe8180800010f1818080001a0b0e0020002001200210dc818080000b0c002000200110e1818080000b0c002000200110e2818080000b0a00200010e6818080000b1000200020012002200310e7818080000b0e0020002001200210e8818080000b0c002000200110e9818080000b0e0020002001200210ea818080000b1000200020012002200310eb818080000b0e0020002001200210ee818080000b0c002000200110ef818080000b12002000200120022003200410f0818080000b0c002000200110f3818080000b0a00200010f4818080000b0c002000200110f5818080000b070020012903000bdf0102027f027e23808080800041c0006b22042480808080002004200041086a220541b889c08000410810b381808000370308200129030021062002290300210720042005200310a5818080003703202004200737031820042006370310410021010340024020014118470d00410021010240034020014118460d01200441286a20016a200441106a20016a290300370300200141086a21010c000b0b20052000200441086a2005200441286a410310da8180800010ab81808000200441c0006a2480808080000f0b200441286a20016a4202370300200141086a21010c000b0b130020004200370300200020022903003703080b070020002903000b1e00200120022003ad4220864204842004ad4220864204841080808080000b1f00200120022003ad4220864204842004ad4220864204841081808080001a0b1a002001ad4220864204842002ad4220864204841082808080000b2e00024020022004460d00000b2001ad4220864204842003ad4220864204842002ad4220864204841083808080000b3000024020032005460d00000b20012002ad4220864204842004ad4220864204842003ad4220864204841084808080000b1a002001ad4220864204842002ad4220864204841085808080000b0c00200120021086808080000b0c00200120021087808080000b08001088808080000b08001089808080000b0800108a808080000b0800108b808080000b0a002001108c808080000b0a002001108d808080000b0c0020012002108e808080000b0a002001108f808080000b0a0020011090808080000b08001091808080000b0e002001200220031092808080000b0c00200120021093808080000b0a0020011094808080000b0c00200120021095808080000b0e002001200220031096808080000b0c00200120021097808080000b0c00200120021098808080000b0c00200120021099808080000b0a002001109a808080000b10002001200220032004109b808080000b0c0020012002109c808080000b0e00200120022003109d808080000b0a002001109e808080000b0800109f808080000b0a00200110a0808080000b0a00200110a1808080000b0e0020012002200310a2808080000b0a00200110a3808080000b0900428390808080010bb50102017f017e23808080800041106b220324808080800002400240200241094b0d00420021040340024020020d002000410036020020002004420886420e843703080c030b200341086a20012d000010fb81808000024020032d00084103460d0020002003290308370204200041013602000c030b200141016a21012002417f6a2102200442068620033100098421040c000b0b20002002360208200041003a0004200041013602000b200341106a2480808080000b820101017f410121020240200141ff017141df00460d000240200141506a41ff0171410a490d000240200141bf7f6a41ff0171411a490d0002402001419f7f6a41ff0171411a490d00200020013a0001200041013a00000f0b200141456a21020c020b2001414b6a21020c010b200141526a21020b200041033a0000200020023a00010b070020004208880b070020004201510b0b002000ad4220864204840b08002000422088a70b160020002001423f87370308200020014208873703000b3201017e420121020240200142ffffffffffffffff00560d0020002001420886420684370308420021020b200020023703000b5001017e42012103024020014280808080808080c0007c42ffffffffffffffff00560d0020012001852001423f87200285844200520d0020002001420886420b84370308420021030b200020033703000ba00601067f0240200028020022032000280208220472450d0002402004410171450d00200120026a210502400240200028020c22060d0041002107200121080c010b41002107200121080340200822042005460d020240024020042c00002208417f4c0d00200441016a21080c010b0240200841604f0d00200441026a21080c010b0240200841704f0d00200441036a21080c010b200441046a21080b200820046b20076a21072006417f6a22060d000b0b20082005460d00024020082c00002204417f4a0d0020044160491a0b024002402007450d00024020072002490d0020072002460d01410021040c020b200120076a2c000041404e0d00410021040c010b200121040b2007200220041b21022004200120041b21010b024020030d00200028021c20012002200028022028020c118080808000000f0b200028020421030240024020024110490d0020012002108a8280800021040c010b024020020d00410021040c010b2002410371210602400240200241044f0d0041002104410021070c010b2002410c712105410021044100210703402004200120076a22082c000041bf7f4a6a200841016a2c000041bf7f4a6a200841026a2c000041bf7f4a6a200841036a2c000041bf7f4a6a21042005200741046a2207470d000b0b2006450d00200120076a21080340200420082c000041bf7f4a6a2104200841016a21082006417f6a22060d000b0b02400240200320044d0d00200320046b2106024002400240410020002d0018220420044103461b22040e03020001020b20062104410021060c010b20064101762104200641016a41017621060b200441016a21042000280210210720002802202108200028021c210003402004417f6a2204450d0220002007200828021011818080800000450d000b41010f0b200028021c20012002200028022028020c118080808000000f0b0240200020012002200828020c11808080800000450d0041010f0b410021040340024020062004470d0020062006490f0b200441016a210420002007200828021011818080800000450d000b2004417f6a2006490f0b200028021c20012002200028022028020c118080808000000b4d01017f23808080800041206b22032480808080002003410036021020034101360204200342043702082003200136021c200320003602182003200341186a36020020032002108582808000000b3601017f23808080800041106b2202248080808000200241013b010c2002200136020820022000360204200241046a10a381808000000b8f0101017f23808080800041c0006b22052480808080002005200136020c2005200036020820052003360214200520023602102005410236021c200541c08ac08000360218200542023702242005418280808000ad422086200541106aad843703382005418380808000ad422086200541086aad843703302005200541306a360220200541186a2004108582808000000b130041908ac08000412b2000108482808000000b14002001200028020020002802041083828080000b180020002802002001200028020428020c118180808000000be90601087f024002402001200041036a417c71220220006b2203490d00200120036b22044104490d002004410371210541002106410021010240200220004622070d004100210102400240200020026b2208417c4d0d00410021090c010b4100210903402001200020096a22022c000041bf7f4a6a200241016a2c000041bf7f4a6a200241026a2c000041bf7f4a6a200241036a2c000041bf7f4a6a2101200941046a22090d000b0b20070d00200020096a21020340200120022c000041bf7f4a6a2101200241016a2102200841016a22080d000b0b200020036a210002402005450d0020002004417c716a22022c000041bf7f4a210620054101460d00200620022c000141bf7f4a6a210620054102460d00200620022c000241bf7f4a6a21060b20044102762108200620016a21030340200021042008450d02200841c001200841c001491b220641037121072006410274210541002102024020084104490d002004200541f007716a210941002102200421010340200128020c2200417f7341077620004106767241818284087120012802082200417f7341077620004106767241818284087120012802042200417f7341077620004106767241818284087120012802002200417f7341077620004106767241818284087120026a6a6a6a2102200141106a22012009470d000b0b200820066b2108200420056a2100200241087641ff81fc0771200241ff81fc07716a418180046c41107620036a21032007450d000b2004200641fc01714102746a22022802002201417f734107762001410676724181828408712101024020074101460d0020022802042200417f7341077620004106767241818284087120016a210120074102460d0020022802082202417f7341077620024106767241818284087120016a21010b200141087641ff811c71200141ff81fc07716a418180046c41107620036a0f0b024020010d0041000f0b2001410371210902400240200141044f0d0041002103410021020c010b2001417c712108410021034100210203402003200020026a22012c000041bf7f4a6a200141016a2c000041bf7f4a6a200141026a2c000041bf7f4a6a200141036a2c000041bf7f4a6a21032008200241046a2202470d000b0b2009450d00200020026a21010340200320012c000041bf7f4a6a2103200141016a21012009417f6a22090d000b0b20030b1a00200028021c20012002200028022028020c118080808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141dc89c0800036020820014204370210200141086a2000108582808000000b4301017f23808080800041206b2201248080808000200141003602182001410136020c200141888ac0800036020820014204370210200141086a2000108582808000000b5701017e02400240200341c000710d002003450d012002410020036b413f71ad8620012003413f71ad220488842101200220048821020c010b20022003413f71ad882101420021020b20002001370300200020023703080bf60804017f017e037f047e23808080800041b0016b2205248080808000420021060240024002400240024020047920037942c0007c20044200521ba7220720027920017942c0007c20024200521ba722084d0d002008413f4b0d01200741df004b0d02200720086b4120490d03200541a0016a2003200441e00020076b2209108e8280800020053502a00142017c210a4200210b420021060240024002400240034020054190016a2001200241c00020086b2208108e82808000200529039001210c0240200820094f0d00200541d0006a200320042008108e82808000024002402005290350220a50450d000c010b200c200a80210c0b200541c0006a200c420020032004109282808000024020012005290340220d5422082002200541c8006a290300220a542002200a511b0d002002200a7d2008ad7d21022001200d7d21012006200b200c7c220c200b54ad7c21060c0b0b200220047c200120037c2204200154ad7c200a7d2004200d54ad7d21022004200d7d21012006200c200b7c427f7c220c200b54ad7c21060c0a0b20054180016a200c200a80220c4200200820096b41ff00712208109082808000200541f0006a200c420020032004109282808000200541e0006a2005290370200541f0006a41086a290300200810908280800020054180016a41086a29030020067c2005290380012206200b7c220b200654ad7c210620072002200541e0006a41086a2903007d20012005290360220c54ad7d2202792001200c7d22017942c0007c20024200521ba722084d0d012008413f4d0d000b200350450d010c020b20012003542208200220045420022004511b450d02200b210c0c070b200120038021020b200120038221012006200b20027c220c200b54ad7c2106420021020c050b200220047d2008ad7d2102200120037d21012006200b42017c220c50ad7c21060c040b200220044200200120035a200220045a20022004511b22081b7d20012003420020081b220454ad7d2102200120047d21012008ad210c0c030b20012001200380220c20037e7d210142002106420021020c020b20022002200342ffffffff0f83220480220620037e7d4220862001422088220c842004802202422086200c200220037e7d422086200142ffffffff0f83842201200480220384210c2001200320047e7d210120024220882006842106420021020c010b200541306a2003200441c00020086b2208108e82808000200541206a200120022008108e8280800042002106200541106a200342002005290320200529033080220c4200109282808000200520044200200c42001092828080002005290310210a02400240200541086a290300200541106a41086a290300220d20052903007c220b200d54ad7c4200520d002001200a5422082002200b542002200b511b450d010b200420027c200320017c2201200354ad7c200b7d2001200a54ad7d2102200c427f7c210c2001200a7d21010c010b2002200b7d2008ad7d21022001200a7d2101420021060b200020013703102000200c3703002000200237031820002006370308200541b0016a2480808080000b5701017e02400240200341c000710d002003450d0120022003413f71ad2204862001410020036b413f71ad88842102200120048621010c010b20012003413f71ad862102420021010b20002001370300200020023703080bf50303017f027e027f23808080800041e0006b220624808080800042002107420021084100210902402001200284500d002003200484500d00420020037d2003200442005322091b2107420020017d20012002420053220a1b2108420020042003420052ad7c7d200420091b21032004200285210402400240420020022001420052ad7c7d2002200a1b2202500d0002402003500d00200641d0006a2007200320082002109282808000200641d8006a290300210141012109200629035021020c020b200641c0006a2008420020072003109282808000200641306a2002420020072003109282808000200641c0006a41086a290300220220062903307c2201200254200641306a41086a290300420052722109200629034021020c010b02402003500d00200641206a2007420020082002109282808000200641106a2003420020082002109282808000200641206a41086a290300220220062903107c2201200254200641106a41086a290300420052722109200629032021020c010b20062007200320082002109282808000200641086a290300210141002109200629030021020b420020027d20022004420053220a1b2108420020012002420052ad7c7d2001200a1b22072004854200590d00410121090b200520093602002000200737030820002008370300200641e0006a2480808080000b6e01067e2000200342ffffffff0f832205200142ffffffff0f8322067e22072003422088220820067e22062005200142208822097e7c22054220867c220a3703002000200820097e2005200654ad4220862005422088847c200a200754ad7c200420017e200320027e7c7c3703080ba50501087f02400240200241104f0d00200021030c010b02402000410020006b41037122046a220520004d0d002004417f6a2106200021032001210702402004450d002004210820002103200121070340200320072d00003a0000200741016a2107200341016a21032008417f6a22080d000b0b20064107490d000340200320072d00003a0000200341016a200741016a2d00003a0000200341026a200741026a2d00003a0000200341036a200741036a2d00003a0000200341046a200741046a2d00003a0000200341056a200741056a2d00003a0000200341066a200741066a2d00003a0000200341076a200741076a2d00003a0000200741086a2107200341086a22032005470d000b0b2005200220046b2208417c7122066a210302400240200120046a22074103710d00200520034f0d0120072101034020052001280200360200200141046a2101200541046a22052003490d000c020b0b200520034f0d002007410374220241187121042007417c71220941046a2101410020026b411871210a2009280200210203402005200220047620012802002202200a7472360200200141046a2101200541046a22052003490d000b0b20084103712102200720066a21010b02402003200320026a22054f0d002002417f6a2108024020024107712207450d000340200320012d00003a0000200141016a2101200341016a21032007417f6a22070d000b0b20084107490d000340200320012d00003a0000200341016a200141016a2d00003a0000200341026a200141026a2d00003a0000200341036a200141036a2d00003a0000200341046a200141046a2d00003a0000200341056a200141056a2d00003a0000200341066a200141066a2d00003a0000200341076a200141076a2d00003a0000200141086a2101200341086a22032005470d000b0b20000b4b01017f23808080800041206b220524808080800020052001200220032004108f82808000200529031021042000200541186a29030037030820002004370300200541206a2480808080000b0bda0a0100418080c0000bd00a7372632f6c69622e7273616d6f756e74656e645f64617465686f7572735f6c6f67676564696470726f6f665f7665726966696564726174655f7065725f686f757273746172745f64617465737461747573746f6b656e776f726b65720a001000060000001000100008000000180010000c0000002400100002000000260010000e000000340010000d000000410010000a0000004b001000060000005100100005000000560010000600000063616e63656c6c656466696e616e63655f617070726f76656466696e616e63655f617070726f7665726d616e616765726d616e616765725f617070726f7665646f7261636c655f7075626b65796f7261636c655f726f746174696f6e737061796d656e7473000000ac00100009000000b500100010000000c500100010000000d500100007000000dc00100010000000ec0010000d000000f9001000100000000901100008000000457363726f77436f756e7400540110000b000000457363726f77000068011000060000004e6f6e6365000000780110000500000041646d696e000000880110000500000050656e64696e6741646d696e980110000c0000005061757365640000ac011000060000004f7261636c654b6579000000bc011000090000000300000000000000000000000000000004000000000000000000000000000000050000000000000000000000000000000143465750000000000010000a000000fd01000009000000000010000a0000000302000030000000000010000a000000f70100002700000000000000000000000000000000000000000010000a000000500200001e0000000000000000000000000010000a000000b302000025000000000010000a0000005d0200002b000000000010000a0000006c02000029000000000010000a0000006e02000015000000000010000a0000006102000024000000000010000a0000003c02000025000000000010000a0000004b0200000d000000000010000a000000ea0200003b000000000010000a000000a503000030000000000010000a000000a80300000d000000000010000a000000950300002c000000000010000a0000001104000030000000000010000a000000ee03000032000000000010000a000000fd03000030000000000010000a000000ff03000015000000000010000a000000f20300002b000000000010000a000000e403000027000000000010000a0000008e040000370000000000000000000000010000000100000063616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c7565436f6e76657273696f6e4572726f722f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f656e762e7273000000ca03100063000000770100000e0000002f686f6d652f61726a6179726f73656c2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d313934396366386336623562353537662f736f726f62616e2d73646b2d32302e352e302f7372632f73746f726167652e72730040041000670000009a000000090000007472616e73666572617474656d707420746f206164642077697468206f766572666c6f77c00410001c000000617474656d707420746f2073756274726163742077697468206f766572666c6f77000000e40410002100000063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75653a2000000001000000000000003b05100002000000008f460e636f6e74726163747370656376300000000400000000000000000000000d436f6e74726163744572726f7200000000000016000000000000000f416c7265616479417070726f7665640000000001000000000000000c556e617574686f72697a6564000000020000000000000016496e76616c69644f7261636c655369676e61747572650000000000030000000000000010496e76616c69645061796d656e744964000000040000000000000015496e73756666696369656e74417070726f76616c730000000000000500000000000000175061796d656e74416c726561647946696e616c697a65640000000006000000000000000d496e76616c6964416d6f756e7400000000000007000000000000000f457363726f7743616e63656c6c65640000000008000000000000000c496e76616c69644e6f6e63650000000900000000000000084e6f7441646d696e0000000a000000000000000650617573656400000000000b000000000000000f41646d696e416c7265616479536574000000000c000000000000000c50726f6f664d697373696e670000000d000000000000000d4e6f6e63654f766572666c6f770000000000000e00000000000000125369676e6572734e6f7444697374696e637400000000000f0000003b546865206f7261636c65207075626c6963206b6579206973206e6f74206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000164f7261636c654b65794e6f745265676973746572656400000000001000000042417474657374656420686f757273207820726174655f7065725f686f757220646f6573206e6f7420657175616c2074686520657363726f77656420616d6f756e742e000000000013416d6f756e74486f7572734d69736d6174636800000000110000002a656e645f64617465206973206e6f74207374726963746c792061667465722073746172745f646174652e00000000000d496e76616c6964506572696f64000000000000120000002642617463682065786365656473204d41585f42415443485f53495a45207061796d656e74732e00000000000d4261746368546f6f4c61726765000000000000130000004454686973205741534d2070696e7320616e2065787065637465642061646d696e20616e642074686520737570706c6965642061646472657373206973206e6f742069742e0000000d41646d696e4d69736d6174636800000000000014000000464e6f2061646d696e207472616e736665722069732070656e64696e672c206f72207468652063616c6c6572206973206e6f74207468652070726f706f7365642061646d696e2e00000000000e4e6f50656e64696e6741646d696e000000000015000000336075706772616465602072657175697265732074686520636f6e747261637420746f206265207061757365642066697273742e00000000094e6f74506175736564000000000000160000000300000000000000000000000d5061796d656e7453746174757300000000000005000000000000000750656e64696e670000000000000000000000000f4d616e61676572417070726f7665640000000001000000000000000f46696e616e6365417070726f7665640000000002000000000000000946696e616c697a656400000000000003000000000000000943616e63656c6c6564000000000000040000000100000000000000000000000f5061796d656e745363686564756c65000000000a0000000000000006616d6f756e7400000000000b0000000000000008656e645f6461746500000006000000000000000c686f7572735f6c6f676765640000000b00000000000000026964000000000004000000815365742074727565206f6e6c7920627920607375626d69745f686f7572735f70726f6f666020616674657220612076616c69642045643235353139206f7261636c650a7369676e61747572652e20607061795f626174636860207265667573657320746f20736574746c652061207061796d656e7420776974686f75742069742e0000000000000e70726f6f665f7665726966696564000000000001000000000000000d726174655f7065725f686f75720000000000000b000000000000000a73746172745f6461746500000000000600000000000000067374617475730000000007d00000000d5061796d656e745374617475730000000000008c5065722d7061796565205374656c6c617220417373657420436f6e7472616374202853414329206164647265737320e2809420652e672e2074686520555344432053414320666f720a6f6e6520706179656520616e6420746865206e617469766520584c4d2053414320666f7220616e6f746865722077697468696e207468652073616d652062617463682e00000005746f6b656e000000000000130000000000000006776f726b65720000000000130000000100000000000000000000000e436f7265466c6f77457363726f77000000000008000000000000000963616e63656c6c656400000000000001000000000000001066696e616e63655f617070726f76656400000001000000000000001066696e616e63655f617070726f7665720000001300000000000000076d616e61676572000000001300000000000000106d616e616765725f617070726f76656400000001000000000000000d6f7261636c655f7075626b6579000000000003ee000000200000004354696d657320746865206f7261636c65206b657920686173206265656e20726f7461746564206f6e207468697320657363726f772028617564697420747261696c292e00000000106f7261636c655f726f746174696f6e730000000400000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000002000000000000000000000007446174614b6579000000000700000000000000000000000b457363726f77436f756e7400000000010000000000000006457363726f77000000000001000000040000000100000000000000054e6f6e6365000000000000010000000400000000000000000000000541646d696e000000000000000000003d50726f706f736564206e6578742061646d696e2c206177616974696e6720616363657074616e6365202874776f2d737465702068616e646f766572292e0000000000000c50656e64696e6741646d696e0000000000000000000000065061757365640000000000010000004a52656769737465726564206f7261636c65207369676e696e67206b6579732e2050726573656e6365203d3e20747275737465642062792074686520706c6174666f726d2061646d696e2e0000000000094f7261636c654b657900000000000001000003ee0000002000000000000000c85365742074686520636f6e74726163742061646d696e206f6e63652c20696d6d6564696174656c79206166746572206465706c6f792e204964656d706f74656e742d67756172643a0a6661696c7320696620616e2061646d696e20697320616c726561647920636f6e666967757265642e204966206e657665722063616c6c65642c2074686520636f6e74726163740a73696d706c7920686173206e6f2061646d696e20616e642063616e206e6576657220626520706175736564206f722075706772616465642e0000000a696e69745f61646d696e000000000001000000000000000561646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000f85468652061646d696e20616464726573732062616b656420696e746f2074686973205741534d206174206275696c642074696d652c20696620616e792e0a0a526561642d6f6e6c792c20736f20616e206f70657261746f7220286f7220616e2061756469746f72292063616e20636f6e6669726d206166746572206465706c6f7920746861740a7468652072756e6e696e6720636f64652069732070696e6e656420746f20746865206b65792074686579206578706563742c20726174686572207468616e207472757374696e670a7468617420746865206465706c6f7920736372697074207761732072756e20636f72726563746c792e0000000e65787065637465645f61646d696e00000000000000000001000003e800000013000000000000010d50726f706f73652061206e65772061646d696e202863757272656e742061646d696e206f6e6c79292e20537465702031206f6620322e0a0a48616e646f7665722069732074776f2d73746570206265636175736520612073696e676c652d73746570207472616e7366657220746f2061206d69737479706564206f720a756e636f6e74726f6c6c65642061646472657373207065726d616e656e746c792064657374726f797320746865206162696c69747920746f2070617573652c20757067726164652c0a6f72206d616e61676520746865206f7261636c652072656769737472792e205468652070726f706f736564206b6579206d7573742070726f76652069742063616e207369676e2e0000000000000d70726f706f73655f61646d696e0000000000000100000000000000096e65775f61646d696e0000000000001300000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000004341636365707420612070656e64696e672061646d696e2068616e646f766572202870726f706f7365642061646d696e206f6e6c79292e20537465702032206f6620322e000000000c6163636570745f61646d696e0000000000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000345468652063757272656e746c7920636f6e666967757265642061646d696e2c206966206f6e6520686173206265656e207365742e000000096765745f61646d696e0000000000000000000001000003e80000001300000000000000865061757365206f7220756e70617573652073746174652d6368616e67696e67206f7065726174696f6e73202861646d696e206f6e6c79292e206063616e63656c5f657363726f77600a737461797320617661696c61626c65207768696c652070617573656420736f2066756e64732063616e20616c7761797320626520726566756e6465642e00000000000a7365745f706175736564000000000001000000000000000670617573656400000000000100000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000000000000969735f7061757365640000000000000000000001000000010000000000000076557067726164652074686520636f6e7472616374205741534d202861646d696e206f6e6c79292e20456e61626c657320666978657320776974686f7574206368616e67696e670a74686520636f6e74726163742061646472657373206f72206d6967726174696e6720657363726f772066756e64732e000000000007757067726164650000000001000000000000000d6e65775f7761736d5f68617368000000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000001e4526567697374657220616e206f7261636c65207369676e696e67206b657920617320747275737465642062792074686520706c6174666f726d202861646d696e206f6e6c79292e0a0a57485920412052454749535452593a2070726576696f75736c7920746865206d616e616765722070617373656420616e7920606f7261636c655f7075626b65796020746865790a6c696b656420696e746f2060696e697469616c697a655f6d756c74695f7369675f657363726f77602c20736f2061206d616e6167657220636f756c6420696e7374616c6c0a7468656972206f776e206b657920616e64207369676e207468656972206f776e2022766572696669656420776f726b22206174746573746174696f6e732e205468650a70726f6f662d6f662d776f726b206761746520776173207468657265666f7265206d616e616765722d61747465737461626c65202d2d2070726f6365647572616c2c206e6f740a63727970746f677261706869632e20457363726f7773206d6179206e6f77206f6e6c79206e616d652061206b6579207468652061646d696e2068617320726567697374657265642c0a7768696368206d616b657320746865206f7261636c6520616e20696e646570656e64656e7420706172747920627920636f6e737472756374696f6e2e0000001372656769737465725f6f7261636c655f6b6579000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019b5265766f6b6520612070726576696f75736c792072656769737465726564206f7261636c65206b6579202861646d696e206f6e6c79292e0a0a4578697374696e6720657363726f777320616c7265616479206e616d696e672074686973206b6579206b6565702066756e6374696f6e696e67202d2d207265766f6b696e672069730a6e6f7420726574726f6163746976652c20626563617573652073696c656e746c7920696e76616c69646174696e6720696e2d666c69676874206174746573746174696f6e730a776f756c6420737472616e642066756e64656420657363726f77732e2049742073746f707320746865206b6579206265696e67206e616d6564206279204e455720657363726f77730a616e64204e455720726f746174696f6e732e20546f207265746972652061206b65792066726f6d2061206c69766520657363726f772c20746865206d616e616765722063616c6c730a60726f746174655f6f7261636c655f6b6579602c207768696368207265766f6b6573207468617420657363726f7727732076657269666965642070726f6f66732e00000000117265766f6b655f6f7261636c655f6b65790000000000000100000000000000067075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000325472756520696620607075626b657960206973206f6e207468652061646d696e2d6d616e616765642072656769737472792e00000000001869735f6f7261636c655f6b65795f726567697374657265640000000100000000000000067075626b65790000000003ee00000020000000010000000100000000000000d3526f7461746520746865206f7261636c65207075626c6963206b657920666f7220616e20657363726f772e205369676e6174757265732070726f6475636564206279207468650a72657469726564206b65792073746f7020766572696679696e6720696d6d6564696174656c792c2073696e636520607665726966795f6f7261636c655f776f726b602072656164730a746869732073746f726564206b65792e204d616e616765722d617574686f72697a65643b2072656675736564206f6e63652066756e64732068617665206d6f7665642e0000000011726f746174655f6f7261636c655f6b6579000000000000020000000000000009657363726f775f696400000000000004000000000000000a6e65775f7075626b65790000000003ee0000002000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000a2496e697469616c697a652061206d756c74692d7369676e617475726520657363726f772077697468207061796d656e74207363686564756c657320616e64206f7261636c65207075626c6963206b65792e0a546865206f7261636c655f7075626b657920697320616e2045643235353139207075626c6963206b6579207573656420746f2076657269667920776f726b2070726f6f66207369676e6174757265732e00000000001b696e697469616c697a655f6d756c74695f7369675f657363726f77000000000400000000000000076d616e616765720000000013000000000000001066696e616e63655f617070726f76657200000013000000000000000d6f7261636c655f7075626b6579000000000003ee0000002000000000000000087061796d656e7473000003ea000007d00000000f5061796d656e745363686564756c650000000001000003e900000004000007d00000000d436f6e74726163744572726f7200000000000000000001595375626d697420686f7572732070726f6f6620766572696669656420627920616e2045643235353139206f7261636c65207369676e61747572652e0a0a546865206f7261636c65207369676e7320746865203139382d6279746520646f6d61696e2d73657061726174656420707265696d61676520646f63756d656e746564206f6e0a606275696c645f70726f6f665f6d657373616765602028736368656d61207632292e2054686520636f6e74726163742072656275696c6473207468617420707265696d6167650a66726f6d2073746f7265642073746174652c20766572696669657320697420616761696e73742074686520657363726f772773206f7261636c65207075626c6963206b65792c0a656e666f726365732060686f75727320782072617465203d3d20616d6f756e74602c20616e6420636f6e73756d657320746865206e657874206578706563746564206e6f6e63652e000000000000127375626d69745f686f7572735f70726f6f660000000000050000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f6964000000000004000000000000000c686f7572735f6c6f676765640000000b00000000000000056e6f6e63650000000000000600000000000000097369676e6174757265000000000003ee0000004000000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e4d616e6167657220617070726f76616c206f66207061796d656e7428732900000000000f6d616e616765725f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000001e46696e616e636520617070726f76616c206f66207061796d656e7428732900000000000f66696e616e63655f617070726f766500000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f7200000000000000000000bb46696e616c697a65207061796d656e74206f6e636520626f746820617070726f76616c7320617265206f627461696e65640a4465707265636174656420616c6961732072657461696e656420736f20746865204d61696e6e65742d6465706c6f7965642041424920616e6420746865206578697374696e670a64617368626f61726420636c69656e74206b65657020776f726b696e672e204e65772063616c6c6572732073686f756c642075736520607061795f6261746368602e000000001066696e616c697a655f7061796d656e74000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f7200000000000000000000b2536574746c65206576657279207061796d656e7420696e2074686520657363726f773a206f6e65207472616e73616374696f6e2c206f6e6520534143207472616e73666572207065720a70617965652c206561636820696e20746861742070617965652773206f776e2061737365742e20526571756972657320626f746820617070726f76616c7320414e4420610a7665726966696564206f7261636c652070726f6f66206f6e20657665727920726f772e0000000000097061795f6261746368000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ea000007d00000000f5061796d656e745363686564756c6500000007d00000000d436f6e74726163744572726f72000000000000000000006e43616e63656c20616e20657363726f77202864697370757465207265736f6c7574696f6e20e28094206d616e61676572206f6e6c79292e0a416c6c6f776564206576656e207768696c65207061757365642028656d657267656e6379207769746864726177616c2070617468292e00000000000d63616e63656c5f657363726f77000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f720000000000000000000017526574726965766520657363726f772064657461696c73000000000a6765745f657363726f770000000000010000000000000009657363726f775f69640000000000000400000001000003e9000007d00000000e436f7265466c6f77457363726f770000000007d00000000d436f6e74726163744572726f720000000000000000000121457874656e6420616e20657363726f7727732073746f72616765206c69666574696d652e20416e796f6e65206d61792063616c6c20746869732e0a0a50657273697374656e7420656e747269657320746861742072756e206f7574206f662072656e742061726520617263686976656420746f2074686520457870697265640a537461746520537461636b20616e642063616e20626520726573746f7265643b207468657920617265206e6f742064656c657465642e20546865206661696c75726520746869730a61766f69647320697320612066756e64656420657363726f77206265636f6d696e672074656d706f726172696c7920756e757361626c6520756e74696c20736f6d656f6e650a7061797320746f20726573746f72652069742e00000000000011657874656e645f657363726f775f74746c000000000000010000000000000009657363726f775f69640000000000000400000001000003e9000003ed00000000000007d00000000d436f6e74726163744572726f72000000000000000000019552657475726e2074686520657861637420627974657320746865206f7261636c65206d757374207369676e20666f722074686973207061796d656e742e0a0a526561642d6f6e6c792e204578706f73696e672074686520707265696d616765206d616b65732074686520434f4e5452414354207468652073696e676c6520736f75726365206f660a747275746820666f7220746865206d65737361676520666f726d61743a20616e206f66662d636861696e207369676e65722063616e2073696d756c61746520746869732063616c6c0a616e64207369676e207468652072657475726e656420627974657320766572626174696d20696e7374656164206f66207265696d706c656d656e74696e6720746865206c61796f75740a616e6420686f70696e67207468652074776f2061677265652e20457665727920686973746f726963616c206d69736d61746368206265747765656e2061207369676e657220616e640a6120766572696669657220697320612062756720746869732072656d6f76657320627920636f6e737472756374696f6e2e0000000000000e70726f6f665f707265696d6167650000000000040000000000000009657363726f775f696400000000000004000000000000000a7061796d656e745f69640000000000040000000000000005686f7572730000000000000b00000000000000056e6f6e63650000000000000600000001000003e90000000e000007d00000000d436f6e74726163744572726f7200000000000000000000ac52657475726e20746865206e657874206578706563746564206f7261636c65206e6f6e636520666f7220616e20657363726f772e0a546865206f7261636c65206d757374207369676e20612070726f6f66207573696e6720746869732065786163742076616c756520287265706c61792070726f74656374696f6e292e0a52657475726e73203020666f7220616e20756e6b6e6f776e2f756e696e697469616c697a656420657363726f772e000000096765745f6e6f6e6365000000000000010000000000000009657363726f775f6964000000000000040000000100000006001e11636f6e7472616374656e766d6574617630000000000000001400000000006f0e636f6e74726163746d65746176300000000000000005727376657200000000000006312e38352e3000000000000000000008727373646b7665720000002f32302e352e30233965326333303232623433353562323234613761383134653133626135313736316565623134626200" + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "set_paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "paused" + } + ], + "data": { + "bool": true + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_paused" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "system", + "body": { + "v0": { + "topics": [ + { + "symbol": "executable_update" + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] + }, + { + "vec": [ + { + "symbol": "Wasm" + }, + { + "bytes": "496379c637132afc0c6f07e4da0afc00eeb882c459b36fb20e6e22c3c7a5d6da" + } + ] + } + ], + "data": { + "vec": [] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": "void" + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_pause_first.1.json b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_pause_first.1.json new file mode 100644 index 0000000..a0fd68c --- /dev/null +++ b/contracts/core-flow/test_snapshots/test/tests/test_upgrade_requires_pause_first.1.json @@ -0,0 +1,326 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 20, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "init_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "admin" + }, + { + "symbol": "init" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "bytes": "0707070707070707070707070707070707070707070707070707070707070707" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 22 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 22 + } + } + ], + "data": { + "vec": [ + { + "string": "contract try_call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "bytes": "0707070707070707070707070707070707070707070707070707070707070707" + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/docs/AUDIT_BASELINE.md b/docs/AUDIT_BASELINE.md new file mode 100644 index 0000000..eb1e849 --- /dev/null +++ b/docs/AUDIT_BASELINE.md @@ -0,0 +1,326 @@ +# CoreFlow β€” Engineering Baseline Audit + +**Date:** 2026-09-10 +**Branch audited:** `instawards/pay-batch-sac` @ `301145a` +**Method:** direct source inspection + executed test suites + deployed env inspection. +Documentation claims were *not* taken as evidence. + +--- + +## 0. Executive summary + +CoreFlow is in substantially better shape than a typical hackathon repo. The Soroban +contract is real: `pay_batch` performs genuine per-payee SAC transfers, escrow custody +is funded on creation, Ed25519 verification is wired to the host function, replay +protection uses a monotonic nonce watermark, and 40 Rust tests pass. This is not a mock +contract. + +The problem is not the contract. **The problem is that the deployed product cannot reach it, +and the security model has a gap that makes its central claim untrue.** + +Three findings dominate everything else: + +1. **The live application performs no on-chain operations at all.** Production is + misconfigured such that every contract call is guaranteed to fail (Β§F-1). What the + public site demonstrates is the mock path. +2. **Oracle attestations were mintable by anyone on the internet** (Β§F-2) β€” fixed in this + pass. The proof-of-work gate that `pay_batch` enforces was, until now, unguarded at + the only place that issues proofs. +3. **Amounts are denominated in cents but settled as stroops** (Β§F-4), a silent + 100,000Γ— under-payment on any batch that does reach the chain. + +Test baseline as found: **40 Rust** (1 ignored), **79 TypeScript**, typecheck clean. +After this pass: **40 Rust**, **86 TypeScript**, typecheck clean. + +--- + +## 1. Category A β€” Production-ready + +| Component | Evidence | +|---|---| +| Soroban escrow core | `contracts/core-flow/src/lib.rs`; 40 passing tests incl. multi-asset settlement, custody-sum fuzz invariant, 50-payee E2E | +| Real SAC token settlement | `pay_batch` / `initialize_multi_sig_escrow` call `TokenClient::transfer`; verified by `test_pay_batch_settles_two_assets_in_one_call` | +| Ed25519 host verification | `env.crypto().ed25519_verify`; correctly returns `()` and traps rather than a bool that could only ever be `true` | +| Replay protection (nonce) | Monotonic watermark in persistent storage; O(1), rejects everything at/below watermark | +| Signature-before-nonce ordering | Prevents burning an escrow's nonce sequence with garbage signatures | +| Dual-control invariant | `SignersNotDistinct` rejects `manager == finance_approver` at creation *and* at settlement | +| Wallet auth (challenge/response) | SEP-53 prefix + SHA-256 + Ed25519; challenges are single-use via atomic `updateMany`, 5-min TTL | +| Session revocation | Role and wallet re-read from DB on every request β€” a stale JWT role claim is never trusted | +| Overflow safety (contract) | `overflow-checks = true` in release profile; arithmetic traps rather than wraps | +| Rust toolchain pin | `rust-toolchain.toml` documents a genuinely load-bearing 1.85.0 window | + +## 2. Category B β€” Functional, needs hardening + +- **Rate limiter** is in-memory per-instance (`src/lib/ratelimit.ts`). Correct interface, but + on Vercel each lambda has its own Map, so effective limits are ~NΓ— the configured value. +- **Indexer** has cursor + idempotency via `ChainEvent.id` (RPC paging token). Lacks + reconciliation *reporting* β€” it projects chainβ†’DB but never surfaces a mismatch. +- **Audit log** exists and is written on privileged actions, but nothing enforces + append-only; any DB write path can mutate history. +- **`/api/admin/bootstrap`** has no rate limit and compares the secret with `!==` + (non-constant-time). Brute-forcing `BOOTSTRAP_SECRET` grants ADMIN. +- **Error surfaces** are mostly generic strings, not the what/why/next-step model the + product needs. + +## 3. Category C β€” Partially implemented + +- **Bulk Pay** (`src/app/bulk-pay/page.tsx`) is a real client against real contract + methods, but is hardcoded to `const ESCROW_ID = 1` and its role selector is a + cosmetic client-side dropdown. It is a demo harness, not a product workflow. + (On-chain `require_auth` still enforces the real authorization, so this is a product + gap rather than a security hole.) +- **Receipts** β€” `PaymentReceipt.tsx` renders, but takes a hardcoded PHP conversion and + is not wired to settled on-chain state. +- **Observability** β€” logger + Sentry configs exist; no request IDs, no health-gated + readiness signal for the indexer. + +## 4. Category D β€” Documented but not implemented + +- **`.env.example`** describes granting "manager/finance/worker" roles via + `POST /api/admin/roles`. The Prisma enum has exactly two values: `ADMIN`, `EMPLOYEE`. + The five-role RBAC model in the brief does not exist. +- **Multi-tenancy** β€” no `Organization` model exists. Every escrow row is global. There is + no tenant boundary to test. +- **README traction claims** β€” the mainnet contract is genuinely deployed and the + evidence TSVs contain real hashes. But those transactions were produced by + `scripts/`, **not** by the deployed application, which (per Β§F-1) cannot transact. + The README does not draw that distinction, and it needs to. + +## 5. Category E β€” Missing + +- Organizations / teams / tenant isolation +- Explicit payment state machine (`PREPARING β†’ … β†’ CONFIRMED` + failure states) +- DB↔chain reconciliation reporting +- Idempotency keys on financial mutation endpoints +- Structured request-ID correlation +- CSV staged-approval workflow (upload β†’ preview β†’ batch β†’ approvals) + +--- + +## 6. Category F β€” Dangerous + +Ordered by severity. **[FIXED]** items were remediated in this pass; the rest are open. + +### F-1 Β· Live production cannot execute any on-chain operation β€” **[FIXED IN CODE β€” needs redeploy]** +Production env has `NEXT_PUBLIC_STELLAR_CONTRACT_ID=""` and `NEXT_PUBLIC_STELLAR_NETWORK=""`. +Both are empty strings, which are falsy, so `src/lib/config.ts` falls back to: +- contract ID β†’ hardcoded **mainnet** `CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW` +- network β†’ `'testnet'` β†’ testnet RPC + testnet passphrase + +Every read (`get_escrow`, `get_nonce`) targets a mainnet contract over testnet RPC and +fails. `NEXT_PUBLIC_STELLAR_TOKEN_ID` is also unset, so escrow creation throws +*"Settlement token not configured"* before building a transaction. + +**Impact:** the public site demonstrates only the mock path. Any claim that the deployed +app settles on-chain is currently false. + +**Fix (code):** `src/lib/config.ts` no longer falls back to a hard-coded mainnet address. +`requireContractId()` throws a clear error when unset; the network resolves to `testnet` +for any unrecognised value, so the app never silently selects a chain where funds are +real. `NetworkBadge` renders the active network persistently, styling Mainnet as a +warning and calling out an unconfigured contract instead of showing green. + +**Still required from the operator:** set `NEXT_PUBLIC_STELLAR_CONTRACT_ID`, +`NEXT_PUBLIC_STELLAR_NETWORK` and `NEXT_PUBLIC_STELLAR_TOKEN_ID` in the deployment, then +redeploy. Until then the app correctly refuses on-chain actions rather than failing +opaquely. + +### F-2 Β· Oracle attestations were mintable by anyone β€” **[FIXED]** +`POST /api/submit-batch` had **no authentication and no rate limit**, and was absent from +the middleware matcher. It signs the Ed25519 attestations that set `proof_verified`, which +is the sole gate `pay_batch` enforces before releasing funds. + +Anyone on the internet could mint the proof-of-work half of the security model. + +**Fix:** requires a verified session, **and** that the caller equals the escrow's +**on-chain manager** (read live from the contract, not from request content). Added +rate limiting and audit events. 7 regression tests in +`src/app/api/__tests__/submit-batch.route.test.ts`, including a signed-in worker and a +platform ADMIN who is not the manager β€” both rejected, with `signHoursProof` asserted +un-called. + +### F-3 Β· Publicly-known JWT signing key fallback β€” **[FIXED]** +`src/lib/env.ts` defaulted `AUTH_SECRET` to the literal +`'default_super_secret_coreflow_jwt_key_32bytes'` β€” 44 chars, so it satisfied the +`min(32)` guard. Any deployment missing the variable would sign sessions with a key +published in the repo, allowing anyone to forge an ADMIN JWT. +**Fix:** default removed; a missing `AUTH_SECRET` is now a startup failure. + +### F-4 Β· Money unit mismatch: cents settled as stroops β€” **[FIXED]** +`CreateEscrowModal` collects dollars β†’ `Math.floor(parseFloat(x) * 100)` = **cents**. +`useDashboard.ts:563` passes that straight through as the on-chain amount: +`amount: BigInt(amountCents)`. Stellar assets use **7 decimals**. + +`$250.50` β†’ `25050` base units β†’ **0.0025050 USDC** actually escrowed and paid, while the +dashboard renders `amountCents / 100` = "$250.50". + +**Impact:** a 100,000Γ— under-settlement that the UI reports as success. This is precisely +the "never show success unless the chain agrees" failure mode. + +**Fix:** new `src/lib/money.ts` parses decimal *strings* into `bigint` base units β€” money +never touches a JS `number`, because `parseFloat('0.1') * 100` is `10.000000000000002`. +`CreateEscrowModal` emits base units and previews the exact on-chain figures; +`useDashboard` passes them through unchanged; the indexer stops casting on-chain `bigint` +through `Number` (lossy above 2^53βˆ’1). 21 tests in `src/lib/__tests__/money.test.ts`. + +### F-5 Β· The dual-approval flow cannot execute β€” **[FIXED]** +`useDashboard.ts:575` calls +`submitInitializeEscrow(walletAddress, walletAddress, …)` β€” manager and finance are the +**same address**. The contract rejects exactly this with `SignersNotDistinct` (#15). + +**Impact:** the primary escrow-creation path traps 100% of the time. Separation of duties β€” +the product's core value proposition β€” has no working code path in the app. + +**Fix:** `CreateEscrowModal` now collects a distinct finance approver, and refuses one +equal to the manager (or to the worker being paid) before a transaction is built. +`useDashboard` passes it through instead of duplicating the manager. + +### F-6 Β· Oracle message has no domain separation β€” **[FIXED]** +The signed message is 32 bytes: `escrow_id β€– payment_id β€– hours β€– nonce`. It omits the +network passphrase, contract address, worker address, and amount. + +**Consequences:** a signature valid on testnet is valid on **mainnet** for the same +`(escrow_id, payment_id, hours, nonce)`; a signature for contract A is valid on contract B. + +**Fix β€” schema v2 (`CFWP`, 198 bytes):** magic, version, `network_id`, contract digest, +worker digest, token digest, escrow id, payment id, amount, hours, period start/end, +nonce. Every field except hours and nonce is read from **stored escrow state**, so a +caller cannot retarget a signature. The contract also exposes `proof_preimage`, making it +the single source of truth for what must be signed. See `docs/ORACLE.md`. + +### F-7 Β· The manager controls the oracle key β€” **[FIXED]** +`initialize_multi_sig_escrow` accepts `oracle_pubkey` **from the manager**, and +`rotate_oracle_key` is manager-authorized. A manager can install their own key and sign +their own "verified work" attestations. + +**Impact:** `proof_verified` is manager-attestable, so "funds only move against proof of +work" is procedural, not cryptographic. Economic damage is bounded (custody is the +manager's own funds), but the *stated security property* does not hold. +**Fix:** an admin-managed on-chain registry β€” `register_oracle_key`, `revoke_oracle_key`, +`is_oracle_key_registered`. `initialize_multi_sig_escrow` and `rotate_oracle_key` both +refuse a key that is not registered, so rotation cannot be used as a back door. An +admin-less deployment keeps the v1 trust model rather than bricking (no registry +authority exists, so no key could satisfy the check). + +### F-8 Β· Attested "hours" are just the payment amount β€” **[FIXED]** +`src/app/api/submit-batch/route.ts`: `const hours = Math.max(1, Math.round(Number(payee.amount)))`. + +The oracle attests to a number derived from the amount being paid, carrying zero +information about work performed. Separately, `submit_hours_proof` never validates +`amount` against `hours Γ— rate_per_hour`, so verified hours have **no effect on the amount +paid**. The attestation is a rubber stamp on both ends. + +**Fix:** the contract enforces `hours Γ— rate_per_hour == amount` (`AmountHoursMismatch`, +#17), and rejects at creation any amount that no whole number of hours can reach β€” so +custody is never funded into an escrow that cannot settle. Server-side, hours are now +**derived from the on-chain payment row** rather than taken from the upload, and +`/api/submit-batch` refuses a CSV whose payees do not match the funded escrow. + +**Known limitation:** this makes hours whole numbers. Fractional-hour payroll needs a +scaled-hours field, which would be a v3 schema change. + +### F-9 Β· `init_admin` is front-runnable, and admin can drain everything β€” **OPEN, high** +`init_admin` is first-caller-wins. If it is not called in the same operational step as +deploy, anyone may claim admin, then call `upgrade()` to replace the contract WASM and +transfer out all escrow custody. Even when correctly claimed, `upgrade()` is an +unrestricted centralization risk that must be disclosed, not hidden. + +### F-10 Β· 32-bit money column β€” **[FIXED]** +`Escrow.amountCents` was Prisma `Int` β†’ PG `INTEGER`, overflowing at **$21,474,836.47**. + +**Fix:** replaced with `amountBaseUnits BigInt`, `rateBaseUnits BigInt` and +`assetDecimals Int`, plus a `financeApprover` column. Migration +`20260910000000_money_base_units` scales existing cent values by 10^5 so displayed +figures stay stable. BigInt values are serialized as strings at the API boundary β€” +`JSON.stringify` throws on bigint outright, so an unconverted value is a 500, not a +silent rounding bug. + +### F-11 Β· `BOOTSTRAP_SECRET` brute-forceable β€” **OPEN, medium** +`/api/admin/bootstrap` is deliberately exempt from session auth, has no rate limit, and +uses a non-constant-time `!==` comparison. Success grants ADMIN. + +### F-12 Β· Live production secrets sat one `git add -A` from publication β€” **[FIXED]** +`prodenv.txt` (a `vercel env pull` dump containing live `ORACLE_SECRET_KEY`, `AUTH_SECRET`, +`BOOTSTRAP_SECRET`, and database credentials) was untracked but **not** covered by +`.gitignore`. +**Fix:** `.gitignore` now excludes `prodenv.txt` and `*env*.txt`. + +> **⚠ These secrets must still be rotated.** Gitignoring the file does not undo the +> exposure β€” it has existed in plaintext in a working tree. Rotate `ORACLE_SECRET_KEY`, +> `AUTH_SECRET`, `BOOTSTRAP_SECRET`, and the database credentials. +> Note that rotating `ORACLE_SECRET_KEY` invalidates every escrow whose stored +> `oracle_pubkey` is the old key; those escrows need `rotate_oracle_key` or cancellation. + +### F-13 Β· Storage TTL can strand funds β€” **OPEN, medium** +Escrow data lives in persistent storage with a 90-day extension applied **on write only**. +Reads do not extend TTL. An escrow left idle past the TTL loses its data while its custody +remains in the contract β€” permanently unrecoverable, since every entry point loads the +escrow first. + +--- + +## 7. Changes made in this pass + +| Change | Where | Verification | +|---|---|---| +| Gate oracle signing behind session + on-chain manager check | `src/app/api/submit-batch/route.ts`, `src/middleware.ts` | 10 route tests | +| Remove publicly-known `AUTH_SECRET` fallback | `src/lib/env.ts` | typecheck | +| Bulk Pay performs full challenge/verify sign-in | `src/app/bulk-pay/page.tsx` | typecheck, build | +| Exclude env dumps from git | `.gitignore` | `git check-ignore` | +| Oracle attestation schema v2 (domain separation) | `contracts/core-flow/src/lib.rs`, `src/lib/oracle/index.ts`, `scripts/oracle-cli.mjs` | cross-language vector, 16 TS + 5 Rust tests | +| `proof_preimage` β€” contract as source of truth for the message | `contracts/core-flow/src/lib.rs` | `test_contract_preimage_matches_independent_implementation` | +| Admin-managed oracle key registry | `contracts/core-flow/src/lib.rs` | 6 Rust tests | +| `hours Γ— rate == amount` invariant | `contracts/core-flow/src/lib.rs` | 2 Rust tests | +| Batch cap + pay-period validation | `contracts/core-flow/src/lib.rs` | 2 Rust tests | +| Exact-decimal money module (base units, `bigint`) | `src/lib/money.ts` | 21 tests | +| Distinct finance approver in escrow creation | `src/components/modals/CreateEscrowModal.tsx`, `src/hooks/useDashboard.ts` | 11 modal tests | +| DB money β†’ `BigInt` base units + `assetDecimals` | `prisma/schema.prisma`, migration | typecheck, route tests | +| Fail-closed network/contract config + visible network badge | `src/lib/config.ts`, `src/components/NetworkBadge.tsx` | build | + +### Verification after this pass + +| Check | Result | +|---|---| +| Rust contract tests | **54 passed**, 1 ignored (was 40) | +| TypeScript tests | **128 passed** (was 79) | +| `tsc --noEmit` | clean | +| `next build` | succeeds | +| `cargo build --target wasm32v1-none --release` | succeeds (38 KB) | + +The single ignored Rust test is pre-existing and documented: `ed25519_verify` traps +non-catchably in native `cargo test`, so a bad-signature rejection cannot be asserted +with `#[should_panic]`. The same limitation is why oracle-registry authorization is +asserted via `env.auths()` rather than by calling unauthorized. + +--- + +## 8. Recommended execution order + +**P0 β€” done in this pass, except where noted** +1. ~~Rotate all exposed secrets (F-12).~~ **STILL REQUIRED β€” operator action.** +2. ~~Fail-closed network/contract config (F-1).~~ Code done; **needs env set + redeploy.** +3. ~~Fix the cents/stroops denomination end-to-end (F-4).~~ +4. ~~Give escrow creation a real distinct finance approver (F-5).~~ + +**P1 β€” done in this pass, except where noted** +5. ~~Domain-separate the oracle payload (F-6).~~ Schema v2, cross-language pinned. +6. ~~Admin-managed oracle key allowlist (F-7).~~ +7. ~~Bind attested hours to the amount (F-8).~~ +8. Harden `init_admin` / disclose `upgrade` authority (F-9); rate-limit bootstrap (F-11). + **Still open.** +9. Storage TTL can strand funds (F-13). **Still open.** + +**P2 β€” product architecture** +9. `Organization` model + tenant isolation, with explicit isolation tests. +10. Five-role RBAC + endpoint permission matrix. +11. Payment state machine, idempotency keys, DB↔chain reconciliation reporting. + +**P3 β€” product surface** +12. Bulk Pay as a real staged workflow (not `ESCROW_ID = 1`). +13. Dashboard, landing page, receipts, demo mode. +14. README rewritten to separate *deployed*, *testnet-validated*, and *script-generated* + evidence from what the application itself does. + +> Ordering rationale: every P1 item is a statement the product makes to investors about +> its security. Building UI on top of claims that are not yet true increases the surface +> that has to be walked back later. diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md new file mode 100644 index 0000000..fe7537b --- /dev/null +++ b/docs/BACKLOG.md @@ -0,0 +1,117 @@ +# Engineering backlog + +Known gaps that are deliberately **not** being fixed in the current phase, recorded +so they cannot quietly become permanent. + +--- + +## 1. Test files are not typechecked + +**Status:** open. Raised 2026-09-11 during the funding API work. + +### What + +[`tsconfig.json`](../tsconfig.json) excludes test files from the compiler: + +```json +"exclude": ["node_modules", "test", "e2e", "playwright.config.ts", + "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**"] +``` + +So `npm run typecheck` checks production code only. A test file can reference a +function with the wrong arguments, the wrong types, or a parameter that no longer +exists, and nothing objects until runtime β€” if then. + +### Why it matters, concretely + +It silently weakened a security test. A required `batchId` parameter was added to +three funding service functions to scope an attempt to its batch. The existing test +call sites were not updated, and: + +- **no compile error**, because tests are not typechecked +- **no test failure**, because Prisma (and the in-memory double) treat + `where: { batchId: undefined }` as *"no filter"* rather than *"match null"* + +The tests kept passing while no longer exercising the isolation they were written to +prove. That is worse than a failing test: a green suite asserting nothing. + +Found by reading the code, not by any tool. There is no reason to think it is the +only instance. + +### Likely cost of fixing + +Unknown, and probably not small. Across ~40 test files the expected classes are: + +- fixtures passing partial objects where a full type is required (the fake-db rows + are written by hand and omit optional columns) +- `any`-typed `db` handles hiding argument mismatches +- mock factories returning narrower shapes than the real module +- `vi.mock` factories whose return type does not match the mocked module + +### Intended migration + +1. Add a second config, `tsconfig.test.json`, extending the base and *including* + test files. Do not change the main `tsconfig.json`. +2. Run it and record the error count before changing any test. +3. Fix by directory, smallest first, so each step is reviewable: `lib/money`, + `lib/payroll`, `lib/funding`, `lib/payments`, `lib/tenancy`, routes. +4. Where a fixture genuinely needs a partial row, introduce an explicit test-only + type rather than casting to `any` β€” a cast reintroduces the blindness. +5. Add `typecheck:tests` to the npm scripts and to CI only once it is clean, so it + cannot regress. + +Not started. Scheduled after the funding UI gate. + +--- + +## 2. Reconciliation panel is built but unrouted + +`ReconciliationPanel` is implemented and tested and reachable from no page. See +[RECONCILIATION.md](RECONCILIATION.md). To be routed with the remaining payroll +workflow UI. + +## 3. Transfer verification is bounded by RPC retention + +Soroban RPC retains roughly 22 hours of events, so a payment older than that reads +as `UNREADABLE` rather than absent. Partially mitigated: funding now persists +verified settlement evidence at confirmation time. The same persistence is still +needed for `pay_batch` settlement evidence. + +## 4. Alerting is in-product only + +Findings surface in the UI. There is no email, Slack or pager path, so a critical +finding raised overnight is seen the next morning. Deliberately out of scope until +the workflow is complete. + +## 5. Approval granularity is per-escrow + +The contract's `manager_approve` / `finance_approve` act on the whole escrow, so a +reviewer cannot approve eleven of twelve payments on-chain. The off-chain approval +records are per-payment. A v3 contract decision. + +## 6. Whole hours only + +`hours` is an integer and the contract enforces `hours Γ— rate == amount`. Fractional +hours need a versioned scaled-hours schema. Refused rather than rounded. + +## 7. πŸ”΄ Secret rotation outstanding β€” now on the critical path + +The oracle half of this is blocking the live Testnet funding run: the contract trusts +the deployment-time oracle key and not the one in the local environment, so escrow +creation is refused with `OracleKeyNotRegistered`. Prepared, unexecuted runbook: +[ORACLE_KEY_TRANSITION.md](ORACLE_KEY_TRANSITION.md). It waits on the owner +confirming which public key is post-rotation β€” a fact that cannot be inferred from +here, and guessing it could re-authorize an exposed credential. + + +Everything exposed by `prodenv.txt`. Scope established by enumerating the dump's +variable names: the oracle signing key, `AUTH_SECRET`, `BOOTSTRAP_SECRET`, and the +database credential across all four URL variables. `CRON_SECRET` / `INDEXER_SECRET` +are **not** in that dump β€” an earlier revision of this item claimed they were, and +that was wrong; they are hygiene, not breach response. Runbook and executor: +[SECRET_ROTATION.md](SECRET_ROTATION.md), `scripts/rotate-secrets.mjs`, which +refuses `ORACLE_SECRET_KEY` because its public half is registered on chain. +Owner-operated; +not something to be solved by reading the secrets. Until each is rotated and the old +value proven unable to authenticate or sign, this environment is not +production-grade regardless of what the test suite reports. diff --git a/docs/BULK_PAY.md b/docs/BULK_PAY.md new file mode 100644 index 0000000..dce75c3 --- /dev/null +++ b/docs/BULK_PAY.md @@ -0,0 +1,269 @@ +# Bulk Pay β€” API Layer + +> **Status: API layer implemented, unit-tested, and validated against real PostgreSQL.** +> +> All 10 migrations apply from zero against a local PostgreSQL 18.6 database with +> **zero schema drift**. 71 integration tests exercise these routes and the payment +> state machine against that database. Two real defects were found in the process β€” +> see [REVIEWER_EVIDENCE](evidence/REVIEWER_EVIDENCE.md). +> +> **Still BLOCKED:** the live Testnet golden path (chain settlement, indexing, +> reconciliation) and the product UI. See [Blocked](#what-is-blocked). + +Verified work β†’ approved payment β†’ programmable escrow β†’ on-chain settlement. This +document covers the first two links: turning a payroll file into individual, +reviewable, approvable payment records. + +--- + +## Endpoints + +| Method | Path | Permission | Writes? | +|---|---|---|---| +| POST | `/api/payroll/batches` | `payroll:create` | Yes β€” one batch, N payments, 1 audit event | +| GET | `/api/payroll/batches` | `payroll:read` | No | +| POST | `/api/payroll/batches/validate` | `payroll:create` | **No β€” nothing at all** | +| GET | `/api/payroll/batches/:id` | `payroll:read` | No | +| POST | `/api/payroll/batches/:id/validate` | `payroll:read` | **No β€” nothing at all** | +| POST | `/api/payroll/batches/:id/approve` | `payment:approve:manager` **or** `payment:approve:finance` | Yes β€” approvals + audit events | + +Per-payment business actions already exist and are unchanged: +`POST /api/payments/:id/{approve,reject,cancel,submit,retry,reconcile}`. + +There is deliberately **no** `PATCH /api/payments/:id`. No endpoint anywhere accepts +a destination `PaymentState`. The state machine owns state; the one state a caller +would most like to name is `PAID`. + +### Two different `/validate` endpoints + +- **`POST /batches/validate`** β€” stateless. Checks an uploaded file before any record + exists. Returns **200** with `valid: false` for a bad file: the *call* succeeded, + the *file* is wrong, and a reviewer iterating on a preview is not making failing + requests. +- **`POST /batches/:id/validate`** β€” re-checks an **existing draft** against *current* + configuration. A payroll drafted on Monday and funded on Wednesday may be + denominated in an asset the deployment no longer settles. + +Creation, by contrast, returns **422** for an invalid file β€” there, bad content does +mean the request cannot be honoured. + +--- + +## Request schemas + +Declared in [`src/lib/payroll/schemas.ts`](../src/lib/payroll/schemas.ts) with Zod, +every object `.strict()`. + +### What a client may send + +| Field | Constraint | +|---|---| +| `csv` | string, 1 byte – 1 MB | +| `filename` | ≀ 255 chars, no control characters | +| `reference` | ≀ 64 chars, no control characters, trimmed | +| `projectId` | ≀ 64 chars, `[A-Za-z0-9_-]`, resolved **within the tenant** | +| `idempotencyKey` | 8–128 chars, `[A-Za-z0-9._:-]` (header `Idempotency-Key` wins) | +| `rejectDuplicateRecipients` | boolean, default `true` | +| `paymentIds` | 1–100 ids, approval only | +| `reason` | ≀ 500 chars, no control characters | +| `orgId` | names which organization to act in β€” **never** a claim of membership | + +Unknown fields are **rejected**, not ignored. Silently dropping `{"state":"PAID"}` +teaches a client it was honoured, and the next reader of that code assumes it is. + +### What a client can never send + +`role` Β· `state` / `status` Β· `settlementTxHash` Β· `settledAt` Β· `approvalRole` Β· +`actorAddress` Β· `amountBaseUnits` Β· `orgId` as an authorization claim. + +All derived from the authenticated session, from membership, and from server-side +state. **A field that cannot be sent cannot be forged.** + +Monetary values enter the system through exactly one path: the CSV parser, as decimal +**strings** converted to `bigint` base units. No endpoint accepts an amount as a +number. + +--- + +## Authorization + +Every route passes through `withTenant()` +([`src/lib/tenancy/http.ts`](../src/lib/tenancy/http.ts)) β€” authenticate β†’ resolve +membership from the database β†’ check permission. No route implements its own tenant +check. + +Approval is the one route without a single `permission`, because either half of the +dual-approval gate is a legitimate approver and gating on one would reject the other. +It uses `requireAnyPermission(ctx, ['payment:approve:manager', 'payment:approve:finance'])` +β€” still the central helper, in the central module. + +### Dual approval + +The approver's role is derived from membership by `approvePayment`, never from the +request. A manager sending `{"role":"FINANCE"}` is rejected at the schema β€” the field +does not exist. + +Separation of duties is enforced even for roles holding **both** permissions: an +`ADMIN` who approves twice finds their own role already recorded on the second +attempt and adds nothing. `Approval` is `@@unique([paymentId, role])`, so a second +manager approval is a duplicate, not a new fact. + +Recording an approval **does not settle anything**. It is the workflow record of who +decided what; the authoritative approval is the on-chain signature the indexer +observes. + +### Tenant isolation + +`orgId` is part of every **query**, not a check afterwards. A batch belonging to +another organization returns the identical 404 as one that never existed β€” verified by +a test that asserts the two responses are byte-equal, so an id cannot be probed for +existence. + +--- + +## Idempotency + +| Scenario | Result | +|---|---| +| Same request twice (double-click) | 200, `created: false`, the original batch | +| Retry after timeout | 200, `created: false`, the original batch | +| Two tabs, same key | One batch; one response says `created: true` | +| N concurrent identical requests | One batch, N payments β€” not NΓ—rows | +| Same key, **different** payload | **409 `IDEMPOTENCY_KEY_REUSED`** | +| No key, two deliberate uploads | Two batches (NULLs are distinct) | +| Same key, different organization | Two batches (the key is tenant-scoped) | +| Byte-identical file within an hour | Created, plus `possibleDuplicateOf` | + +The guarantee is a **unique index** on `(orgId, idempotencyKey)`, not a read-then-write: +check-then-insert loses exactly the race it is meant to cover. The pre-check is an +optimization; the index is the correctness argument, and the collision path is tested +by blinding the pre-check so only the index can stop the second write. + +A reused key with a different payload is **refused rather than replayed**. Returning +the original batch would hand back a payroll that is not the one the caller just +described. The comparison uses `idempotencyFingerprint` β€” a hash of the CSV checksum, +reference, project, asset and duplicate-handling option. The **filename is excluded**: +re-uploading identical rows as `september-final.csv` is the same payroll. + +`possibleDuplicateOf` only **warns**. Running the same figures next period is +legitimate payroll, so it is surfaced for a human rather than blocked. + +--- + +## Error model + +[`src/lib/api/errors.ts`](../src/lib/api/errors.ts). Envelope: +`{ error, code, details? }`. + +| Status | Meaning | +|---|---| +| 400 | Malformed request β€” bad JSON, wrong content type, unknown field, oversized body | +| 401 | Not authenticated | +| 403 | Authenticated, organization known, role insufficient | +| 404 | Absent **or** not visible to this tenant β€” deliberately identical | +| 409 | Conflicts with current state, or an idempotency key reused | +| 422 | Well-formed request whose **content** fails domain validation | +| 429 | Rate limited (`Retry-After` set) | +| 500 | Unexpected. Opaque, always | +| 503 | A dependency is unavailable | + +`details` carries only **the caller's own input echoed back** β€” field paths, row +numbers, and messages derived from the schema. Never server state. + +A 500 never carries detail, and there is deliberately **no** "include the stack in +development" switch: a conditional that reveals internals is one misconfigured +environment variable away from revealing them in production. A test asserts the 500 +body contains no model name, no `prisma`, and no stack frames. + +### Row-level CSV errors + +```json +{ + "error": "The payroll file has 3 problems that must be fixed before a batch can be created.", + "code": "CSV_INVALID", + "details": { + "errors": [ + { "row": 3, "field": "recipient", "code": "INVALID_ADDRESS", + "message": "\"NOTANADDRESS\" is not a valid Stellar address. Expected 56 characters beginning with G." }, + { "row": 4, "field": "amount", "code": "AMBIGUOUS_NUMBER", + "message": "amount \"1e3\" uses scientific notation, which is ambiguous. Write the number out in full." }, + { "row": 5, "field": "hours", "code": "FRACTIONAL_HOURS", + "message": "hours \"7.5\" is fractional. CoreFlow v2 records whole hours and will not round a payroll figure." } + ] + } +} +``` + +Every problem in the file is reported at once. A finance user fixing a 40-row file one +error per upload cannot work. + +--- + +## Financial safety + +- One `Payment` per CSV row. A multi-payee payroll is never one aggregate record β€” + "11 paid, 1 needs attention" has to be representable, because it is the normal + outcome of a real batch. +- `bigint` base units throughout. No JS `number` touches money. +- Excess precision is **refused**, not truncated. Scientific notation is refused as + ambiguous. Accounting negatives `(500)` are refused. +- Whole hours only, and `hours Γ— rate == amount` is checked before anything is funded + β€” the contract enforces it on-chain (error #17), so a drifted row would fund custody + that can never be released. **Nothing is rounded to make a demo work.** +- One settlement asset per escrow. A row naming an asset this deployment cannot settle + is refused at validation, never allowed into a state that can never settle. SAC + addresses are read from configuration, never inferred from a symbol. +- Batch creation is atomic: if any payment fails to write, the batch, every payment + and the audit event are rolled back. A partially created payroll would look complete + and quietly underpay someone. +- No endpoint can set `PAID`, write a settlement hash, alter an amount or recipient, + bypass the oracle requirement, or bypass manager/finance separation. + +--- + +## What is blocked + +> These are **not** done, and no mock stands in for them. + +| Item | State | Prerequisite | +|---|---|---| +| Migrations apply from zero | βœ… **10/10, verified** | β€” | +| Schema matches the Prisma model | βœ… **zero drift** | β€” | +| Composite FKs reject cross-tenant rows | βœ… **verified (P2003)** | β€” | +| `(orgId, idempotencyKey)` under real concurrency | βœ… **verified** | β€” | +| Transactional rollback, real PostgreSQL | βœ… **verified** | β€” | +| Exact bigint money through the column | βœ… **verified** | β€” | +| Route integration tests | βœ… **56 passing** | β€” | +| State machine against real records | βœ… **15 passing** | β€” | +| Escrow funding from an approved draft | ❌ **not built** | UI + wallet signing | +| Chain settlement, indexing, reconciliation E2E | ❌ **blocked** | a funded v2 Testnet escrow | +| A fresh Testnet golden-path run | ❌ **blocked** | the above | +| Production `migrate diff` | ❌ **not run** | an authorized production operation | + +Setup: [ENVIRONMENTS.md](ENVIRONMENTS.md#setting-up-the-development-database). + +### What each test layer proves + +| Layer | Command | Proves | +|---|---|---| +| **Unit** (761) | `npm run test:ci` | authorization decisions, query scoping, schema rejection, the idempotency contract, exact arithmetic | +| **Integration** (71) | `npm run test:integration` | what PostgreSQL itself enforces: composite FKs, unique and partial indexes, bigint columns, cascades, transaction isolation, real concurrency | +| **Rust** (70) | `cargo test` | contract invariants, CFWP-v2 attestation, dual approval, upgrade authority | +| **Live Testnet** (opt-in) | `COREFLOW_LIVE_TESTNET=1` | the chain actually behaves as the indexer and reconciler assume | + +The unit suite deliberately **excludes** `*.integration.test.ts`, so its total can +never be presented as database validation. The in-memory double's limitations are +enumerated at the top of [`fake-db.ts`](../src/lib/payments/__tests__/fake-db.ts). + +## Not yet built + +The API layer is complete; the product workflow is not. Still to come: + +- Upload / preview / draft UI, and the batch detail page +- Freighter pre-signing disclosure (network, asset, total, recipient count, org, role, + batch id, with TESTNET shown plainly) +- Escrow funding from an approved draft +- Batch and per-payment receipts +- Audit-trail timeline rendering + +The `ReconciliationPanel` component remains built, tested, and **not routed to a page**. diff --git a/docs/DEPLOYMENTS.md b/docs/DEPLOYMENTS.md new file mode 100644 index 0000000..b74b9ca --- /dev/null +++ b/docs/DEPLOYMENTS.md @@ -0,0 +1,110 @@ +# CoreFlow Deployments + +CoreFlow has **two distinct on-chain deployments**. They are different contracts +with different security properties, and this document exists so that distinction +is never blurred. + +> **v2's security improvements are deployed on Testnet only.** +> They are **not** on Mainnet. Nothing in this repository should be read as +> claiming otherwise. + +--- + +## CoreFlow v2 β€” Stellar **Testnet** (active, hardened) + +| Field | Value | +|---|---| +| Network | **Stellar Testnet** | +| Network passphrase | `Test SDF Network ; September 2015` | +| Contract ID | `CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4` | +| WASM SHA-256 | `d9f2d849d69b56aebcbdf585e8b2e0e8d81d9e5bf13f51534f27bf479d0e56da` | +| WASM size | 42,425 bytes | +| Attestation schema | `CFWP-v2` (198-byte domain-separated preimage) | +| Admin | `GAELEFW56FPEVOO57SJATCGEHX4ROQHULSUHEFMMPLEMACTA5A7PO2J2` | +| Admin pinned in WASM | **yes** β€” `COREFLOW_ADMIN` baked at build time | +| Oracle public key | `f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0` | +| Oracle key registered | yes | +| Paused | no | +| Explorer | https://stellar.expert/explorer/testnet/contract/CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 | + +### Settlement asset (Testnet) + +| Field | Value | +|---|---| +| Asset | Test `USDC` (**not** Circle USDC) | +| SAC contract | `CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M` | +| Decimals | 7 | +| Explorer | https://stellar.expert/explorer/testnet/contract/CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M | + +### What v2 adds over v1 + +| Hardening | Effect | +|---|---| +| Domain-separated attestations (`CFWP-v2`) | A Testnet proof cannot be replayed on Mainnet, on another deployment, for another payee, asset, amount or period | +| `proof_preimage` read-only entry point | The contract is the single source of truth for what must be signed | +| Admin-managed oracle registry | A manager can no longer install their own oracle and attest to their own work | +| `hours Γ— rate == amount` invariant | Verified work determines payment; `hours_logged` is no longer decorative | +| Build-time admin pin | `init_admin` front-running gains an attacker nothing | +| Two-step admin handover | A mistyped transfer cannot destroy admin control | +| `upgrade` requires pause first | No silent one-transaction replacement of the code holding custody | +| Permissionless `extend_escrow_ttl` | Anyone β€” including the worker awaiting payment β€” can keep a funded escrow's storage alive | +| Batch cap + pay-period validation | Bounds a DoS vector and a meaningless attested period | + +--- + +## CoreFlow v1 β€” Stellar **Mainnet** (historical) + +| Field | Value | +|---|---| +| Network | Stellar Mainnet (Public) | +| Contract ID | `CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW` | +| Attestation schema | v1 (32-byte, **no domain separation**) | +| Explorer | https://stellar.expert/explorer/public/contract/CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW | + +**Status:** deployed and untouched. This pass did not modify, repoint, pause or +upgrade it. It does **not** carry any of the v2 hardening above. + +Known limitations of v1, retained here for accuracy: + +- Attestations lack domain separation β€” a proof is not bound to network, + contract, payee, asset, amount or period. +- The manager supplies and may rotate the oracle key, so the proof-of-work gate + is manager-attestable. +- `hours_logged` does not constrain the amount paid. +- `init_admin` is not pinned and is therefore front-runnable. + +A controlled v1 β†’ v2 Mainnet migration is future work and has not been scheduled. + +--- + +## Reproducing the v2 deployment + +```bash +# 1. Oracle keypair (server-side only; never commit the seed) +openssl rand -hex 32 > /dev/null # generate, then store in your secret manager +ORACLE_SECRET_KEY= node scripts/oracle-cli.mjs pubkey + +# 2. Deploy. Refuses to proceed unless the admin pin is present in the WASM. +ADMIN_IDENTITY=coreflow-v2-admin \ +ORACLE_PUBKEY=<64 hex> \ + ./scripts/deploy-testnet.sh +``` + +The script builds with `COREFLOW_ADMIN` set, **greps the resulting binary to +confirm the pin is physically present** (building with the variable set is not +evidence the compiler used it β€” a cached unpinned artifact would look identical), +deploys, initializes, registers the oracle key, and then verifies every one of +those facts by reading them back from the chain. + +## Environment + +``` +NEXT_PUBLIC_STELLAR_NETWORK=testnet +NEXT_PUBLIC_STELLAR_CONTRACT_ID=CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 +NEXT_PUBLIC_STELLAR_TOKEN_ID=CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M +NEXT_PUBLIC_STELLAR_READ_ADDRESS=GAELEFW56FPEVOO57SJATCGEHX4ROQHULSUHEFMMPLEMACTA5A7PO2J2 +ORACLE_SECRET_KEY=<32-byte hex seed, server-side only> +``` + +An unset `NEXT_PUBLIC_STELLAR_CONTRACT_ID` is a hard error, not a fallback β€” +see `src/lib/config.ts`. diff --git a/docs/ENVIRONMENTS.md b/docs/ENVIRONMENTS.md new file mode 100644 index 0000000..b9663b4 --- /dev/null +++ b/docs/ENVIRONMENTS.md @@ -0,0 +1,196 @@ +# Environments + +CoreFlow runs in three environments. They differ in **which database** and +**which chain** they touch, and those two facts decide whether a mistake is +recoverable. + +A development command must never silently inherit production configuration. On +2026-09-11 one did: a `vercel env pull` overwrote `.env` / `.env.local` with the +deployment's variables, repointing local development at the production database +and Mainnet v1. Every individual value was valid; only the combination was wrong. +[`scripts/check-env.mjs`](../scripts/check-env.mjs) now refuses that combination. + +--- + +## The three environments + +| | DEVELOPMENT | TESTNET VALIDATION | PRODUCTION | +|---|---|---|---| +| **Database** | Private local cluster, port 5440 | Dedicated hosted Postgres, disposable | Managed Postgres (`db.prisma.io`) | +| **Chain** | Stellar **Testnet** | Stellar **Testnet** | Stellar **Mainnet** | +| **Contract** | v2 `CDN4FIKL…VAQRG5F4` | v2 `CDN4FIKL…VAQRG5F4` | v1 `CCTF5WBO…J2XPRFFW` | +| **Settlement asset** | v2 testnet SAC `CBW2ZKFB…JS743Q5M` | same | v1 mainnet SAC | +| **Funds at risk** | None | None | **Real** | +| **Schema version** | v2 (10 migrations, applied & verified) | v2 (10 migrations) | **v1** β€” see below | +| **Who may reset it** | Anyone, freely | Anyone, deliberately | Nobody, ever, from a dev workflow | +| **Preflight verdict** | must pass | `COREFLOW_ALLOW_REMOTE_DB=1` | `COREFLOW_ALLOW_MAINNET=1` + out-of-band authorization | + +### Production is still v1 + +No part of the v2 hardening is deployed. Production holds the 11-table v1 schema, +and its migration history diverges from this repository's. See +[PRODUCTION_DATABASE_REMEDIATION.md](PRODUCTION_DATABASE_REMEDIATION.md). + +Do not describe v2's security properties as live on Mainnet. They are not. + +### Which is the default + +**DEVELOPMENT: local database + Testnet v2.** Every `npm` task that can write +runs the preflight first and refuses anything else: + +``` +predev predev:http predb:migrate predb:deploy predb:seed β†’ npm run check:env +``` + +`build` and `vercel-build` deliberately do **not** run it. The production +deployment legitimately is Mainnet v1, and failing its build would take the live +site down. + +--- + +## Setting up the development database + +One command: + +```bash +./scripts/dev-db.sh init +``` + +That creates a **private PostgreSQL cluster owned by your user**, in your home +directory, on a non-default port, and writes the connection URLs into `.env`. + +### Why a separate cluster rather than a database in the system one + +Creating a role in the system instance needs an existing superuser. On the machine +this was set up on, neither `postgres` peer authentication nor the old `coreflow` +password was available β€” the password was lost with the `.env.local` that a +`vercel env pull` overwrote. + +`initdb` does not need root. A cluster you create is one you are legitimately the +superuser of, so no authentication is circumvented and the system instance is left +alone. It has a useful side effect: development cannot reach anything but its own +data. + +| | | +|---|---| +| Data directory | `~/.local/share/coreflow/pgdata` (override with `COREFLOW_PGDATA`) | +| Port | `5440` (override with `COREFLOW_PGPORT`) | +| Listens on | `127.0.0.1` only | +| Databases | `coreflow_dev`, `coreflow_shadow` | +| Role | `coreflow`, no `CREATEDB`, no `SUPERUSER` | +| Password | generated locally, written only to `.env`, never printed | + +Port 5440 rather than 5433: on this machine 5432 is the system PostgreSQL and 5433 +was held by podman's `pasta` networking. + +The **shadow** database is separate because Prisma **resets** it. Pointing +`SHADOW_DATABASE_URL` at a database holding anything you want to keep destroys it β€” +which is exactly how the original dev database was lost once already. + +```bash +./scripts/dev-db.sh start # after a reboot +./scripts/dev-db.sh stop +./scripts/dev-db.sh status +./scripts/dev-db.sh psql # a shell on coreflow_dev +./scripts/dev-db.sh destroy # delete the cluster and all its data +``` + +### Then migrate + +```bash +npm run check:env # must print "OK: local database + Testnet v2." +npm run db:deploy # applies all 10 migrations from zero +npx prisma migrate status +npm run test:integration +``` + +--- + +## Which file holds what + +This is the part that went wrong on 2026-09-11, so it is worth being exact. + +| File | Written by | Read by | Holds | +|---|---|---|---| +| `.env` | **you**, and `scripts/dev-db.sh` | Next.js **and** Prisma CLI | local development configuration, including the database URLs | +| `.env.local` | **you** | Next.js only | personal overrides; **no database URLs** | +| `.env.vercel` | `vercel env pull --env=production` | nothing automatically | the deployment's configuration, for reference | +| `.env.example` | **you** | nobody | placeholders, committed | + +**Database configuration lives in `.env`, not `.env.local`.** This is not a +preference: the **Prisma CLI reads only `.env`**, while Next.js reads both. Putting +the URLs in `.env.local` makes the app work and every `prisma migrate` fail with +*Environment variable not found: DIRECT_URL*. + +### After a `vercel env pull` + +**Never pull into `.env` or `.env.local`.** Pull into `.env.vercel`: + +```bash +vercel env pull .env.vercel --env=production +``` + +A bare `vercel env pull` writes `.env.local` and will silently replace your local +database URLs and network settings with the deployment's. If it has already +happened, the production database URLs are still commented out in `.env` with a +note; the preflight will refuse to run until they are gone or local: + +```bash +npm run check:env +``` + +Neither `.env`, `.env.local` nor `.env.vercel` is ever committed β€” `.gitignore` +denies every `.env` variant and re-admits only `.env.example`. + +## Preflight reference + +[`scripts/check-env.mjs`](../scripts/check-env.mjs) is fail-closed and judges +against explicit allowlists, not string patterns. It refuses when: + +- `NEXT_PUBLIC_STELLAR_NETWORK` is `public` / `mainnet` +- `NEXT_PUBLIC_STELLAR_CONTRACT_ID` is the Mainnet v1 contract +- `NEXT_PUBLIC_STELLAR_CONTRACT_ID` is **absent from the deployment registry** β€” + an unrecognized address is refused rather than assumed +- `NEXT_PUBLIC_STELLAR_TOKEN_ID` is not the SAC the v2 Testnet deployment was + configured with (an escrow holds exactly one asset) +- any of `DATABASE_URL`, `DIRECT_URL`, `PRISMA_DATABASE_URL`, `POSTGRES_URL` + resolves to a host outside the local allowlist + +It prints hostnames and contract addresses only. It never reads a secret for its +value and never prints one. + +### Overrides + +Per-run and deliberate. Never set these as defaults, and never commit them: + +| Variable | Meaning | +|---|---| +| `COREFLOW_ALLOW_MAINNET=1` | A deliberate Mainnet action | +| `COREFLOW_ALLOW_REMOTE_DB=1` | A deliberate action against a non-local database | +| `COREFLOW_ALLOW_UNKNOWN_CONTRACT=1` | A contract not in the registry, e.g. a scratch deployment | + +Adding a deployment is meant to require editing `KNOWN_CONTRACTS` in the script +and [DEPLOYMENTS.md](DEPLOYMENTS.md). That friction is the feature. + +--- + +## Secrets + +Never committed: `.env`, `.env.local`, any `.env.*`, `*.pem`, `*.key`, +`prodenv.txt`, any `*env*.txt`. `.gitignore` denies every `.env` variant and +re-admits only `.env.example`, which holds placeholders and local-only values. + +Never logged: oracle secret keys, `AUTH_SECRET`, `BOOTSTRAP_SECRET`, +`CRON_SECRET`, `INDEXER_SECRET`, database passwords, `VERCEL_OIDC_TOKEN`, session +tokens, wallet secrets. + +A `vercel env pull` writes **live production secrets** to disk. Those files are +git-ignored, but they are still real credentials sitting in the working tree. + +### Outstanding + +πŸ”΄ **Secret rotation is still outstanding** for everything exposed by +`prodenv.txt`: the oracle signing key, `AUTH_SECRET`, `BOOTSTRAP_SECRET`, cron / +indexer secrets, and the database credential. Until each is rotated and the old +value proven unable to authenticate or sign, this environment is not +production-grade, regardless of what the test suite reports. diff --git a/docs/FUNDING.md b/docs/FUNDING.md new file mode 100644 index 0000000..f0144aa --- /dev/null +++ b/docs/FUNDING.md @@ -0,0 +1,252 @@ +# Funding β€” an approved draft becomes a funded escrow + +> **Status: domain and API complete and unit-tested. UI and live Testnet run outstanding.** + +## The architecture, as the contract actually is + +**Creating the escrow *is* funding the escrow.** + +`initialize_multi_sig_escrow(manager, finance_approver, oracle_pubkey, payments)` +transfers custody β€” one `TokenClient::transfer(manager β†’ contract)` per distinct +asset β€” and *then* stores the escrow. There is no `fund()` entry point, and +`CoreFlowEscrow` has no `funded` field. + +So: + +- **One wallet interaction, not two.** "Created but not funded" is not a state the + product can display, because it is not a state the contract can be in. The UI + must not present creation and funding as separate steps. +- **An escrow exists if and only if its custody moved.** Existence is the funding + evidence. + +| Question | Answer | +|---|---| +| Custody destination | the contract's own address (`env.current_contract_address()`) | +| Asset | per-payment `token`, a SAC address β€” never inferred from a symbol | +| Amount | sum per distinct token, moved during creation | +| Creation proof | `escrow/created` + one `payment/add` per payee | +| Funding proof | the **SAC's own** `transfer` event | +| Read state | `get_escrow(id)` | +| Preconditions | `manager != finance_approver`, registered oracle key, `end_date > start_date`, `amount % rate_per_hour == 0`, ≀100 payments | + +## The contract is not idempotent β€” this is the central safety problem + +A second `initialize_multi_sig_escrow` creates a **second escrow** and moves the +money **again**. Nothing on-chain prevents it. + +Every control against double-funding is therefore off-chain, and the enforcement is +a **database constraint**, not a disabled button: + +``` +BlockchainTransaction.idempotencyKey UNIQUE = fund:batch: +``` + +The attempt is opened **before the wallet is shown**. A second call while an +attempt is open returns *that* attempt. Double-click, refresh, two tabs and a +client retry all converge on one escrow. + +A retry after a genuinely finished attempt gets a new key +(`fund:batch::retry:`), so a failed attempt does not block the batch forever +while a confirmed one can never be repeated. + +## The plan is frozen, and is what the chain is compared against + +When the intent opens, the exact plan is persisted to +`BlockchainTransaction.plan` with a SHA-256 `planDigest`: batch, organization, +project, contract, custody destination, network, manager, finance approver, oracle +key, asset code + SAC + decimals, total, and every row's payment id, recipient, +token, amount, rate and period. Money is stored as **decimal strings** β€” JSON has +no bigint. + +Confirmation compares the chain against **that**, never against a recomputed plan. +Configuration can move under a pending transaction β€” the settlement asset switched, +a different finance approver becoming the first candidate, a payment edited β€” and a +recomputed plan would quietly agree with whatever the chain happened to contain. +That is the agreement that must not be manufactured. + +A plan whose digest does not match its content is refused outright. + +## Lifecycle + +``` +READY ──▢ AWAITING_SIGNATURE ──▢ SUBMITTED ──▢ CONFIRMED (funded) + β”‚ β”‚ + β”‚ β”œβ”€β–Ά FAILED chain says it failed + β”‚ β”œβ”€β–Ά UNVERIFIABLE chain unreadable β€” retry + β”‚ └─▢ MISMATCH chain β‰  plan; not adopted + └─▢ CANCELLED signature declined, nothing submitted +``` + +Every transition writes an audit event: `funding.intent.opened`, +`funding.submitted`, `funding.confirmed`, `funding.failed`, `funding.declined`, +`funding.mismatch`. + +## Confirmation: what has to be true + +`CONFIRMED` requires **all** of: + +1. `readTransactionSucceeded(hash)` is true +2. the escrow reads back, is not cancelled, and its manager and finance approver are + the planned ones β€” and are not the same key +3. its payment count matches, and every row's recipient, amount and token match the + plan **in order** +4. the payments in the database still match the plan (nothing edited or removed + since it was frozen) +5. a transfer of the **exact** total, from the plan's manager to the custody + address, is observable **in that transaction** + +Freighter returning is not evidence. The client builds and signs, so it could +submit something other than the plan. + +### The three non-success outcomes are never collapsed + +| Outcome | Meaning | Effect | +|---|---|---| +| `FAILED` | the chain says the transaction failed | payments return to DRAFT; nothing moved | +| `UNVERIFIABLE` | the chain could not be read | **nothing recorded**; retry later | +| `MISMATCH` | we read the chain and it disagrees | escrow **not adopted**; CRITICAL finding opened | + +`UNVERIFIABLE β‰  FAILED`. An RPC outage proves nothing about the transaction, and +marking a funded escrow failed would be worse than waiting. + +`MISMATCH` preserves the evidence as a `ReconciliationFinding` (CRITICAL) and +attaches nothing to the batch. Someone funded an escrow this batch did not +describe; adopting it would make the product assert something untrue about money. + +## Recovery: the transaction hash is the anchor + +A client can submit `initialize_multi_sig_escrow` and then fail to learn which +escrow it created β€” a dropped connection, an RPC hiccup, a reload, an unparseable +return value. That must never require signing again, because signing again funds a +**second** escrow with the same money. + +So the escrow id is **resolved server-side from the transaction hash**, in order of +durability: + +1. **`ChainEvent`** β€” the indexer'''s own record of the `escrow/created` event, + scoped to the attempt'''s contract and network. Survives RPC event retention. +2. **Soroban RPC** β€” authoritative, bounded by retention, used while the indexer has + not caught up. + +| Resolution | Outcome | +|---|---| +| exactly one escrow | verification proceeds | +| none visible yet | `UNVERIFIABLE` β€” pending, not failed | +| more than one | `MISMATCH` β€” ambiguity is refused, never resolved by choosing | + +`onChainEscrowId` in the confirm request is **optional and advisory**. The server +uses what it resolved; a supplied value is only cross-checked, so transaction A +cannot adopt the escrow created by transaction B. A disagreement is a `MISMATCH` +with both ids recorded. + +Recovery is repeatable: confirming twice yields one escrow, one attempt, one audit +event. It never creates a new funding attempt. + +## Uncertain transactions β€” the retry policy + +| Situation | Behaviour | +|---|---| +| Wallet declined, nothing submitted | safe. Attempt `CANCELLED`, payments return to DRAFT, a fresh attempt may be opened | +| Submitted, then the client died | the attempt persists with its hash. **Do not submit again** β€” call confirm | +| Submitted, RPC unreadable | `UNVERIFIABLE`. The attempt stays `SUBMITTED` and keeps blocking a new one | +| Confirmed | a new attempt can never be opened | +| Mismatch | stop. A human resolves the finding | + +Abandon is narrow by design: payments return to DRAFT **only when no hash exists**. +Once a transaction was submitted the money may have moved, so the record is not +rewound β€” manufacturing a "not funded" state is how a second escrow gets funded. + +The UI must say so plainly while verification is pending: + +> We're verifying whether your funding transaction completed. **Do not fund again.** + +## The pay period is required + +`period_start` and `period_end` are **required CSV columns**. + +The period is a signed field of the CFWP-v2 attestation and the contract refuses +`end_date <= start_date`. A row without one can be drafted but can never be funded. +CoreFlow will not supply it β€” defaulting to today, last month or the upload date +means attesting to a pay period nobody stated. + +This is enforced at **upload**, not at funding. Discovering it at the wallet prompt, +after a payroll has been prepared and approved, is far worse than being told at +row 8. + +## Eligibility + +Every blocker is reported at once β€” 13 codes: `ROLE_NOT_PERMITTED`, +`ALREADY_FUNDED`, `FUNDING_IN_FLIGHT`, `NO_PAYMENTS`, `TOO_MANY_PAYMENTS`, +`PAYMENT_NOT_FUNDABLE`, `PAYMENT_ALREADY_ON_CHAIN`, +`SETTLEMENT_ASSET_UNCONFIGURED`, `ASSET_MISMATCH`, `MIXED_ASSETS`, +`AMOUNT_NOT_POSITIVE`, `HOURS_RATE_MISMATCH`, `PERIOD_REQUIRED`, +`PERIOD_INVALID`, `NO_DISTINCT_FINANCE_APPROVER`, `ORACLE_KEY_UNAVAILABLE`. + +Funding is permitted to `OWNER`, `ADMIN`, `MANAGER` β€” mirroring `escrow:create`, and +matching the contract, where the signer **is** the escrow's manager. The finance +approver is chosen **server-side** and never taken from the request: letting a +caller nominate it would let them nominate themselves, which is what the contract +refuses with `SignersNotDistinct`. + +## API + +| Method | Path | Permission | +|---|---|---| +| GET | `/api/payroll/batches/:id/funding` | `escrow:read` | +| POST | `/api/payroll/batches/:id/funding/intent` | `escrow:create` | +| POST | `/api/payroll/batches/:id/funding/submitted` | `escrow:create` | +| POST | `/api/payroll/batches/:id/funding/confirm` | `escrow:create` | +| POST | `/api/payroll/batches/:id/funding/abandon` | `escrow:create` | + +Every attempt id is scoped to the batch in the URL, so an attempt belonging to +another batch cannot be acted on by naming it. No request body carries an amount, +recipient, asset, manager or finance approver β€” all of that comes from the frozen +plan. + +Confirm returns **200 for every outcome**: the verification request succeeded, and +the body says what the chain showed. A 4xx would conflate "we could not check" with +"your request was wrong". + +## Division of labour with the indexer + +| Component | Owns | +|---|---| +| **Funding** | the intent, the frozen plan, chain verification, creating/linking the `Escrow`, and setting each payment's `onChainPaymentIndex` | +| **Indexer** | state from chain events. On `payment/add` it recognises the linked payment and advances `VALIDATING β†’ AWAITING_ORACLE` | + +Linking payments to their on-chain slot is what stops the indexer creating a second +set of rows: it looks up `(escrowId, onChainPaymentIndex)` and finds ours. It also +cross-checks recipient and amount, opening an `AMOUNT_MISMATCH` finding if they +disagree. + +## The batch detail page + +`/dashboard/payroll/[id]` is the workspace: header with the mapped domain status, +summary figures formatted server-side, the funding card (the same `FundingPanel`, so +the three funding states and recovery behave identically wherever they appear), one +table row per payment with a drill-down to exact base units, approvals read from +`Approval` records β€” including the half that is **missing**, since "waiting for +finance" is the fact a reviewer needs β€” an activity timeline from real `AuditEvent` +rows, collapsed technical details, and any open reconciliation findings. + +The timeline is sparse early in a batch's life, and is left that way. Padding it with +plausible entries nobody recorded would make the one screen whose job is to show what +happened the least trustworthy thing in the product. + +On arrival, a submitted attempt with a known hash and no escrow id is recovered +**automatically, once** β€” no user action, no second signature. Repeated verification +of a genuinely pending transaction would be noise, so it is not retried on a loop and +`Check status` stays available. + +## Still blocked + +| | Prerequisite | +|---|---| +| Funding review UI + Freighter disclosure | βœ… built | +| Batch detail page | βœ… built | +| Live Testnet funding run | the UI, plus a Freighter signature in a browser | + +Test USDC **can** be obtained through the project's existing setup: the Stellar CLI +holds `coreflow-v2-usdc-issuer`, `coreflow-v2-manager` and `coreflow-v2-finance` +identities, and the previous golden path funded escrow #8 with 2,860 test USDC that +way. The browser wallet interaction cannot be automated and needs the operator. diff --git a/docs/IMPLEMENTATION_MAP.md b/docs/IMPLEMENTATION_MAP.md new file mode 100644 index 0000000..ea07a7e --- /dev/null +++ b/docs/IMPLEMENTATION_MAP.md @@ -0,0 +1,235 @@ +# Implementation Map β€” the contract the UI must implement + +Every step of Bulk Pay, from the screen to the chain and back. Written before the UI +so the UI is built against a workflow that exists, rather than the workflow being +bent to fit screens. + +Legend: βœ… built and verified Β· 🟑 built, not verified end-to-end Β· ❌ not built + +--- + +## The nine stages + +### 1. Upload β€” choose a file + +| | | +|---|---| +| **UI** | Drop zone; filename, size, row count. Client-side size check only as courtesy | +| **API** | none yet β€” the file is not sent until stage 2 | +| **State** | none | +| **Status** | ❌ UI | + +The file is never trusted client-side. The browser's row count is a convenience; the +server re-derives everything. + +### 2. Validate β€” see every problem at once + +| | | +|---|---| +| **UI** | Row-level error table: line number, column, message. Re-validate on edit | +| **API** | `POST /api/payroll/batches/validate` β†’ `{ valid, errors[], warnings[], summary, asset }` | +| **Domain** | `validateCsv` β†’ `parsePayrollCsv` + `settleableAssetCodes` | +| **Database** | **none β€” writes nothing** | +| **State** | none | +| **Status** | βœ… API Β· ❌ UI | + +Returns **200** with `valid: false` for a bad file. Safe to call on every keystroke. +Errors carry `row` (1-based, as the uploader sees it), `field`, `code`, `message`. + +Refused here, never silently fixed: invalid addresses Β· scientific notation Β· +excess precision Β· zero/negative amounts Β· accounting negatives Β· fractional hours Β· +`hours Γ— rate β‰  amount` Β· assets this deployment cannot settle Β· duplicate +recipients (unless opted out) Β· >100 rows Β· >1 MB Β· control characters. + +### 3. Draft β€” create the batch + +| | | +|---|---| +| **UI** | Preview table, total, recipient count, period; "Create draft" | +| **API** | `POST /api/payroll/batches` + `Idempotency-Key` β†’ 201 `{created:true}` / 200 `{created:false}` / 409 / 422 | +| **Domain** | `createBatch` β†’ `createDraftBatch` | +| **Database** | 1 `PayrollBatch` + **N `Payment`** + 1 `AuditEvent`, one transaction | +| **State** | every payment `DRAFT` | +| **Status** | βœ… API, DB-verified Β· ❌ UI | + +**One payment per row.** Never an aggregate. Rollback is all-or-nothing β€” verified +against PostgreSQL by injecting a failure on row 3. + +The UI **must** send `Idempotency-Key` and keep it stable across retries of the same +file. Reusing it with a different payload returns 409 `IDEMPOTENCY_KEY_REUSED`. + +### 4. Fund β€” put custody on-chain + +| | | +|---|---| +| **UI** | Pre-signing disclosure, then Freighter | +| **API** | ❌ not built | +| **Domain** | needs `requireSettlementContractId()`; escrow creation exists at `POST /api/escrows` for the single-escrow path | +| **Chain** | `create_escrow` + token transfer into custody | +| **Indexer** | `escrow/created` β†’ `Escrow` row, `resolveEscrowTenant` | +| **State** | `DRAFT β†’ VALIDATING β†’ AWAITING_ORACLE` | +| **Status** | βœ… domain + API + UI, DB-verified Β· ❌ live Testnet run | + +The disclosure must show, before the wallet opens: **network (TESTNET, plainly)** Β· +contract id Β· settlement asset + SAC address Β· exact total Β· recipient count Β· +organization Β· the caller's role Β· batch reference. A signature request that does not +say what is being signed is the problem CFWP-v2 exists to solve at the protocol +level; the UI must not reintroduce it at the human level. + +### 5. Verify work β€” the oracle attestation + +| | | +|---|---| +| **UI** | "Work verified" with hours and period. **Not** "CFWP-v2 signature validated" | +| **API** | `POST /api/oracle/attest` (session + on-chain-manager gated) | +| **Domain** | `buildProofMessage` β€” 198-byte domain-separated preimage | +| **Chain** | `submit_hours_proof`; contract enforces `hours Γ— rate == amount` (#17) and a monotonic nonce | +| **Indexer** | `oracle/verified` β†’ `ORACLE_VERIFIED` | +| **State** | `AWAITING_ORACLE β†’ ORACLE_VERIFIED` | +| **Status** | βœ… API + contract Β· ❌ UI | + +### 6. Approve β€” two distinct people + +| | | +|---|---| +| **UI** | Two separate affordances, each labelled with the role it satisfies and who filled it | +| **API** | `POST /api/payroll/batches/:id/approve` (batch) Β· `POST /api/payments/:id/approve` (one) | +| **Domain** | `approveBatch` β†’ `approvePayment`; role from **membership**, never the body | +| **Database** | `Approval` `@@unique([paymentId, role])`; `AuditEvent` per decision | +| **Chain** | `approve_manager` / `approve_finance`, two distinct keys | +| **Indexer** | `approve/*` β†’ `AWAITING_FINANCE` β†’ `READY_TO_SETTLE` | +| **State** | `ORACLE_VERIFIED β†’ AWAITING_MANAGER β†’ AWAITING_FINANCE β†’ READY_TO_SETTLE` | +| **Status** | βœ… off-chain API, DB-verified Β· 🟑 on-chain signing Β· ❌ UI | + +The UI must **never** imply one wallet satisfied both halves. An `ADMIN` holds both +permissions, and the second attempt records nothing β€” the screen must say *"waiting +for a second approver"*, not *"approved"*. + +Off-chain approval is a **workflow record**. The authoritative approval is the +on-chain signature the indexer observes. The UI must distinguish "decision recorded" +from "approval observed on-chain". + +### 7. Settle β€” submit, and wait for the chain + +| | | +|---|---| +| **UI** | "Submitting…" β†’ "Confirming…" β†’ per-payment outcome. **Never "Paid" on submission** | +| **API** | `POST /api/payments/:id/submit` (+ `Idempotency-Key`) | +| **Domain** | `submitPaymentForSettlement`; `BlockchainTransaction.idempotencyKey` unique | +| **Chain** | `pay_batch` β€” one transaction, one SAC transfer per payee | +| **State** | `READY_TO_SETTLE β†’ SUBMITTING β†’ CONFIRMING` | +| **Status** | 🟑 API exists Β· ❌ UI | + +`SUBMITTING` is not `PAID`. Verified against PostgreSQL: **no user actor of any +role β€” OWNER, ADMIN, MANAGER, FINANCE β€” can persist `PAID`.** + +### 8. Confirm β€” PAID, from chain evidence only + +| | | +|---|---| +| **UI** | "11 paid / 1 requires attention", per-payment, with explorer links | +| **API** | `GET /api/payroll/batches/:id` (standing derived on read) | +| **Indexer** | `payment/paid` β†’ `PAID` + `settlementTxHash` + `settledAt` | +| **State** | `CONFIRMING β†’ PAID`, actor `indexer` **only** | +| **Status** | βœ… indexer + state machine, DB-verified Β· ❌ UI | + +A transaction link is rendered **only** where `mayHaveTransaction` is true. Showing an +explorer URL for an unsubmitted payment invites a reader to believe it settled. + +Partial failure is the normal case. The UI must make one failed payment among eleven +prominent without implying the eleven failed. + +### 9. Reconcile β€” independent verification + +| | | +|---|---| +| **UI** | `ReconciliationPanel` β€” **built, tested, not routed to any page** | +| **API** | `POST /api/organizations/:id/reconciliation` | +| **Domain** | reads **SAC transfer events**, not CoreFlow's own projection | +| **State** | may open `RECONCILIATION_REQUIRED`; **never moves `PAID` backwards** | +| **Status** | βœ… engine Β· 🟑 panel built, unrouted | + +Transaction-scoped matching: the same contract paying the same contractor the same +rate every period is **not** unique on `(contract, recipient, asset, amount)` β€” the +live-caught bug that produced seven false `DUPLICATE_PAYMENT_EVENT` findings. + +--- + +## What the UI may never do + +| Never | Because | +|---|---| +| Send `organizationId`, `role`, `state`, `status`, `settlementTxHash` or an approval identity as trusted input | All server-derived. The schemas **reject** these fields, they are not ignored | +| Show "Paid" before `state === 'PAID'` | `PAID` comes only from observed chain evidence | +| Show a transaction link where `mayHaveTransaction` is false | It implies settlement that has not happened | +| Imply one wallet satisfied both approvals | The contract refuses it (`SignersNotDistinct`); the UI must not suggest otherwise | +| Parse a formatted amount back into money | Use `*BaseUnits` strings; never `parseFloat` | +| Round or reformat hours | v2 records whole hours; fractional input is refused upstream | +| Retry an already-settled payment | Terminal financial state | +| Invent a field when evidence is absent | Render "not yet available", not a placeholder value | +| Call an endpoint that sets state directly | None exists, deliberately | + +--- + +## Response shapes the UI binds to + +Money always arrives twice: a display string **and** an exact base-unit string. + +```ts +// POST /api/payroll/batches β†’ 201 +{ created: true, + batch: { id, reference, paymentCount, periodStart, periodEnd, + total: "2,860.00", totalBaseUnits: "28600000000", + asset: "USDC", unlinkedRecipients: 2 }, + warnings: [{ row?, field?, code, message }], + possibleDuplicateOf?: { id, reference, createdAt } } + +// GET /api/payroll/batches/:id +{ batch: { id, reference, source: {...}, + total, totalBaseUnits, paymentCount, + standing: { headline, byState, needsAttention, + totalAmountBaseUnits, paidAmountBaseUnits, paid }, + payments: [{ id, recipient, amount, amountBaseUnits, + rateBaseUnits, hours, state, stateLabel, tone, + needsAttention, stateReason, reference, + transactionHash, // null unless the state allows one + settledAt, onChainPaymentIndex, + approvals: [{ role, decision, actorAddress, createdAt }] }] } } + +// POST /api/payroll/batches/:id/approve β†’ 200 +{ batchId, approvalRole: "MANAGER", + recorded: 3, alreadyRecorded: 0, failed: 0, + results: [{ paymentId, ok, recorded, state, stateLabel, message?, code? }], + completeForRole: true } +``` + +Errors are always `{ error, code, details? }`; `details` only ever echoes the +caller's own input. + +--- + +## Pages to build + +| Page | Stages | Needs | +|---|---|---| +| Bulk Pay upload | 1–3 | validate + create | +| Batch detail (built) | 3–9 | header, summary, funding card, per-payment table with drill-down, approvals, activity timeline, technical details, findings | +| Batch list | β€” | list | +| Reconciliation | 9 | **route the existing panel** | + +Every page carries the persistent **Testnet** badge. Every page derives state from +the API; none computes a state of its own. + +--- + +## The acceptance scenario + +One realistic run, against real PostgreSQL **and** real v2 Testnet: + +> Organization A β†’ 3 contractors β†’ CSV upload β†’ exactly 3 `Payment` rows β†’ exact +> amounts β†’ manager approval β†’ finance approval (distinct wallet) β†’ Testnet +> settlement β†’ 3 indexed payments β†’ independent reconciliation β†’ 3 receipts β†’ +> complete audit trail. **And Organization B cannot see any of it.** + +Stages 1–3, 6 (off-chain) and the isolation requirement are verified against +PostgreSQL today. Stages 4, 7, 8 and the receipts are not yet end-to-end. diff --git a/docs/MULTI_TENANCY.md b/docs/MULTI_TENANCY.md new file mode 100644 index 0000000..6fe732b --- /dev/null +++ b/docs/MULTI_TENANCY.md @@ -0,0 +1,258 @@ +# CoreFlow Multi-Tenancy + +Tenant isolation is treated as a **security boundary**, not a UI filter. This +document states the model, where it is enforced, and what it does not yet cover. + +--- + +## 1. The ownership graph + +``` +Organization +β”œβ”€β”€ OrgMember ──────── User (role + lifecycle status) +β”œβ”€β”€ Project +β”œβ”€β”€ Worker +β”œβ”€β”€ Escrow ─────────── Project? +β”œβ”€β”€ PayrollBatch ───── Project? +β”œβ”€β”€ Payment ────────── PayrollBatch, Escrow?, Project?, Worker? +β”‚ β”œβ”€β”€ Approval +β”‚ β”œβ”€β”€ OracleAttestation +β”‚ β”œβ”€β”€ BlockchainTransaction +β”‚ └── ReconciliationFinding +└── AuditEvent +``` + +**Every tenant-owned record carries `orgId` directly.** Nothing relies on the +application "remembering" which tenant a record belongs to by walking a parent +chain. `Approval` and `OracleAttestation` gained an explicit `orgId` in this phase +precisely because they were previously reachable only by joining through `Payment`. + +Records that are deliberately **not** tenant-owned: + +| Model | Why | +|---|---| +| `User`, `Session`, `AuthChallenge` | Identity is global; a wallet may belong to several organizations | +| `ChainEvent`, `IndexerCursor` | Raw chain data, scoped by `(contractId, network)` rather than tenant | +| `AuditLog` (legacy) | Predates organizations; **not** exposed through any tenant-scoped endpoint | +| `TimeLog` (legacy) | Superseded by `Payment.hours` + `OracleAttestation` | + +--- + +## 2. The database enforces the boundary + +Every parent relation on a tenant-owned record is a **composite foreign key** on +`(orgId, id)`, not on `id` alone. + +```prisma +batch PayrollBatch @relation(fields: [orgId, batchId], references: [orgId, id]) +``` + +A plain `batchId` foreign key guarantees only that the batch *exists*. It says +nothing about whose batch it is, so a bug or a crafted request could produce a +payment in org A attached to a batch, escrow, project or worker in org B β€” with no +constraint objecting. Isolation would then rest entirely on every query +remembering to filter. + +Verified directly against PostgreSQL: + +``` +org A payment β†’ org A batch INSERT 0 1 ALLOWED +org A payment β†’ org B batch Payment_orgId_batchId_fkey BLOCKED +org A payment β†’ org B project Payment_orgId_projectId_fkey BLOCKED +org A payment β†’ org B worker Payment_orgId_workerId_fkey BLOCKED +org B approval β†’ org A payment Approval_orgId_paymentId_fkey BLOCKED +org B audit β†’ org A payment AuditEvent_orgId_paymentId_fkey BLOCKED +org B escrow β†’ org A project Escrow_orgId_projectId_fkey BLOCKED +``` + +Note the MATCH SIMPLE semantics: when the optional id is NULL the constraint is +satisfied, which is the intended behaviour for optional parents. + +`@@unique([orgId, id])` on `Project`, `Worker`, `Escrow`, `PayrollBatch` and +`Payment` exists to make them valid composite-FK targets. + +--- + +## 3. The application boundary + +One module: `src/lib/tenancy/`. + +| File | Responsibility | +|---|---| +| `rbac.ts` | Permission and delegation tables β€” see [`RBAC.md`](RBAC.md) | +| `resolve.ts` | Membership resolution and every scoped resource lookup | +| `membership.ts` | Membership lifecycle, invitations, escalation guards | +| `http.ts` | `withTenant()` β€” the wrapper every tenant-scoped route uses | + +### Nothing from the client is trusted + +A client may **name** which of its organizations to act in (`X-Organization-Id`, +`?orgId`, or a body field). It may never assert that it belongs to one, nor what it +may do there. Both come from `OrgMember`, read on every request. + +Specifically untrusted: `organizationId` as a bearer of authority, role claims, +project ownership, hidden form fields, client-side route protection. + +When the caller belongs to exactly one organization, that one is used. When they +belong to several, the request **must** say which β€” guessing could perform an +action in the wrong tenant's name, and a payment approved in the wrong +organization is not a recoverable mistake. + +### Scoping lives in the WHERE clause + +Resources are loaded with `orgId` as part of the query, never fetched by global id +and checked afterwards. A post-fetch check still performs the read, and any +logging, error path or timing difference around it can disclose existence. + +### 404, not 403, for anything out of scope + +A 403 on a foreign resource confirms it exists. Repeated across a range of ids that +becomes an enumeration oracle: an attacker learns how many payments another tenant +has, and roughly what they are worth, without reading one. Every cross-tenant miss +is therefore **byte-identical** to a genuine miss β€” same status, same message. + +403 is reserved for resources the caller *can* see but may not act on. + +The same applies to membership: `INVITED`, `SUSPENDED` and `REMOVED` are all +indistinguishable from non-membership, so suspending someone does not tell them +they were ever a member. + +### On-chain ids are not tenant-safe + +Escrow ids are assigned by the contract, so org A and org B can both hold an +"escrow 3" on different deployments. `findEscrowByOnChainId` always filters by +organization; resolving an on-chain id globally would hand one tenant another's +escrow. + +--- + +## 4. Membership lifecycle + +``` +INVITED ──▢ ACTIVE ──▢ SUSPENDED ──▢ ACTIVE + β”‚ β”‚ β”‚ + └──────────▢└───────────▢└──────▢ REMOVED (terminal) +``` + +Only **ACTIVE** grants authority. `SUSPENDED` is kept distinct from `REMOVED` so +access can be revoked without destroying the record of who held what β€” an audit +trail that says a role "never existed" after an incident is worse than none. + +`REMOVED` is terminal. Re-admitting someone creates a **new** membership, so the +previous one's history stays attributable. + +Guards, each tested: no self-targeting, no granting above your level, no removing +or suspending or demoting the last active administrator. + +--- + +## 5. Invitations + +| Property | Implementation | +|---|---| +| Unpredictable | 32 bytes of CSPRNG output, base64url | +| Stored hashed | Only `sha256(token)` is persisted β€” a database dump must not yield working invitations | +| Single-use | Conditional update on `usedAt IS NULL` inside a transaction, so two racing requests cannot mint two memberships | +| Expiring | 7 days | +| Org-scoped | `orgId` is **required**; an invitation that cannot name its organization is not acceptable | +| Role-scoped | `orgRole` validated against the inviter's delegation | +| Revocable | `revokedAt`, kept distinct from `usedAt` so "withdrawn" is never read as "accepted" | +| Returned once | The plaintext token appears only in the creation response | + +**Email uniqueness is per organization**, not global. A global constraint meant +that once org A invited `alice@example.com`, org B could never invite her β€” and the +failure disclosed that some other tenant already had her. Contractors working for +several agencies is the normal case. + +**Every rejection looks the same to the caller.** Expired, revoked, already-used +and never-existed all return one 404 body. Distinguishing them tells someone +probing tokens which of their guesses were real. + +**Acceptance does not change an existing member's role**, in either direction β€” an +invitation must not be a promotion channel for someone who already belongs. A +`REMOVED` member cannot be re-admitted by an old invitation. + +--- + +## 6. Indexer tenancy mapping + +**The chain knows nothing about CoreFlow organizations.** The only authoritative +mapping is an `Escrow` row the application itself wrote β€” created when a member +submitted the creation transaction, or when an operator explicitly claimed the +escrow. + +``` +chain event (escrow N, contract C, network W) + β”‚ + β–Ό + Escrow WHERE onChainId=N AND contractId=C AND network=W + β”‚ + β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β” +found not found + β”‚ β”‚ + β–Ό β–Ό +project record with attributed=false, project NOTHING +under +that org +``` + +The indexer **never invents a tenant.** It previously auto-created one +organization per deployment and attached every discovered escrow to it; that is a +guess, and it would place one party's payroll, recipients and amounts inside +another's workspace. A data breach produced by a convenience default. + +Consequences, stated plainly: + +- Escrows created outside the app (CLI, validation scripts, another client) are + **invisible until claimed**. That is the intended trade. +- `POST /api/organizations/:id/escrows/claim` attributes one, and requires the + caller to prove against **live contract state** that their wallet is the escrow's + on-chain manager. Without that, any organization could claim any escrow by naming + its id. +- An escrow already claimed by another organization returns a deliberately vague + 409: confirming that another tenant holds it would disclose that tenant's + existence. +- Unattributed events are **recorded, not dropped** (`ChainEvent.attributed = + false`, with the decoded payload). After a claim, `replayUnattributed()` applies + the backlog β€” the cursor has advanced past those ledgers, so without replay a + claimed escrow would silently be missing all history predating its claim. + +Protections: duplicate events are keyed by RPC paging token; duplicate payments are +impossible via `@@unique([escrowId, onChainPaymentIndex])`; conflicting mappings +are refused rather than reassigned; orphan and conflicting chain state becomes a +`ReconciliationFinding`. + +--- + +## 7. Threat model + +| Attack | Mitigation | Residual risk | +|---|---|---| +| Substitute another tenant's resource id | Scoped queries + composite FKs; 404 response | β€” | +| Enumerate ids to infer another tenant's volume | Identical response for foreign and nonexistent | Timing differences not measured | +| Claim another tenant's on-chain escrow | On-chain manager proof against live state | A compromised manager key can claim its own escrows into an attacker's org | +| Escalate via invitation | `orgRole` validated against inviter's delegation | β€” | +| Escalate via self-promotion | `checkNotSelf` | β€” | +| MANAGER manufactures the finance approval | Cannot grant FINANCE; cannot hold both halves; contract enforces `SignersNotDistinct` | β€” | +| Strand an organization with no admin | `checkNotLastAdministrator` on remove/suspend/demote | A single owner losing their key is unrecoverable without operator action | +| Steal an invitation from the database | Only the hash is stored | A leaked creation **response** yields a live token until expiry | +| Replay an invitation | Single-use conditional update | β€” | +| Platform admin reads every tenant's payroll | Legacy platform `Role` grants no payment authority; `/api/escrows` and audit logs are org-scoped | β€” | +| Indexer mis-assigns chain data | No tenant is invented; unattributed by default | β€” | +| Stale client shows org A data after switching | Server always re-resolves; client must refetch | **Frontend switcher not yet built** β€” see Β§8 | + +--- + +## 8. What is NOT done + +Stated explicitly rather than implied by omission. + +| Gap | Status | +|---|---| +| **Organization UX** | No switcher, no onboarding wizard, no members/projects screens. The API exists and is enforced; the interface does not. Stale-client-state invalidation is therefore untested in a real UI. | +| **Project-level scoping** | Projects are tenant-scoped entities, but permissions are **organization-wide**: a role grants the same access to every project in the organization. Per-project membership is not implemented. This is deliberate β€” the simplest model that is secure β€” and is recorded as a product decision, not a completed feature. | +| **Worker identity across organizations** | The same wallet **may** be a worker in multiple organizations (`@@unique([orgId, walletAddress])`), and each is a separate `Worker` record with its own payment history. Whether that should be linkable to one human is undecided. | +| **Treasury** | `treasury:read` exists in the matrix; no treasury endpoint or view is implemented. | +| **Query performance under scoping** | Indexes exist on `(orgId, state)`, `(orgId, role)`, `(orgId, status)`, `(orgId, resolvedAt)` and the composite-FK targets. Not load-tested; no N+1 audit beyond the route handlers changed here. | +| **Reconciliation scheduling** | `reconcileOrganization` is implemented and tested but nothing invokes it on a timer. Reconciliation logic, not reconciliation operations. | +| **Approval granularity** | Per-escrow, not per-payment β€” a contract-level constraint. See `PAYMENT_STATE_MACHINE.md` Β§12. | diff --git a/docs/ORACLE.md b/docs/ORACLE.md new file mode 100644 index 0000000..57fa811 --- /dev/null +++ b/docs/ORACLE.md @@ -0,0 +1,232 @@ +# CoreFlow Oracle Protocol + +**Schema:** `CFWP-v2` Β· **Preimage:** 198 bytes, fixed width Β· **Signature:** Ed25519 + +The oracle is the component that turns "a payment row exists" into "the contract will +release funds for it". `pay_batch` refuses to settle any payment whose `proof_verified` +flag is false, so an oracle signature is the only thing standing between a funded escrow +and a paid worker. This document is the specification of that signature. + +--- + +## 1. What the oracle attests to + +> *This worker performed these hours in this period, and is owed exactly this amount, +> in this asset, under this escrow, on this contract, on this network β€” once.* + +Every clause in that sentence is a field in the signed preimage. That is the whole design. + +--- + +## 2. The preimage (198 bytes) + +| Offset | Size | Field | Source | +|-------:|-----:|-------|--------| +| 0 | 4 | magic `"CFWP"` | constant | +| 4 | 2 | version `u16 BE` (= 2) | constant | +| 6 | 32 | `network_id` β€” sha256(network passphrase) | `env.ledger().network_id()` | +| 38 | 32 | contract digest | sha256(ScVal XDR of `current_contract_address`) | +| 70 | 32 | worker digest | sha256(ScVal XDR of the **stored** payment's worker) | +| 102 | 32 | token digest | sha256(ScVal XDR of the **stored** payment's asset) | +| 134 | 4 | `escrow_id` `u32 BE` | call argument | +| 138 | 4 | `payment_id` `u32 BE` | call argument | +| 142 | 16 | `amount` `i128 BE` | **stored** payment row | +| 158 | 16 | `hours` `i128 BE` | call argument | +| 174 | 8 | `start_date` `u64 BE` | **stored** payment row | +| 182 | 8 | `end_date` `u64 BE` | **stored** payment row | +| 190 | 8 | `nonce` `u64 BE` | call argument | + +**Addresses are hashed, not embedded.** An account `ScAddress` and a contract `ScAddress` +serialize to different lengths, so hashing each to a fixed 32 bytes keeps the preimage +fixed-width and trivially reproducible off-chain. + +**Fields marked *stored* are read from escrow state, never from call arguments.** This is +what stops a caller retargeting a signature onto a different payee, asset, amount or +period than the one the oracle actually saw. + +--- + +## 3. What v1 got wrong + +v1 signed 32 bytes: `escrow_id β€– payment_id β€– hours β€– nonce`. + +That message named no chain, no contract, no payee, no asset and no amount. The +consequences were concrete: + +| Substitution | Possible under v1? | Closed by | +|---|---|---| +| Replay a Testnet attestation against Mainnet | **Yes** | `network_id` | +| Reuse a signature on a different deployment of the same contract | **Yes** | contract digest | +| Redirect a payment to a different worker | **Yes** | worker digest | +| Settle in a different asset than attested | **Yes** | token digest | +| Change the amount paid after attestation | **Yes** | `amount` | +| Reuse an attestation for a different pay period | **Yes** | period | +| Replay the same attestation twice | No β€” nonce watermark | `nonce` | + +The Testnetβ†’Mainnet case is the sharpest one: the same escrow id and payment id on two +networks is not a hypothetical, it is the *expected* outcome of testing before launch. + +--- + +## 4. Replay protection + +The contract keeps a **monotonic nonce watermark** per escrow, in persistent storage. +`submit_hours_proof` accepts a nonce only if it equals the current watermark, then +increments it. + +This is stronger than a set of spent nonces, and cheaper: a `Vec` of consumed values grows +without bound, costs more rent on every call, and eventually makes its own escrow +unusable β€” while only ever rejecting *exact* duplicates. A watermark is O(1) forever and +rejects every nonce at or below it. + +**Ordering matters.** The signature is verified **before** the nonce is consumed. Consuming +first would let an attacker burn an escrow's nonce sequence by submitting garbage +signatures. + +--- + +## 5. Who may hold an oracle key + +Oracle keys are held in an **admin-managed on-chain registry**: + +``` +register_oracle_key(pubkey) // contract admin only +revoke_oracle_key(pubkey) // contract admin only +is_oracle_key_registered(pubkey) // read-only +``` + +`initialize_multi_sig_escrow` and `rotate_oracle_key` both refuse a key that is not +registered β€” so rotation cannot be used as a back door around the registry. + +**Why this exists.** Previously the *manager* supplied the `oracle_pubkey` at escrow +creation and could rotate it at will. A manager could therefore install their own key and +sign their own "verified work" attestations, which made the proof-of-work gate procedural +rather than cryptographic. Economic damage was bounded β€” custody is the manager's own +funds β€” but the security property the product advertises did not hold. + +**Revocation is deliberately not retroactive.** It stops a key being named by *new* +escrows and *new* rotations; it does not invalidate in-flight attestations, because doing +so would strand escrows that are already funded. To retire a key from a live escrow, the +manager calls `rotate_oracle_key`, which revokes that escrow's verified proofs and forces +re-attestation under the new key. + +**Bootstrap exception.** A contract with no admin has no registry authority, so no key +could ever satisfy the check. Rather than bricking such a deployment, an admin-less +contract accepts any key β€” exactly the v1 trust model, and no weaker. The registry is +enforced from the moment `init_admin` runs. + +--- + +## 6. Work must justify payment + +The contract enforces: + +``` +hours Γ— rate_per_hour == amount +``` + +Without it, `hours_logged` was decorative: the oracle could attest to any number of hours +while `amount` β€” fixed at creation and already funded into custody β€” paid out regardless. + +Two corollaries: + +- `initialize_multi_sig_escrow` rejects an `amount` that is not a whole multiple of + `rate_per_hour`, so custody is never funded into an escrow that can never settle. +- `/api/submit-batch` **derives** hours from the on-chain payment row rather than trusting + the upload, and refuses a CSV whose payees do not match the funded escrow. + +**Known limitation.** This makes hours whole numbers. Fractional-hour payroll (e.g. 40.04 h) +needs a scaled-hours representation, which would be a v3 schema change. This is a real +product constraint, not an oversight β€” it is recorded here rather than hidden. + +--- + +## 7. Keeping signer and verifier in agreement + +Three implementations build this preimage: + +| Implementation | File | +|---|---| +| Contract (the verifier) | `contracts/core-flow/src/lib.rs` β†’ `build_proof_message` | +| Server signer | `src/lib/oracle/index.ts` β†’ `buildProofMessage` | +| CLI | `scripts/oracle-cli.mjs` β†’ `buildProofMessage` | + +They are pinned to **one shared vector**, `docs/evidence/proof-vector-v2.json`, by a chain +of three assertions: + +1. `test_proof_preimage_matches_cross_language_vector` (Rust) β€” the Rust layout equals the + vector produced by the JS CLI. +2. `builds the exact preimage pinned by the cross-language vector` (vitest) β€” the TS + signer equals the same vector. +3. `test_contract_preimage_matches_independent_implementation` (Rust) β€” the **contract's + own** builder equals an independent reimplementation written out longhand in the test + file. + +Step 3 matters: the test-side builder is deliberately a *second* implementation rather +than a call into the contract's. Sharing the builder would make every signature test +tautological β€” it would prove only that one function agrees with itself, and a field +silently dropped from the preimage would still pass. + +**Better still: don't reimplement it.** The contract exposes + +``` +proof_preimage(escrow_id, payment_id, hours, nonce) -> Bytes +``` + +as a read-only call. A signer can simulate it and sign the returned bytes verbatim, +eliminating drift by construction. `CoreFlowClient.getProofPreimage()` wraps it. + +--- + +## 8. Using the CLI + +```bash +# Print the oracle public key (hex) β€” this is what gets registered on-chain +ORACLE_SECRET_KEY=<64 hex chars> node scripts/oracle-cli.mjs pubkey + +# Sign a batch +ORACLE_SECRET_KEY=... node scripts/oracle-cli.mjs sign batch.json + +# Verify locally, and demonstrate replay + domain binding +ORACLE_SECRET_KEY=... node scripts/oracle-cli.mjs verify batch.json signed.json +``` + +`batch.json`: + +```json +{ + "networkPassphrase": "Test SDF Network ; September 2015", + "contractId": "C...", + "escrowId": 1, + "startNonce": 0, + "payees": [ + { "paymentId": 0, "worker": "G...", "token": "C...", + "amount": "10000", "hours": 40, "startDate": 1000, "endDate": 2000 } + ] +} +``` + +`verify` prints, for the operator to see before broadcasting: + +``` +payment 0 nonce 0 VALID +replay protection: nonce IS bound into the signature +domain separation: network IS bound into the signature +``` + +The CLI requires `networkPassphrase` and `contractId` explicitly and refuses to default +them β€” guessing which chain an attestation is for is exactly the failure v2 exists to +prevent. + +--- + +## 9. Key handling + +- `ORACLE_SECRET_KEY` is a 32-byte hex seed. Generate with `openssl rand -hex 32`. +- It lives **only** on the server. It is never sent to the browser and never logged. +- Only the *public* half reaches clients, via `GET /api/oracle/pubkey`. +- Requesting an attestation requires a verified session **and** that the caller is the + escrow's **on-chain manager**, read live from the contract rather than from the request. +- Rotating `ORACLE_SECRET_KEY` invalidates every escrow whose stored `oracle_pubkey` is + the old key. Those escrows need `rotate_oracle_key` (to a registered replacement) or + cancellation. Plan rotation accordingly. diff --git a/docs/ORACLE_KEY_TRANSITION.md b/docs/ORACLE_KEY_TRANSITION.md new file mode 100644 index 0000000..326e50c --- /dev/null +++ b/docs/ORACLE_KEY_TRANSITION.md @@ -0,0 +1,203 @@ +# Oracle registry transition β€” runbook + +> **Status: PREPARED, NOT EXECUTED.** Awaiting the owner's confirmation of which +> public key is the post-rotation one. No `register_oracle_key` or +> `revoke_oracle_key` transaction has been sent. + +## Why this is blocked on a human + +The live funding run stopped at simulation with `Error(Contract, #16)` +`OracleKeyNotRegistered`. The read-only facts: + +| Key | Registered on `CDN4FIKL…VAQRG5F4` | +|---|---| +| `3b9d395a725ba0be4c476a0504540fb8dd70a278fac140a20e0d74cd7f41ae44` β€” in the local environment | **false** | +| `f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0` β€” recorded at deployment | **true** | + +The contract trusts the deployment-time key and not the current environment's. That +is consistent with the oracle secret having been rotated locally without the new +public key being registered. + +**Which key is the post-rotation one cannot be determined from the registry +alone.** The naming suggests an answer and the naming is not evidence. A cheaper +question may be decidable from evidence on disk β€” see the next section β€” but it +does not change who authorizes the transaction. If `f42a4883…` is the key +`prodenv.txt` exposed, then registering `3b9d395a…` is correct and revoking +`f42a4883…` is urgent. If the mapping is the other way round, registering +`3b9d395a…` would authorize a credential an attacker may hold to sign work +attestations β€” precisely the attack the admin-managed registry exists to prevent. + +So this waits. A manager cannot install their own oracle; neither can an agent. + +## A determination that may not need anyone's memory + +The section above asks the wrong question. "Which key is *newer*" is a fact about +history, recoverable only from whoever performed the rotation. But the action does +not depend on age β€” it depends on **which key is exposed**, and that is a fact +about a file still sitting on disk. + +`prodenv.txt` contains exactly one `ORACLE_SECRET_KEY` line, and the public half +is derived from it deterministically +([`src/lib/oracle/index.ts:74`](../src/lib/oracle/index.ts)). Deriving that public +key names the exposed key from evidence rather than from naming. + +**The owner runs this, not an agent** β€” the secret would otherwise pass through an +agent's process, and the standing rule is that it does not. Load the +`ORACLE_SECRET_KEY` value from `prodenv.txt` into a shell variable, then derive +and print only the **public** key with `Keypair.fromRawEd25519Seed`, exactly as +`getOraclePublicKeyHex()` does. Nothing secret is displayed. + +Read the single line it prints: + +| Output | Meaning | Action | +|---|---|---| +| `f42a4883…` | The **registered** key is the exposed one. The local environment holds a different secret β€” a replacement was generated and never registered. | Register `3b9d395a…`, then revoke `f42a4883…`. The urgency is real: an exposed key is currently trusted. | +| `3b9d395a…` | The **local environment's** key is the exposed one. | Do **not** register `3b9d395a…`. Generate a fresh oracle secret, register its public key, revoke both. | +| neither | A third key is in production; this file settles nothing about the two candidates. | Still owner-gated. Treat both as suspect and prefer a fresh key. | + +Note which way the risk falls. `prodenv.txt` is a **production** dump, and +production is Mainnet v1 while this blocker concerns the v2 Testnet contract, so +"neither" is a realistic outcome β€” the two deployments need not share an oracle. +That makes this a cheap check rather than a guaranteed answer: one command either +decides it or eliminates the file from consideration. + +In every branch the decision stays the owner's, and in none of them does guessing +from key names become acceptable. + +## Order, and why + +``` +1. register(new) ← first +2. verify(new) == true +3. revoke(old) ← only after 2 succeeds +4. verify(old) == false +``` + +Registering first means there is never a window in which **no** key is registered. +Revoking first would leave the contract unable to accept any attestation, and would +strand every escrow awaiting one. + +## Steps + +Each step prints only public values: network, contract, admin address, the oracle +**public** key, and the operation. No secret is read or printed at any point. + +### 0. Preflight + +```bash +npm run check:env # must report profile: LOCAL +stellar keys address coreflow-v2-admin +stellar contract invoke --id "$CONTRACT" --source coreflow-v2-admin \ + --network testnet --send=no -- get_admin +``` + +`get_admin` must equal the `coreflow-v2-admin` address. If it does not, stop: the +identity cannot perform this transition. + +### 1. Register the new key + +```bash +stellar contract invoke --id "$CONTRACT" --source coreflow-v2-admin \ + --network testnet -- register_oracle_key --pubkey "$NEW_PUBKEY" +``` + +Record the transaction hash. + +### 2. Verify registration from chain state, not from the exit code + +```bash +stellar contract invoke --id "$CONTRACT" --source coreflow-v2-admin \ + --network testnet --send=no -- is_oracle_key_registered --pubkey "$NEW_PUBKEY" +# must print: true +``` + +**If this is not `true`, STOP. Do not revoke the old key.** A failed registration +followed by a revocation leaves the contract with no usable oracle. + +### 3. Revoke the old key + +```bash +stellar contract invoke --id "$CONTRACT" --source coreflow-v2-admin \ + --network testnet -- revoke_oracle_key --pubkey "$OLD_PUBKEY" +``` + +Record the transaction hash. + +### 4. Verify the final registry state + +```bash +is_oracle_key_registered(NEW) == true +is_oracle_key_registered(OLD) == false +``` + +**If the post-state does not match, STOP and report the exact chain state.** Do not +attempt a corrective transaction without review. + +### 5. Re-run the read-only preflight + +```bash +npm run check:env +node scripts/validate-testnet-v2.mjs +``` + +## Out of scope for this transition + +Not to be touched: the contract **admin**, the **pause** state, the **upgrade** +state, escrow data, and anything outside the oracle registry. Two invocations only. + +## What this does and does not accomplish + +It makes the contract trust the current oracle key and stop trusting the previous +one. It does **not** complete the πŸ”΄ outstanding secret rotation: `AUTH_SECRET`, +`BOOTSTRAP_SECRET`, the cron/indexer secrets and the database credential are +separate, and remain the owner's to rotate. See [BACKLOG.md](BACKLOG.md). + +## Execution + +One reproducible pass, which emits the evidence record itself rather than relying on +a narrated summary afterwards: + +```bash +# read-only checks only +node scripts/oracle-key-transition.mjs --new --old --confirm-mapping --dry-run + +# execute +node scripts/oracle-key-transition.mjs --new --old --confirm-mapping +``` + +[`scripts/oracle-key-transition.mjs`](../scripts/oracle-key-transition.mjs) refuses +unless both keys are named AND `--confirm-mapping` is passed, so it cannot run by +accident. It verifies `get_admin` matches the signing identity, registers before +revoking, reads chain state after each step rather than trusting the CLI exit code, +and stops β€” writing the record β€” if the new key is not registered after registration +or if the final state is not `new=registered, old=not registered`. + +**Verified:** it refuses without `--confirm-mapping` (exit 1) and refuses a +malformed key. The dry-run path is deliberately unexercised, because running it would +mean asserting a key mapping that has not been confirmed. + +## Evidence to record on completion + +Written to `docs/evidence/oracle-key-transition.json` by the script: + +``` +Network: Stellar Testnet +Contract: CDN4FIKL…VAQRG5F4 +Admin: + +Old oracle: f42a… +New oracle: 3b9d… + +Registration TX: +Post-registration: new = registered + old = registered + +Revocation TX: +Post-revocation: new = registered + old = not registered +``` + +Both post-states are read from the contract, so the record states what the chain +says rather than what the commands were asked to do. The intermediate +post-registration state is captured deliberately: it is the evidence that there was +never a window in which no oracle key was registered. diff --git a/docs/PAYMENT_STATE_MACHINE.md b/docs/PAYMENT_STATE_MACHINE.md new file mode 100644 index 0000000..1193d22 --- /dev/null +++ b/docs/PAYMENT_STATE_MACHINE.md @@ -0,0 +1,333 @@ +# CoreFlow Payment State Machine + +**16 states Β· 38 declared transitions.** The tables below are generated from +`src/lib/payments/state-machine.ts`, which is the single source of truth. If they +disagree with the code, the code is right and this document is stale. + +--- + +## 1. The one rule that matters + +> **Only a chain observer may move a payment to `PAID`.** + +There is no user-initiated path to `PAID`, and no system-initiated one. The only +actors that can assert settlement are the **indexer** (reading the contract's +event log) and the **reconciler** (reading live contract state). Everything else +β€” a route handler, an optimistic UI update, a retry β€” is structurally incapable +of it. + +Two corollaries that are easy to get wrong, so they are named explicitly: + +- **`SUBMITTING` is not paid.** A transaction has been built and signed. It may + never reach the network. +- **`CONFIRMING` is not paid.** The network accepted it. It may still fail at + ledger close. + +A payroll product that lets the frontend manufacture "settled" is worse than one +with no status at all, because it is confidently wrong about money. + +--- + +## 2. Chain vs. database authority + +| Concern | Authority | +|---|---| +| Token movement, settlement, final transaction result | **Chain** | +| Contract authorization, approvals, oracle verification | **Chain** | +| Organizations, projects, workers, CSV provenance | Database | +| Workflow metadata, search, filtering, UX state | Database | +| Audit projection, reconciliation state | Database | + +The database is a **projection**. Where the two disagree, the disagreement is +recorded as a `ReconciliationFinding` and surfaced β€” the losing side is not +quietly rewritten, because overwriting it destroys the only evidence the two ever +diverged. + +--- + +## 3. States + +| State | Label | Meaning | Exits | Tx may exist | Needs attention | +|---|---|---|---|---|---| +| `DRAFT` | **Draft** | Not yet submitted. Still editable. | 2 exit(s) | no | no | +| `VALIDATING` | **Validating** | Checking recipient, amount, asset and hours. | 4 exit(s) | no | no | +| `AWAITING_ORACLE` | **Awaiting oracle verification** | Funded on-chain. Waiting for a signed work attestation. | 3 exit(s) | no | no | +| `ORACLE_VERIFIED` | **Work verified** | The contract accepted the oracle attestation for this payment. | 2 exit(s) | yes | no | +| `AWAITING_MANAGER` | **Awaiting manager approval** | Needs the manager’s on-chain signature. | 4 exit(s) | yes | yes | +| `AWAITING_FINANCE` | **Awaiting finance approval** | Manager approved. Needs the separate finance signature. | 4 exit(s) | yes | yes | +| `READY_TO_SETTLE` | **Ready to settle** | Both approvals are on-chain. Settlement can be submitted. | 3 exit(s) | yes | yes | +| `SUBMITTING` | **Submitting to Stellar** | Building and signing the settlement transaction. Not yet paid. | 3 exit(s) | yes | no | +| `CONFIRMING` | **Confirming on Stellar** | Submitted to the network. Awaiting ledger confirmation β€” not yet paid. | 3 exit(s) | yes | no | +| `PAID` | **Paid** | Settled on-chain and confirmed. Funds reached the recipient. | terminal | yes | no | +| `REJECTED` | **Rejected** | An approver declined this payment. | terminal | no | no | +| `CANCELLED` | **Cancelled** | Cancelled before settlement. Escrowed funds were refunded. | terminal | yes | no | +| `EXPIRED` | **Expired** | The approval or attestation window lapsed before settlement. | terminal | no | yes | +| `SUBMISSION_FAILED` | **Submission failed** | The transaction never reached Stellar. Safe to retry. | 3 exit(s) | no | yes | +| `SETTLEMENT_FAILED` | **Settlement failed** | The transaction reached Stellar and failed. Needs reconciliation before retry. | 3 exit(s) | yes | yes | +| `RECONCILIATION_REQUIRED` | **Reconciliation required** | CoreFlow’s records and the chain disagree. An operator must resolve it. | 4 exit(s) | yes | yes | + +**Terminal states:** `PAID`, `REJECTED`, `CANCELLED`, `EXPIRED`. No transition +out of these is declared, and `checkTransition` refuses any attempt with +`TERMINAL`. + +**"Tx may exist"** gates transaction display. The UI shows an explorer link only +where this is `yes` AND a hash is actually present β€” a link on a payment that was +never submitted invites a reader to believe something settled. + +--- + +## 4. Failure states are distinct on purpose + +A single generic `FAILED` cannot answer the only question that matters after a +failure: *is it safe to retry?* + +| State | What happened | Retry safe? | +|---|---|---| +| `SUBMISSION_FAILED` | Never reached the network β€” build, simulate, sign or RPC failure | **Yes.** Nothing was submitted, and the on-chain approvals still stand. A user may retry. | +| `SETTLEMENT_FAILED` | Reached the chain and failed there | **Not until reconciled.** What it did on-chain must be established first. Deliberately *not* a user transition. | +| `RECONCILIATION_REQUIRED` | Database and chain disagree | **No.** Requires operator resolution; never cleared automatically. | +| `EXPIRED` | The attestation or approval window lapsed | N/A β€” terminal. | +| `REJECTED` | An approver declined | N/A β€” terminal. | +| `CANCELLED` | Cancelled before settlement, custody refunded | N/A β€” terminal. | + +`POST /api/payments/:id/retry` enforces this: called on a `SETTLEMENT_FAILED` +payment it returns **409** with code `RECONCILIATION_FIRST` rather than doing +something riskier than the caller asked for. + +--- + +## 5. Transition diagram + +```mermaid +stateDiagram-v2 + DRAFT -->|user| VALIDATING + DRAFT -->|user| CANCELLED + VALIDATING -->|system| DRAFT + VALIDATING -->|indexer/reconciler| AWAITING_ORACLE + VALIDATING -->|user| REJECTED + VALIDATING -->|user| CANCELLED + AWAITING_ORACLE -->|indexer/reconciler| ORACLE_VERIFIED + AWAITING_ORACLE -->|indexer/reconciler| CANCELLED + AWAITING_ORACLE -->|system| EXPIRED + ORACLE_VERIFIED -->|system/indexer| AWAITING_MANAGER + ORACLE_VERIFIED -->|indexer/reconciler| CANCELLED + AWAITING_MANAGER -->|indexer/reconciler| AWAITING_FINANCE + AWAITING_MANAGER -->|user| REJECTED + AWAITING_MANAGER -->|indexer/reconciler| CANCELLED + AWAITING_MANAGER -->|system| EXPIRED + AWAITING_FINANCE -->|indexer/reconciler| READY_TO_SETTLE + AWAITING_FINANCE -->|user| REJECTED + AWAITING_FINANCE -->|indexer/reconciler| CANCELLED + AWAITING_FINANCE -->|system| EXPIRED + READY_TO_SETTLE -->|user| SUBMITTING + READY_TO_SETTLE -->|indexer/reconciler| CANCELLED + SUBMITTING -->|system| CONFIRMING + SUBMITTING -->|system| SUBMISSION_FAILED + CONFIRMING -->|indexer/reconciler| PAID + READY_TO_SETTLE -->|indexer/reconciler| PAID + SUBMITTING -->|indexer/reconciler| PAID + CONFIRMING -->|indexer/system| SETTLEMENT_FAILED + CONFIRMING -->|reconciler/system| RECONCILIATION_REQUIRED + SUBMISSION_FAILED -->|user| READY_TO_SETTLE + SUBMISSION_FAILED -->|user| CANCELLED + SUBMISSION_FAILED -->|reconciler| RECONCILIATION_REQUIRED + SETTLEMENT_FAILED -->|reconciler/system| RECONCILIATION_REQUIRED + SETTLEMENT_FAILED -->|indexer/reconciler| PAID + SETTLEMENT_FAILED -->|reconciler| READY_TO_SETTLE + RECONCILIATION_REQUIRED -->|reconciler/indexer| PAID + RECONCILIATION_REQUIRED -->|reconciler| READY_TO_SETTLE + RECONCILIATION_REQUIRED -->|reconciler| SETTLEMENT_FAILED + RECONCILIATION_REQUIRED -->|user| CANCELLED +``` + +--- + +## 6. Complete transition table + +Anything absent from this table is invalid. The test suite enumerates every +undeclared `(from, to)` pair for every actor kind and asserts rejection β€” a state +machine tested only on its happy paths will happily accept `DRAFT β†’ PAID`. + +| From | To | Who may | Why | +|---|---|---|---| +| `DRAFT` | `VALIDATING` | user (OWNER, ADMIN, MANAGER) | Submitted for validation by whoever is preparing the batch. | +| `DRAFT` | `CANCELLED` | user (OWNER, ADMIN, MANAGER) | A draft row is discarded before anything is funded. | +| `VALIDATING` | `DRAFT` | system | Validation failed; the row returns to editable rather than stalling. | +| `VALIDATING` | `AWAITING_ORACLE` | indexer Β· reconciler | The escrow is funded on-chain. Only the indexer asserts this, because it means custody actually moved. | +| `VALIDATING` | `REJECTED` | user (OWNER, ADMIN, MANAGER, FINANCE) | Declined during review, before funding. | +| `VALIDATING` | `CANCELLED` | user (OWNER, ADMIN, MANAGER) | Withdrawn during review. | +| `AWAITING_ORACLE` | `ORACLE_VERIFIED` | indexer Β· reconciler | A `hours/submit` event was observed, meaning the contract ACCEPTED an Ed25519 attestation for this payment. Requesting an attestation is not the same as the chain verifying one. | +| `AWAITING_ORACLE` | `CANCELLED` | indexer Β· reconciler | The escrow was cancelled on-chain; custody refunded. | +| `AWAITING_ORACLE` | `EXPIRED` | system | The attestation window lapsed without a proof. | +| `ORACLE_VERIFIED` | `AWAITING_MANAGER` | system Β· indexer | Proof in hand; the payment enters the approval chain. | +| `ORACLE_VERIFIED` | `CANCELLED` | indexer Β· reconciler | The escrow was cancelled on-chain. | +| `AWAITING_MANAGER` | `AWAITING_FINANCE` | indexer Β· reconciler | An `approve/manager` event was observed. The approval is the on-chain signature, not the API call that prompted it. | +| `AWAITING_MANAGER` | `REJECTED` | user (OWNER, ADMIN, MANAGER) | The manager declined. | +| `AWAITING_MANAGER` | `CANCELLED` | indexer Β· reconciler | The escrow was cancelled on-chain. | +| `AWAITING_MANAGER` | `EXPIRED` | system | The approval window lapsed. | +| `AWAITING_FINANCE` | `READY_TO_SETTLE` | indexer Β· reconciler | An `approve/finance` event was observed from the distinct finance key. | +| `AWAITING_FINANCE` | `REJECTED` | user (OWNER, ADMIN, FINANCE) | Finance declined. MANAGER is absent here on purpose: a manager who could exercise the finance decision would collapse the separation of duties. | +| `AWAITING_FINANCE` | `CANCELLED` | indexer Β· reconciler | The escrow was cancelled on-chain. | +| `AWAITING_FINANCE` | `EXPIRED` | system | The approval window lapsed. | +| `READY_TO_SETTLE` | `SUBMITTING` | user (OWNER, ADMIN, MANAGER, FINANCE) | A settlement transaction is being built and signed. | +| `READY_TO_SETTLE` | `CANCELLED` | indexer Β· reconciler | The escrow was cancelled before settlement. | +| `SUBMITTING` | `CONFIRMING` | system | The network accepted the transaction; it awaits ledger close. | +| `SUBMITTING` | `SUBMISSION_FAILED` | system | The transaction never reached the network (build, simulate, sign or RPC failure). Nothing was submitted, so a retry cannot double-pay. | +| `CONFIRMING` | `PAID` | indexer Β· reconciler | A confirmed `payment/paid` event was observed in the contract log, which the contract emits only after the SAC transfer for that payee succeeded. | +| `READY_TO_SETTLE` | `PAID` | indexer Β· reconciler | Settled without this application driving the submission β€” by the CLI, a validation script, or another client. The chain is authoritative for settlement, so a `payment/paid` event is accepted from an approved payment even though we never recorded a SUBMITTING step. Refusing would strand every externally-settled payment in RECONCILIATION_REQUIRED, which is noise rather than safety. | +| `SUBMITTING` | `PAID` | indexer Β· reconciler | Confirmation arrived before our own SUBMITTING β†’ CONFIRMING update landed. A real race, and the log is the side that knows. | +| `CONFIRMING` | `SETTLEMENT_FAILED` | indexer Β· system | The transaction reached the chain and failed there. | +| `CONFIRMING` | `RECONCILIATION_REQUIRED` | reconciler Β· system | Confirmation timed out or the result was ambiguous. The outcome is genuinely unknown, and saying so beats guessing either way. | +| `SUBMISSION_FAILED` | `READY_TO_SETTLE` | user (OWNER, ADMIN, MANAGER, FINANCE) | Retry. Safe without reconciliation precisely because nothing reached the chain; the approvals that authorized it are still on-chain and intact. | +| `SUBMISSION_FAILED` | `CANCELLED` | user (OWNER, ADMIN, MANAGER) | Abandoned after a failed submission. | +| `SUBMISSION_FAILED` | `RECONCILIATION_REQUIRED` | reconciler | Reconciliation found chain activity for a submission we recorded as never sent β€” our record of "never submitted" was wrong. | +| `SETTLEMENT_FAILED` | `RECONCILIATION_REQUIRED` | reconciler Β· system | Establish what the chain actually did before anything is retried. | +| `SETTLEMENT_FAILED` | `PAID` | indexer Β· reconciler | A `payment/paid` event arrived for a payment we had recorded as failed. The log wins: our failure record was wrong. | +| `SETTLEMENT_FAILED` | `READY_TO_SETTLE` | reconciler | Reconciliation confirmed the chain did NOT settle. Deliberately not a user transition: retrying a transaction that reached the chain requires first establishing what it did, and a human clicking retry has not. | +| `RECONCILIATION_REQUIRED` | `PAID` | reconciler Β· indexer | Chain evidence confirms settlement. | +| `RECONCILIATION_REQUIRED` | `READY_TO_SETTLE` | reconciler | Chain evidence confirms no settlement occurred; approvals still stand. | +| `RECONCILIATION_REQUIRED` | `SETTLEMENT_FAILED` | reconciler | Chain evidence confirms the settlement attempt failed. | +| `RECONCILIATION_REQUIRED` | `CANCELLED` | user (OWNER, ADMIN) Β· reconciler | An administrator closes out an unrecoverable payment. | + +--- + +## 7. Actors + +| Actor | Authority | Examples | +|---|---|---| +| `user` | An organization role, resolved from `OrgMember` on every request | approve, reject, cancel a draft, submit, retry | +| `indexer` | The contract's **event log** | funding observed, oracle proof accepted, approvals observed, settlement confirmed | +| `reconciler` | **Live contract state** | catching the database up, recording disagreements | +| `system` | Internal process with no chain evidence | validation outcome, submission accepted/failed, window expiry | + +`indexer` and `reconciler` are separate even though both are machines: the +indexer reports what the log says, while the reconciler *adjudicates* a +disagreement. Collapsing them would let routine ingestion silently resolve +discrepancies a human should see. + +### Role permissions + +| Role | May | +|---|---| +| `OWNER`, `ADMIN` | Everything a manager or finance approver may, plus flagging for reconciliation and closing out unrecoverable payments | +| `MANAGER` | Submit for validation, record the manager approval, reject pre-funding, cancel a draft, submit settlement, retry | +| `FINANCE` | Record the finance approval, reject at the finance stage, submit settlement, retry | +| `VIEWER` | Read only β€” no transition permits a VIEWER | +| `WORKER` | No payment reads, no transitions. A payee cannot advance their own payment. | + +**Separation of duties.** `AWAITING_FINANCE β†’ REJECTED` excludes `MANAGER` +deliberately: a manager able to exercise the finance decision would collapse the +dual-approval gate, which is the product's central claim. `approvePayment` +derives the approval role from **membership, never from the request body** β€” a +`{role: 'FINANCE'}` field would let a manager satisfy both halves alone. An +`OWNER` acting for whichever approval is outstanding cannot supply both: the +second attempt is refused with 409 once the same wallet holds one. + +--- + +## 8. Idempotency and retries + +| Scenario | Behaviour | +|---|---| +| Repeated API action | A transition to the state a payment already holds returns `changed: false`, not an error | +| Repeated settlement submission | Keyed by `Idempotency-Key`. A repeat returns the **original attempt**; it does not submit again | +| Same key, different payment | 409 β€” a key is bound to one payment | +| Duplicate chain event | Keyed by RPC paging token; re-seen tokens are skipped | +| Event replayed under a new token | `Payment(escrowId, onChainPaymentIndex)` is unique, so no second payment can be created | +| Indexer restart | Cursor is per `(contract, network)` and advances only past committed events | +| RPC timeout, transaction actually landed | `reconcileFailedTransactions` detects it, opens a `FAILED_TX_ACTUALLY_SUCCEEDED` finding, and the finding text says **do not retry** | +| Browser refresh during confirmation | State lives in the database; `CONFIRMING` is shown until the indexer observes the result | + +**Concurrency.** Every transition is a compare-and-swap on the current state +(`where: { id, state: from }`). Two approvals racing, or an indexer running while +a user acts, would otherwise both read the same prior state and both write β€” +losing one transition and its audit entry. A zero-row update returns 409 +`CONCURRENT_MODIFICATION` rather than overwriting someone else's work. + +--- + +## 9. Reconciliation + +Two disagreements are acted on automatically, because in both the chain's answer +is unambiguous: + +| Finding | Action | +|---|---| +| Chain settled, database behind | Database advanced to `PAID` β€” the money moved regardless of our record | +| Database claims `PAID`, chain disagrees | `DB_PAID_CHAIN_NOT` finding opened. `PAID` is terminal, so the state is **not** rewritten; the table is not weakened to permit an exit | + +Everything else is recorded and left for an operator: + +`AMOUNT_MISMATCH` Β· `RECIPIENT_MISMATCH` Β· `MISSING_ON_CHAIN` Β· +`ORPHAN_ON_CHAIN` Β· `CHAIN_PAID_DB_NOT` Β· `FAILED_TX_ACTUALLY_SUCCEEDED` + +An **unreadable** escrow is explicitly not recorded as agreement β€” assuming "all +fine" when the chain cannot be read is how silent drift accumulates. Re-running +reconciliation does not multiply findings for one unchanged discrepancy; a noisy +queue gets ignored. + +--- + +## 10. Audit + +Every transition writes an `AuditEvent` carrying: event type, actor (address for +a person, `actorSystem` for a machine), organization, payment/batch/escrow +references, **previous and new state**, transaction hash where applicable, and +metadata including the transition's declared reason. + +Nothing in the application updates or deletes these rows. A "current status only" +column cannot answer how a payment reached that status β€” which is the question +asked whenever something has gone wrong. + +--- + +## 11. Why a batch has no stored status + +`PayrollBatch` deliberately has **no** aggregate status column. A stored rollup is +a second copy of mutable truth and will eventually disagree with the payments it +claims to summarize β€” and when it does, it is the copy people have already acted +on. + +`rollupBatch()` derives it, and reports a batch containing **any** broken payment +as broken rather than by majority. The one failed payment inside an +otherwise-paid batch is exactly the one a finance team needs to see. + +--- + +## 12. Known limitation: approval granularity is per-escrow + +On-chain, `manager_approve` and `finance_approve` take an **escrow id**, not a +payment id. One approval therefore advances **every payment in that escrow**. + +The projection mirrors this faithfully rather than pretending otherwise: the +indexer moves all payments in the escrow that are waiting on that specific +approver, and `Approval` rows are written per payment so the audit trail records +who approved what β€” but the *decision* was made once, at escrow granularity. + +**What this means in practice.** A payroll batch is approved as a batch. A +reviewer cannot approve eleven contractors and hold one back; they would have to +cancel the escrow and create a new one without that payee. + +**Why it is not being changed now.** Per-payment approval requires a contract +change β€” new entry points, per-payment approval state, and a reworked +`pay_batch` gate. That is a v3 decision driven by the product model, not a bug to +patch. Changing the settlement authorization path casually, on a contract holding +custody, would be a worse trade than living with batch-level granularity and +saying so. + +**When it will matter.** Larger batches. At twelve payees the blast radius of +"approve all or none" is tolerable; at two hundred it is not. The likely shape is +batch approval *and* an optional per-payment hold, so the common case stays one +signature. + +Tracked as an open product decision, not as completed work. + +--- + +## 13. Known limitation: whole hours + +v2 requires `hours Γ— rate == amount` with integer hours. `$1,001` at `$25/h` is +40.04 hours and is **rejected at escrow creation** rather than rounded. +Fractional-hour payroll needs a versioned scaled-hours schema (v3) β€” e.g. +`hours_scaled = 4004, scale = 100`. Hours and amounts are never silently rounded +to make a figure fit. diff --git a/docs/PRODUCTION_DATABASE_REMEDIATION.md b/docs/PRODUCTION_DATABASE_REMEDIATION.md new file mode 100644 index 0000000..cc1ab25 --- /dev/null +++ b/docs/PRODUCTION_DATABASE_REMEDIATION.md @@ -0,0 +1,329 @@ +# Production Database Remediation β€” Proposal Only + +**Status: PROPOSED. NOT EXECUTED.** +**Nothing in this document has been run against the production database.** + +Discovered 2026-09-11 during a local environment audit. The only commands run +against production were read-only `SELECT`s against `information_schema` and +`_prisma_migrations`, described in [Evidence](#evidence-how-this-was-observed). + +This document exists to be reviewed *before* anyone acts. Do not treat the +production database as repaired until an authorized production deployment has +actually done so and its output has been recorded here. + +--- + +## 1. Database state discovered + +| | | +|---|---| +| Host | `db.prisma.io` (managed Prisma Postgres) | +| Database | `postgres` | +| Reached via | `DATABASE_URL` as it appeared in the local `.env` / `.env.local` after a `vercel env pull` | +| Schema | `public` | +| Tables | 11 | + +Tables present: + +``` +AuditLog, AuthChallenge, ChainEvent, Escrow, IndexerCursor, +Invitation, OracleAttestation, Session, TimeLog, User, +_prisma_migrations +``` + +This is the **CoreFlow v1** schema. None of the v2 tables exist β€” +no `Organization`, `OrgMember`, `PayrollBatch`, `Payment`, `Approval`, +`BlockchainTransaction`, `AuditEvent`, `ReconciliationFinding`, +`ReconciliationRun`, `Project`, `Worker`. + +### Consequence, stated plainly + +**No part of the v2 hardening is deployed.** Multi-tenancy, the payment state +machine, reconciliation, per-payment indexing and dual approval all depend on +tables that do not exist in production. The live site runs v1 against v1 tables. + +--- + +## 2. Migration history in production + +| # | Migration | State | +|---|---|---| +| 1 | `20260616142115_init` | applied | +| 2 | `20260616143739_add_escrow_token` | applied | +| 3 | `20260616144425_add_oracle_attestation` | applied | +| 4 | `20260616145906_add_indexer_tables` | applied | +| 5 | `20260616150704_add_audit_log` | applied | +| 6 | `20260801000000_initial_schema` | **FAILED** | + +### The failed migration + +| Field | Value | +|---|---| +| `migration_name` | `20260801000000_initial_schema` | +| `started_at` | `2026-07-31T19:06:34.072Z` | +| `finished_at` | `null` | +| `rolled_back_at` | `null` | +| `applied_steps_count` | `0` | + +`finished_at` null with `rolled_back_at` null is Prisma's representation of a +**failed migration**. Any subsequent `prisma migrate deploy` against this +database aborts with **P3009** without attempting anything further, so the +deployment pipeline's migrate step is currently broken. + +`applied_steps_count: 0` is load-bearing: **not one statement was applied.** The +production schema was not partially modified. There is no half-built table and +nothing to clean up. + +--- + +## 3. Why it failed, and why the obvious fix is the wrong one + +The repository's migration history does **not** continue production's history. +It is a **squashed baseline** that recreates the same v1 schema from zero: + +``` +$ grep -oE 'CREATE TABLE "[A-Za-z]+"' \ + prisma/migrations/20260801000000_initial_schema/migration.sql | sort -u + +CREATE TABLE "AuditLog" CREATE TABLE "IndexerCursor" +CREATE TABLE "AuthChallenge" CREATE TABLE "Invitation" +CREATE TABLE "ChainEvent" CREATE TABLE "OracleAttestation" +CREATE TABLE "Escrow" CREATE TABLE "Session" +CREATE TABLE "IndexerCursor" CREATE TABLE "TimeLog" + CREATE TABLE "User" +``` + +Those are **exactly the ten tables production already has**, built there by +migrations 1–5. So the migration failed on its first statement with +`relation "..." already exists`, which is precisely what `applied_steps_count: 0` +records. + +### `migrate resolve --rolled-back` would not fix this + +It is the natural reading of a failed migration, and it is safe here β€” with zero +applied steps there is genuinely nothing to roll back, so the marker would be +accurate. But it resolves nothing: + +1. `migrate resolve --rolled-back 20260801000000_initial_schema` clears the + failure flag. +2. The next `migrate deploy` sees the migration as pending, **runs it again**, + and it fails **identically** on the same first `CREATE TABLE`. + +The failure is not a transient error that rollback-and-retry clears. It is a +**history mismatch**: two different migration histories describing the same +schema. Retrying cannot resolve that. + +### The appropriate action is baselining + +`20260801000000_initial_schema` describes a schema state production **is already +in**. That is the textbook definition of a baseline migration, and Prisma's +mechanism for it is `migrate resolve --applied`: + +1. `migrate resolve --applied 20260801000000_initial_schema` records it as + present **without executing it** β€” true, because migrations 1–5 already built + that schema. +2. `migrate deploy` then proceeds to migrations 2–8, which are the real v1 β†’ v2 + changes and whose statements have never run in production. + +This is only correct if production's schema genuinely matches what the baseline +would have created. **That equivalence must be verified, not assumed** β€” see +step 4.3. A baseline asserted over a schema that has drifted will make later +migrations fail in harder-to-diagnose ways. + +--- + +## 4. Proposed remediation + +Every step is to be performed by an authorized operator against production, +deliberately. Nothing here is wired into any script, and no `npm` task reaches +production. + +### 4.1 Freeze + +Stop writes for the duration: pause Vercel Cron (the indexer hits +`GET /api/indexer/run`) and avoid deploys. Migrations 2–8 retype columns and add +constraints; concurrent writes during that window produce partial states that +are significantly harder to reason about than a short maintenance pause. + +### 4.2 Snapshot β€” mandatory, verified before proceeding + +Take a full backup and **confirm it is restorable**. An unverified backup is not +a backup. + +```bash +# Structure + data, custom format. +pg_dump --format=custom --no-owner --no-privileges \ + --file=coreflow-prod-$(date -u +%Y%m%dT%H%M%SZ).dump \ + "$PRODUCTION_DATABASE_URL" + +# Prove it is readable and contains the expected objects. +pg_restore --list coreflow-prod-*.dump | grep -c 'TABLE DATA' +``` + +Also capture the managed provider's own point-in-time snapshot if available, and +**record the row counts** migrations 2–8 will touch, so the verification in 4.5 +has something to compare against: + +```sql +SELECT 'User' AS t, count(*) FROM "User" +UNION ALL SELECT 'Escrow', count(*) FROM "Escrow" +UNION ALL SELECT 'TimeLog', count(*) FROM "TimeLog" +UNION ALL SELECT 'OracleAttestation', count(*) FROM "OracleAttestation" +UNION ALL SELECT 'ChainEvent', count(*) FROM "ChainEvent" +UNION ALL SELECT 'AuditLog', count(*) FROM "AuditLog"; +``` + +### 4.3 Verify the baseline claim β€” BLOCKING + +Do not run step 4.4 until this produces an empty diff. This needs a shadow +database, so it is **currently blocked on local Postgres** (see +[ENVIRONMENTS.md](ENVIRONMENTS.md)). + +```bash +# Does production's live schema match the state the baseline migration describes? +npx prisma migrate diff \ + --from-url "$PRODUCTION_DATABASE_URL" \ + --to-migrations ./prisma/migrations/20260801000000_initial_schema \ + --shadow-database-url "$LOCAL_SHADOW_DATABASE_URL" \ + --script +``` + +- **Empty output** β†’ production is in the baseline state. Baselining is sound; + continue. +- **Any output** β†’ production has drifted from the squashed baseline. **Stop.** + The drift must be understood and reconciled first; asserting the baseline + anyway would make migrations 2–8 run against a schema they were not written + for. Attach the diff to this document and re-review. + +Note: `--from-url` is read-only with respect to production. The shadow database +is written to and reset, which is why it **must never be a production URL** β€” +Prisma resets shadow databases. + +### 4.4 Record the baseline + +```bash +npx prisma migrate resolve --applied 20260801000000_initial_schema +``` + +Expected: one row in `_prisma_migrations` with `finished_at` set and +`applied_steps_count` 0. No DDL is executed by this command. + +### 4.5 Apply the real v1 β†’ v2 migrations + +```bash +npx prisma migrate deploy +``` + +This applies migrations 2–8: + +| Migration | What it does | +|---|---| +| `20260910000000_money_base_units` | money β†’ integer base units | +| `20260910120000_payment_state_machine` | `PaymentState`, transitions, audit | +| `20260911000000_multi_tenancy` | organizations, members, composite FKs | +| `20260911010000_chain_event_attribution` | tenant attribution for chain events | +| `20260911020000_reconciliation_reliability` | findings, runs | +| `20260911030000_reconciliation_run_lock` | partial unique index on `RUNNING` | +| `20260911040000_payroll_batch_idempotency` | batch idempotency key, `sourceReference` | + +These are **not** additive-only. The money and multi-tenancy migrations retype +columns and introduce NOT NULL constraints with backfills, and were hand-ordered +(widen β†’ backfill β†’ constrain β†’ drop). They have only ever been applied to +databases built from this same history. **Rehearse them against a restored copy +of the production dump before production**, not only against a from-zero local +database: + +```bash +# On a scratch database restored from the 4.2 dump: +pg_restore --dbname "$REHEARSAL_DATABASE_URL" --no-owner coreflow-prod-*.dump +DATABASE_URL="$REHEARSAL_DATABASE_URL" npx prisma migrate deploy +``` + +A from-zero run proves the SQL is valid. Only a restored-copy run proves it is +valid **against production's actual data**. + +### 4.6 Verification + +```bash +npx prisma migrate status # all 8 applied, none failed +``` + +```sql +-- 28+ tables, v2 tables present +SELECT count(*) FROM information_schema.tables WHERE table_schema='public'; +SELECT table_name FROM information_schema.tables + WHERE table_schema='public' + AND table_name IN ('Organization','OrgMember','PayrollBatch','Payment', + 'Approval','ReconciliationRun'); + +-- No failed or unfinished rows remain +SELECT migration_name FROM _prisma_migrations + WHERE finished_at IS NULL OR rolled_back_at IS NOT NULL; + +-- Row counts from 4.2 are unchanged (no migration should delete v1 data) +``` + +Then, against production, confirm the application actually works rather than +assuming a green migration means a working product: + +- `GET /api/health/ready` +- sign in with a wallet +- load a page that reads `Escrow` +- confirm the indexer cursor advances after Cron resumes + +### 4.7 Unfreeze + +Resume Vercel Cron. Record in this document: who ran it, when, the +`migrate status` output, and the verification results. + +--- + +## 5. Rollback plan + +| Failure point | Action | +|---|---| +| 4.3 diff is non-empty | Stop. Nothing was changed. Re-review. | +| 4.4 resolve is wrong | `migrate resolve --rolled-back 20260801000000_initial_schema` returns the row to its prior state. No DDL ran. | +| 4.5 fails partway | **Restore from the 4.2 dump.** Do not hand-patch. These migrations retype columns and backfill; a partially applied money or multi-tenancy migration leaves data in an ambiguous state, and "fixing forward" on financial records without knowing which rows converted is how silent corruption happens. | +| Application broken after 4.5 | Restore from the 4.2 dump, redeploy the previous application build, then diagnose off production. | + +Restore: + +```bash +pg_restore --clean --if-exists --no-owner \ + --dbname "$PRODUCTION_DATABASE_URL" coreflow-prod-.dump +``` + +--- + +## 6. Evidence β€” how this was observed + +Read-only. Two queries, both against catalog tables: + +```sql +SELECT table_name FROM information_schema.tables + WHERE table_schema='public' ORDER BY table_name; + +SELECT migration_name, started_at, finished_at, + rolled_back_at, applied_steps_count + FROM _prisma_migrations + WHERE finished_at IS NULL OR rolled_back_at IS NOT NULL; +``` + +No write, DDL, seed, reset or `migrate` command has been run against this +database from the development workflow. The local environment has since been +repointed away from it, and `scripts/check-env.mjs` now refuses to run +development tasks against a non-local database. + +## 7. Open questions for the operator + +1. Is `db.prisma.io/postgres` the database serving `coreflow-psi.vercel.app`, or + a preview/branch database? The remediation path is the same; the blast radius + is not. +2. Who attempted `20260801000000_initial_schema` on 2026-07-31, and was the + squashed baseline intended to replace the `20260616*` history in production? + If the squash was meant to be local-only, the cleaner fix may be to restore + the original five migration directories instead of baselining. +3. Does production hold real user data, or only test records? This determines + whether 4.5's rehearsal-on-a-restored-copy is mandatory or merely advisable. + It is recommended either way. diff --git a/docs/RBAC.md b/docs/RBAC.md new file mode 100644 index 0000000..b41207f --- /dev/null +++ b/docs/RBAC.md @@ -0,0 +1,179 @@ +# CoreFlow RBAC + +**6 roles Β· 35 permissions.** Generated from `src/lib/tenancy/rbac.ts`, which is +the single source of truth. If this document disagrees with the code, the code is +right and this is stale. + +--- + +## 1. Where roles sit in the security boundary + +``` +AUTHENTICATION who are you wallet signature, session +MEMBERSHIP which organizations OrgMember, status = ACTIVE +ROLE what may you do ← this document +RESOURCE OWNERSHIP is this record in scope tenancy/resolve.ts + composite FKs +BUSINESS RULE is the action valid now payments/state-machine.ts +BLOCKCHAIN did it actually happen contract + indexer +``` + +These do not collapse into each other. Holding a role does not imply owning a +record; owning a record does not imply the action is valid right now; and none of +them imply money moved. + +**Permissions are organization-scoped.** A role is held *within* one organization +and grants nothing anywhere else. There is no cross-tenant or platform-wide +authority over payment data β€” the legacy platform `Role` (ADMIN/EMPLOYEE) governs +only sign-in and the admin bootstrap path. + +--- + +## 2. The roles + +| Role | Purpose | +|---|---| +| **OWNER** | Full authority, including deleting the organization. | +| **ADMIN** | Operational authority. Cannot delete the organization or mint an OWNER. | +| **MANAGER** | Prepares payroll and holds the MANAGER half of the approval gate. | +| **FINANCE** | Holds the FINANCE half. Cannot create the payroll it approves. | +| **WORKER** | A payee, not an operator. Holds **no** permissions. | +| **VIEWER** | Read-only. | + +### Separation of duties + +MANAGER and FINANCE are separate and neither implies the other. This is the +product's central claim, and the contract enforces the same property on-chain with +`SignersNotDistinct`. + +Three consequences, each tested: + +- A MANAGER cannot hold `payment:approve:finance`, and vice versa. +- FINANCE cannot hold `payroll:create`, `worker:create` or `escrow:create` β€” an + approver who can also create what they approve is not an independent check. +- Even an OWNER cannot supply both halves of one payment: `approvePayment` refuses + a second approval from a wallet that already recorded the first. + +### WORKER holds nothing + +A worker sees their own payments through a self-scoped query +(`paymentReadScope`) that filters on their wallet address β€” not through +`payment:read`. Granting organization-wide read would let any contractor enumerate +the entire payroll, including colleagues' rates. + +--- + +## 3. Permission matrix + +| Permission | OWNER | ADMIN | MANAGER | FINANCE | WORKER | VIEWER | +|---|---|---|---|---|---|---| +| `audit:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `escrow:cancel` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `escrow:create` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `escrow:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `member:invite` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `member:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `member:remove` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `member:role:assign` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `member:suspend` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `oracle:attest:request` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `org:delete` | βœ… | β€” | β€” | β€” | β€” | β€” | +| `org:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `org:update` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `payment:approve:finance` | βœ… | βœ… | β€” | βœ… | β€” | β€” | +| `payment:approve:manager` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `payment:cancel` | βœ… | βœ… | βœ… | βœ… | β€” | β€” | +| `payment:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `payment:reject` | βœ… | βœ… | βœ… | βœ… | β€” | β€” | +| `payment:retry` | βœ… | βœ… | βœ… | βœ… | β€” | β€” | +| `payment:submit` | βœ… | βœ… | βœ… | βœ… | β€” | β€” | +| `payroll:create` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `payroll:delete` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `payroll:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `payroll:update` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `project:archive` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `project:create` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `project:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `project:update` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `reconciliation:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `reconciliation:resolve` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `treasury:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `worker:archive` | βœ… | βœ… | β€” | β€” | β€” | β€” | +| `worker:create` | βœ… | βœ… | βœ… | β€” | β€” | β€” | +| `worker:read` | βœ… | βœ… | βœ… | βœ… | β€” | βœ… | +| `worker:update` | βœ… | βœ… | βœ… | β€” | β€” | β€” | + +--- + +## 4. Role delegation + +Who may grant which role. Strictly at or below the granter's own level, with one +deliberate exception: an OWNER may create another OWNER, because an organization +with exactly one owner has no recovery path if that key is lost. + +| Role | May grant | +|---|---| +| OWNER | OWNER, ADMIN, MANAGER, FINANCE, WORKER, VIEWER | +| ADMIN | ADMIN, MANAGER, FINANCE, WORKER, VIEWER | +| MANAGER | *(none)* | +| FINANCE | *(none)* | +| WORKER | *(none)* | +| VIEWER | *(none)* | + +Two refusals worth naming, because both are plausible mistakes rather than exotic +attacks: + +- **An ADMIN cannot mint an OWNER.** Otherwise an admin can take the organization. +- **A MANAGER cannot mint a FINANCE approver.** Otherwise a manager manufactures + the second approval they are forbidden from giving. + +Delegation is checked as a pair: the actor needs `member:role:assign` *and* the +specific target role must be within their delegation. Holding the permission does +not imply every role is in reach. + +### Self-targeting + +Nobody may change their own role or membership status β€” self-assignment is how a +limited role becomes an unlimited one. `checkNotSelf` returns 409. + +### Last administrator + +An action that would leave an organization with no ACTIVE administrator is refused +with 409 `LAST_ADMINISTRATOR`. This covers removal, suspension **and** demotion. A +SUSPENDED administrator does not count as cover: an organization whose only other +admin is suspended has nobody who can unsuspend them. + +--- + +## 5. HTTP semantics + +| Situation | Status | Why | +|---|---|---| +| Not signed in | **401** | | +| Resource outside the caller's organizations | **404** | A 403 confirms existence, turning id substitution into an enumeration oracle | +| Nonexistent resource | **404** | Identical response to the above, by design | +| Member, but role lacks the permission | **403** | The caller demonstrably belongs here, so there is nothing to conceal | +| Conflicts with organization state | **409** | e.g. last administrator, invalid membership transition | +| Caller belongs to several organizations, none named | **400** | Guessing could act in the wrong tenant's name | + +--- + +## 6. Enforcement + +Backend authorization is **authoritative**. UI visibility is a convenience, never a +control: `GET /api/organizations` returns each membership's permission list so the +client renders from the same data the server enforces, rather than re-deriving the +rules and drifting. + +Every tenant-scoped route passes through `withTenant()`, which resolves +authentication β†’ membership β†’ permission before the handler runs. No route can +forget a layer because no route performs these checks itself. + +--- + +## 7. Tests + +`src/lib/tenancy/__tests__/rbac.test.ts` enumerates the whole matrix, including +the cells that must be **empty**: every permission is asserted against every role, +every non-read permission is refused for VIEWER, and all 35 are refused for +WORKER. A table tested only on what it allows would happily let a MANAGER approve +finance. diff --git a/docs/RECONCILIATION.md b/docs/RECONCILIATION.md new file mode 100644 index 0000000..4b00dbd --- /dev/null +++ b/docs/RECONCILIATION.md @@ -0,0 +1,274 @@ +# CoreFlow Reconciliation + +Reconciliation is an **independent correctness check** on the payment projection, +not a background tidy-up. This document states what it verifies, how independently, +what it will and will not change, and how an operator works with it. + +> **Scope claim.** Reconciliation runs on a schedule, records every run, detects +> the discrepancy classes below, and surfaces them with remediation. It is not +> described here as production-grade: see Β§11 for what is missing. + +--- + +## 1. Authority + +| Authoritative for | Source | +|---|---| +| Token movement, settlement, custody | **Chain** | +| Contract state and authorization | **Chain** | +| Transaction outcome | **Chain** | +| Application projections, workflow metadata | PostgreSQL | +| Organization/project relationships, tenancy | PostgreSQL | +| Audit presentation, search, reconciliation findings | PostgreSQL | + +The database never silently overrides chain evidence. Where they disagree, the +disagreement is **recorded**. + +--- + +## 2. Independence: what is actually verified + +A reconciler that re-read CoreFlow's own events, through the same parser, into the +same projection would verify nothing β€” a bug in how CoreFlow emits or decodes its +events would validate itself. + +Verification therefore comes from sources CoreFlow did not author: + +| Source | Why it is independent | +|---|---| +| **SAC `transfer` events** | Emitted by the TOKEN contract: `transfer / from / to / asset β†’ amount`. This is the actual movement of value. If CoreFlow claims a payment settled and no transfer from the escrow contract to that recipient for that amount exists, the claim is false regardless of CoreFlow's own log. | +| **Contract storage (`get_escrow`)** | A read of current state, not of the event stream β€” a different derivation of the same truth. | +| **Transaction results** | Whether a specific hash actually succeeded. | + +The indexer trusts `payment/paid`. The reconciler does not. That asymmetry is the +point. + +### Transaction-scoped matching + +A transfer is matched on `(from = escrow contract, to = recipient, asset, exact +amount)` **within one transaction**. + +The transaction scope is load-bearing, and live validation is what proved it: the +tuple without a transaction is **not unique**, because the same escrow contract +pays the same contractor the same rate every pay period. Seven identical payroll +runs produced seven identical transfers, and treating those as seven matches for +one payment reported a duplicate payment that never happened. + +- When the payment records a settlement transaction, the match is confined to it. +- Otherwise the settling transaction is inferred as the most recent one whose + transfers cover **every** expected payment of the escrow β€” a `pay_batch` + transaction contains one transfer per payee. +- `DUPLICATE_PAYMENT_EVENT` is raised only for two transfers to one payee **inside + a single transaction**. Across transactions that is normal recurring payroll. + +--- + +## 3. Outcomes + +| Outcome | Meaning | +|---|---| +| `AGREED` | Projection, contract state and observed transfer all match | +| `CHAIN_AHEAD` | Chain settled; projection behind. **Corrected.** | +| `DATABASE_AHEAD` | Database claims PAID; chain does not support it. **Never reverted.** | +| `CHAIN_UNREADABLE` | Could not check. Not agreement, not a mismatch. | +| `MISMATCHED` | Identity or amount disagrees | +| `UNKNOWN_ON_CHAIN_OBJECT` | On-chain escrow belongs to no organization | +| `ORPHANED_DATABASE_OBJECT` | Recorded payment with no on-chain slot | + +### Only two automatic corrections + +`CHAIN_AHEAD β†’ PAID` and *escrow cancelled on-chain* `β†’ CANCELLED`. + +Both require independent confirmation. A `CHAIN_AHEAD` payment is advanced **only** +when an observed SAC transfer corroborates it: if the contract reports `FINALIZED` +but no transfer is visible, the projection is **not** advanced and a +`MISSING_PAYMENT_EVENT` finding is opened. Advancing on contract state alone would +defeat the independent check. + +### `DATABASE_AHEAD` is never reverted + +`PAID` is terminal in the payment state machine and stays so. The finding is the +durable record, and the state machine is not weakened to permit an exit. + +A system that silently un-pays a payment to look consistent has destroyed the +evidence of its own worst bug. The operator workflow is in Β§8. + +--- + +## 4. Finding taxonomy + +| Kind | Severity | Meaning | +|---|---|---| +| `DB_PAID_CHAIN_NOT` | **CRITICAL** | Shown as settled; chain disagrees | +| `FAILED_TX_ACTUALLY_SUCCEEDED` | **CRITICAL** | Recorded failed, actually succeeded β€” retry would double-pay | +| `AMOUNT_MISMATCH` | HIGH | Settled amount β‰  recorded amount | +| `RECIPIENT_MISMATCH` | HIGH | Funds reached a different address | +| `ASSET_MISMATCH` | HIGH | Settled in a different asset | +| `DUPLICATE_PAYMENT_EVENT` | HIGH | Two transfers for one payee in one transaction | +| `MISSING_PAYMENT_EVENT` | HIGH | Contract says settled, no transfer observed | +| `CHAIN_PAID_DB_NOT` | MEDIUM | Projection lagging | +| `MISSING_ON_CHAIN` | MEDIUM | Recorded payment with no on-chain slot | +| `ORPHAN_ON_CHAIN` | MEDIUM | On-chain payment with no database row | +| `UNKNOWN_ON_CHAIN_OBJECT` | LOW | Escrow belongs to no organization | +| `CHAIN_UNREADABLE` | LOW | Could not check | +| `OTHER` | MEDIUM | Unclassified β€” taxonomy needs an entry | + +**CRITICAL is reserved for findings where the product may be making a false +statement about money.** Everything else, however annoying, is a lag or an +operational issue. Every kind carries operator-facing `remediation`; a finding +without it is a puzzle. + +--- + +## 5. Scheduling and concurrency + +Triggered by **Vercel Cron** (`vercel.json`, hourly at :17) against +`POST /api/reconciliation/run`, protected by `CRON_SECRET`. An operator can also +run one organization via `POST /api/organizations/:id/reconciliation`. + +**No job framework.** CoreFlow deploys on Vercel, where there are no long-lived +workers. Redis or a queue purely to own a cron tick would be infrastructure with no +other purpose and one more thing that can be down. The lock lives in PostgreSQL, +which the application already depends on absolutely. + +### The lock + +```sql +CREATE UNIQUE INDEX "ReconciliationRun_one_running_per_org" + ON "ReconciliationRun" ("orgId") WHERE "status" = 'RUNNING'; +``` + +A check-then-insert is a race: two workers on the same tick both pass the check and +both insert. The partial unique index makes PostgreSQL refuse the second, so the +lock does not depend on the application noticing. Verified directly against the +database β€” see the evidence package. + +### Heartbeats + +A run refreshes `heartbeatAt` every 30s. A run whose heartbeat is older than +**10 minutes** is marked `STALE` and its lock reclaimed. Stale runs are marked, not +deleted: that a run died is evidence, and silently reusing the lock would erase it. + +### Bounded scope + +`maxEscrows` caps one pass. The platform sweep processes at most 50 organizations +per invocation; the rest are picked up next tick rather than making one invocation +unbounded. One tenant's RPC failure does not abort the sweep. + +--- + +## 6. Run records + +Every run records scope, correlation id, status, timings, and counters +(`escrowsExamined`, `paymentsExamined`, `agreed`, `mismatched`, `unreadable`, +`chainAhead`, `databaseAhead`, `findingsOpened`, `correctionsApplied`). + +A failure still **completes** the record, marked `FAILED` with the error. A run that +simply stops existing is indistinguishable from one that never started β€” and then +"no findings" reads as health. + +`reconciliationHealth()` reports **DEGRADED** when the last run failed, went stale, +stopped responding, or never happened. The UI refuses to show "all clear" in that +state: an empty findings list after a failed run means nothing was checked. + +--- + +## 7. Retry behaviour + +| Condition | Behaviour | +|---|---| +| RPC timeout reading an escrow | `CHAIN_UNREADABLE` finding; nothing downgraded; retried next run | +| RPC timeout reading transfers | Payment **not** advanced; `CHAIN_UNREADABLE` | +| Escrow genuinely absent on-chain | `MISSING_ON_CHAIN` β€” distinct from unreadable | +| Transaction not found (beyond retention) | Left alone; absence of a record is not failure | +| Finding still present next run | Re-observed: `lastObservedAt` and `observationCount` updated, no duplicate row | +| Finding resolved, then recurs | A **new** finding opens | + +A duplicated queue becomes noise, and a noisy queue gets ignored β€” the same as +having none. + +--- + +## 8. Operator workflow + +``` +OPEN β†’ ACKNOWLEDGED β†’ INVESTIGATING β†’ RESOLVED +``` + +`PATCH /api/organizations/:id/findings/:findingId`, requiring +`reconciliation:resolve` (OWNER/ADMIN). Resolution requires a substantive +explanation β€” a "mark resolved" button with no reason turns the queue into a +dismiss button, and the next reader during an incident learns nothing. Actor, +timestamp and reason are recorded, and an `AuditEvent` is written. + +**What resolution cannot do:** change a payment's state, amount, recipient or +transaction hash. It records a human judgement *about* a discrepancy. Only +lifecycle fields are written; a test asserts exactly which. + +### Resolving a `DB_PAID_CHAIN_NOT` + +1. Open the transaction on the explorer (link is on the finding). +2. If the transfer exists, the database was right and the verifier's window missed + it β€” resolve, noting the transaction. +3. If it does not, the payment did not settle. The payment record stays `PAID` + (terminal), so re-issuing requires a **new** payment; note the original finding + id in the new batch's reference. +4. Either way the resolution text must say which was established. + +### Investigating an orphaned escrow + +`UNKNOWN_ON_CHAIN_OBJECT` names the escrow id. CoreFlow will not guess an owner. If +it is yours, claim it via `POST /api/organizations/:id/escrows/claim` with the +wallet that created it β€” the claim verifies on-chain manager against live state. +Events recorded before the claim are replayed, not lost. + +--- + +## 9. Observability + +Every run has a correlation id (`rec_`) threaded through its logs, stored on +the run and on every finding it observed. The trace is: + +``` +run correlationId β†’ organization β†’ escrow (onChainId) β†’ payment β†’ transaction hash β†’ finding id +``` + +Never logged: private keys, wallet secrets, session secrets, authentication +payloads. Findings carry addresses and amounts, which are tenant data and are +served only through tenant-scoped endpoints. + +### Metrics available from run records + +Runs (total/succeeded/failed), findings opened and resolved, chain-ahead +corrections, unreadable cases, orphaned objects, run duration +(`completedAt - startedAt`), and oldest unresolved finding age. + +--- + +## 10. Security + +| Control | Implementation | +|---|---| +| Cron trigger not user-reachable | `CRON_SECRET`, constant-time compare, minimum 16 chars, rate limited, 404 on every failure | +| Findings are tenant-scoped | `withTenant` + `orgId` in every query; cross-tenant finding id β†’ 404 | +| Resolution is authorized | `reconciliation:resolve` (OWNER/ADMIN only) | +| Cannot forge PAID | Only `applyTransition` changes state, and only on observed transfer evidence | +| Cannot inject a transaction hash | Hashes come from observed transfers, never from a request | +| Cannot mutate an amount | Reconciliation has no amount-write path at all | +| No user-supplied chain confirmation | The verifier reads RPC; request content is never evidence | +| Organization spoofing | Organization comes from membership, never from the request | + +--- + +## 11. What is NOT done + +| Gap | Status | +|---|---| +| **Alerting is in-product only** | Critical findings surface in the API and the operator panel. There is **no** email, Slack or pager integration β€” nobody is woken up. An unattended deployment would not notice a CRITICAL finding until someone looked. | +| **Operator panel is not routed** | `ReconciliationPanel` is built and tested but not yet mounted on a dashboard page. | +| **Transfer history is bounded by RPC retention** | The default lookback is ~16,000 ledgers (~22h). A payment older than the node's retained event history reads as `CHAIN_UNREADABLE`, not as verified. Long-horizon verification needs an archive or stored per-payment transfer evidence. | +| **No per-payment transfer cache** | Every run re-reads the token's events. Fine at current volume; at scale this needs the verified transfer recorded against the payment on first confirmation. | +| **Batch aggregate check is implicit** | Payment-level verification is exhaustive, and a batch is verified by verifying each of its payments. There is no separate stored batch total assertion β€” deliberately, since a stored aggregate is a second copy of mutable truth. | +| **No automated retry/backoff schedule** | Unreadable findings clear on the next scheduled run; there is no escalating retry for a persistently unreachable RPC. | +| **Not load-tested** | Behaviour with thousands of payments per organization is unmeasured. | +| **Live test suites cannot run in parallel** | The three opt-in live suites share one database and each resets the chain-event table, so they must be invoked sequentially (one `vitest run` per file). Running them together produces spurious failures. Not a product defect, but a real constraint on the validation procedure. | diff --git a/docs/SECRET_ROTATION.md b/docs/SECRET_ROTATION.md new file mode 100644 index 0000000..380ba53 --- /dev/null +++ b/docs/SECRET_ROTATION.md @@ -0,0 +1,224 @@ +# Secret rotation β€” runbook + +> **Status: PREPARED, NOT EXECUTED.** No secret has been rotated. No Vercel +> environment variable has been changed. `docs/evidence/secret-rotation.json` +> does not exist, which is the check for whether any of this has run. + +Executor: [`scripts/rotate-secrets.mjs`](../scripts/rotate-secrets.mjs). It emits +its own evidence, because a rotation you cannot demonstrate is a rotation you +have not finished. + +## What was exposed, and how far + +`prodenv.txt` β€” a `vercel env pull` dump β€” sits in the working directory holding +live production values. Its contents were enumerated by variable **name** to +establish the real scope, because acting on an assumed scope is how a rotation +misses something or wastes effort on something it never touched: + +| Variable in the dump | Classification | +|---|---| +| `ORACLE_SECRET_KEY` | secret β€” **on-chain coupled**, excluded below | +| `AUTH_SECRET` | secret β€” rotate | +| `BOOTSTRAP_SECRET` | secret β€” prefer removal | +| `DATABASE_URL`, `DIRECT_URL`, `PRISMA_DATABASE_URL`, `POSTGRES_URL` | credential β€” provider-side rotation | +| `VERCEL_OIDC_TOKEN` | short-lived, self-expiring β€” no action | +| `ADMIN_WALLETS`, `VERCEL_URL`, `NEXT_PUBLIC_*` | not secret | + +**`CRON_SECRET` and `INDEXER_SECRET` are not in this dump** β€” `grep -ci +'cron|indexer'` returns 0. Earlier notes, including `BACKLOG.md` item 7, listed +them among the exposed values; that was wrong and is corrected here. They are +still worth rotating as hygiene, and the procedure below covers them, but they +are **not** part of this exposure and should not be treated as urgent on its +account. Overstating a breach misdirects the response as surely as understating +it does. + +Containment, verified: + +| Check | Result | +|---|---| +| Tracked in `HEAD` | no | +| Added in any reachable commit (`--all`) | no | +| Matched by `.gitignore` | yes β€” `.gitignore:73`, `*env*.txt` | + +So the exposure is **local disk only**; it was never published through git. That +lowers the urgency but does not remove the requirement: a credential that has sat +in a plaintext file of unclear handling is a credential of unknown disclosure. + +**Do not delete `prodenv.txt` yet.** It is the record of the values being +replaced, and you need it to confirm the old values are dead. Dispose of it in +the last step. + +## What is NOT rotated here, and why + +### `ORACLE_SECRET_KEY` β€” excluded, deliberately + +The oracle public key is *derived* from this secret +([`src/lib/oracle/index.ts:74`](../src/lib/oracle/index.ts)) and stored as each +escrow's `oracle_pubkey`. The contract accepts attestations only from a key its +admin has registered. Rotating the secret is therefore **not an environment +change β€” it is an on-chain change**, and that registration is the action +currently blocked on the owner confirming the key mapping. + +Rotating it now would be actively harmful. The open question is which of two +existing keys is post-rotation; minting a third candidate destroys the ability to +answer it. `scripts/rotate-secrets.mjs` refuses this secret outright. + +See [ORACLE_KEY_TRANSITION.md](ORACLE_KEY_TRANSITION.md). + +### The database credential β€” provider-side, not scriptable + +It exists in the Postgres provider *and* in four environment variables. Changing +either half alone breaks the deployment. Procedure is at the end of this file. + +## Order of operations + +A Vercel environment change **does not affect the running deployment.** Nothing +cuts over until the next deployment. This is the single most useful fact here: +you can stage every change calmly, and the redeploy is the atomic moment. + +``` +1. rotate the env vars ← no user-visible effect yet +2. redeploy ← the cutover +3. prove each OLD value dead ← the step that makes it real +4. dispose of prodenv.txt ← only after 3 passes +``` + +## 1. Cron and indexer secrets β€” hygiene, and rotate BOTH or neither + +Not part of the `prodenv.txt` exposure (see above). Sequenced first only because +it is the lowest-risk change β€” no sessions break and no user notices. + +Both guarded endpoints resolve the secret with a fallback: + +```ts +// src/app/api/indexer/run/route.ts:16 +const secret = process.env.INDEXER_SECRET || process.env.CRON_SECRET; +// src/app/api/reconciliation/run/route.ts:37 +const expected = process.env.CRON_SECRET || process.env.INDEXER_SECRET || ''; +``` + +Rotating one leaves the other accepted. **Rotating only `CRON_SECRET` rotates +nothing** β€” the exposed `INDEXER_SECRET` still authenticates. + +```bash +node scripts/rotate-secrets.mjs --rotate CRON_SECRET +node scripts/rotate-secrets.mjs --rotate INDEXER_SECRET +``` + +Update the Vercel Cron configuration to the new `CRON_SECRET`, or scheduled +indexing and reconciliation stop silently at the next run. + +## 2. `AUTH_SECRET` β€” a hard cutover + +`src/lib/auth/jwt.ts` signs HS256 with a single secret and has no overlap +window, so rotation invalidates every issued session at redeploy. Every user is +logged out and signs in again with their wallet. With the current user count that +is acceptable; it is not a graceful rotation, and it should not be presented as +one. + +```bash +node scripts/rotate-secrets.mjs --rotate AUTH_SECRET +``` + +## 3. `BOOTSTRAP_SECRET` β€” prefer removal + +This guards `POST /api/admin/bootstrap`, which claims the **first** admin. Unset, +the endpoint answers 404 and no secret reaches it at all. If an admin already +exists in production, removal is strictly better than rotation β€” rotation keeps a +live door for a one-time operation that is already complete. + +```bash +node scripts/rotate-secrets.mjs --remove BOOTSTRAP_SECRET +``` + +**This one cannot be verified by probing**, and the script refuses to try: + +- On a secret match the route proceeds to `prisma.user.upsert`, defaulting the + wallet to `ADMIN_WALLETS[0]`. A probe that *succeeded* would grant admin in + production β€” the check would cause the thing it checks for. +- The endpoint returns 404 whether the secret is unset **or** merely wrong, so a + 404 proves nothing. + +Verify from configuration instead: + +```bash +vercel env ls production | grep BOOTSTRAP_SECRET # expect no row +``` + +…then confirm a deployment was created after the removal. + +## 4. Redeploy + +Until this happens, nothing above has taken effect. + +## 5. Prove the old values are dead + +This is the step that distinguishes a rotation from an intention. The old secret +goes in on **stdin, never argv** β€” argv is world-readable via `ps`. + +```bash +node scripts/rotate-secrets.mjs --verify-dead CRON_SECRET \ + --url https://coreflow-psi.vercel.app < old-cron-secret.txt + +node scripts/rotate-secrets.mjs --verify-dead AUTH_SECRET \ + --url https://coreflow-psi.vercel.app < old-auth-secret.txt +``` + +- `CRON_SECRET` / `INDEXER_SECRET`: presents the old bearer to both guarded + endpoints; expects rejection from each. +- `AUTH_SECRET`: mints an HS256 JWT signed with the **old** secret and presents + it as a `cf_session` cookie to `/api/auth/me`. Acceptance would mean the old + secret still validates sessions. + +The script exits non-zero and records `old_value_rejected_everywhere: false` if +any old credential still works. Two guards keep that record honest: it refuses a +path with no `route.ts` in source (a mistyped path 404s, which would otherwise +read as a rejection), and it refuses a stdin value under 32 characters (so a +placeholder cannot manufacture a passing record). + +Shred the temporary files afterwards: `shred -u old-*.txt`. + +## 6. The database credential + +Ordered, because the halves must not drift: + +1. Change the password in the Postgres provider's console. Keep the old user + alive for the moment if the provider allows it. +2. Update all four variables to the new credential β€” `DATABASE_URL`, + `DIRECT_URL`, `PRISMA_DATABASE_URL`, `POSTGRES_URL`. `check-env.mjs` already + knows all four; missing one leaves a path authenticating with the old value. +3. Redeploy, then confirm the app reads and writes. +4. Revoke the old credential provider-side, and confirm it can no longer connect. + +Do **not** point local development at the rotated production database while +doing this. `npm run check:env` enforces that and should stay enforcing it. + +## 7. Dispose of `prodenv.txt` + +Only once every old value above is proven dead: + +```bash +shred -u prodenv.txt +``` + +Then re-run `npm run check:env` to confirm the local environment is still +`profile: LOCAL`. + +## Open finding β€” unequal guards on the same secret + +`CRON_SECRET`/`INDEXER_SECRET` protect two endpoints, and the two do not defend +equally: + +| | `reconciliation/run` | `indexer/run` | +|---|---|---| +| Comparison | `timingSafeEqual` over SHA-256 | `header === \`Bearer ${secret}\`` | +| Minimum length enforced | yes | no | +| Rate limited | yes | no | +| Response on failure | 404 | 401 | + +An attacker attacks the weaker of the two. The timing channel is not practically +exploitable across a network against a 256-bit secret, so this is low severity β€” +but the missing minimum-length check means a short or placeholder secret would be +**accepted** by `indexer/run` while `reconciliation/run` correctly refuses to +expose itself at all. Not changed here: this runbook rotates secrets and does not +alter authentication code mid-rotation. Logged for a separate, reviewed change. diff --git a/docs/UPGRADE_AUTHORITY.md b/docs/UPGRADE_AUTHORITY.md new file mode 100644 index 0000000..33c6da3 --- /dev/null +++ b/docs/UPGRADE_AUTHORITY.md @@ -0,0 +1,118 @@ +# Upgrade, Admin and Pause Authority + +`upgrade` replaces the code that holds every escrow's custody. It is the most +consequential entry point in the contract, so its conditions are specified and +tested here rather than demonstrated once by hand. + +> **This document does not claim the mechanism is safe in the abstract.** An +> upgradeable contract is a trusted-admin contract. What follows is an exact +> statement of who is trusted, with what, and what that trust cannot be stopped +> from doing. + +--- + +## 1. Answers to the direct questions + +| Question | Answer | +|---|---| +| **Who can upgrade?** | Only the address stored as `Admin`, proven by `require_auth()`. | +| **Who can become admin?** | Only the address baked into the WASM at build time (`COREFLOW_ADMIN`). `init_admin` rejects any other with `AdminMismatch` (#20). | +| **What conditions are required?** | An admin signature **and** the contract already paused. Both, every time. | +| **Can an unauthorized actor upgrade?** | No. The call requires the admin's signature; a non-admin cannot produce it. | +| **Is pause mandatory?** | Yes. `upgrade` returns `NotPaused` (#22) otherwise, checked before anything is replaced. | +| **Is the upgrade observable?** | Yes. `admin/upgrade` carries the WASM hash, and the mandatory `admin/paused` precedes it β€” the full sequence is reconstructable from the event log. | +| **Can an admin brick the contract?** | Not by naming a wrong hash: the host refuses an unuploaded one and the transaction fails. An admin **can** upgrade to code that is itself broken β€” see Β§4. | +| **What happens to escrow state?** | Nothing. Custody, approvals, verified proofs, nonce watermarks, the admin and the oracle registry all survive. Verified in tests and on live Testnet. | +| **Can an admin-less contract be upgraded?** | No. With no `Admin` set there is no authority to satisfy, and `upgrade` returns `NotAdmin` (#10). Such a deployment is immutable. | + +--- + +## 2. The required sequence + +``` +admin signs ──▢ set_paused(true) ── emits admin/paused ──┐ + β”‚ observable gap +admin signs ──▢ upgrade(wasm_hash) ── emits admin/upgrade ─── + β”‚ +admin signs ──▢ set_paused(false) ── emits admin/paused β”€β”€β”˜ +``` + +The pause requirement does not stop a malicious admin β€” nothing at this layer +can. What it removes is the **silent** path: code holding custody cannot be +replaced in one transaction while the system still looks healthy. Monitoring has +a pause event to alert on, and the gap between pause and upgrade is visible to +anyone reading the ledger. + +**`cancel_escrow` stays callable while paused.** If an operator pauses and walks +away, managers can still recover their own custody. A pause window therefore +cannot trap funds. + +--- + +## 3. Tests + +`contracts/core-flow/src/test.rs`: + +| Test | Asserts | +|---|---| +| `test_upgrade_requires_admin_authorization` | Exactly one signer, and it is the admin | +| `test_upgrade_impossible_without_an_admin` | `NotAdmin` when no admin is configured | +| `test_upgrade_requires_pause_first` | `NotPaused` when live | +| `test_upgrade_pause_is_mandatory_and_checked_first` | Still `NotPaused` after a pause/unpause cycle β€” no latent permission | +| `test_upgrade_emits_an_event_carrying_the_wasm_hash` | Upgrade and the preceding pause are both observable | +| `test_upgrade_preserves_escrow_state_and_custody` | Custody, approvals, proofs, nonce, admin and registry survive; the escrow still settles afterwards | +| `test_pause_for_upgrade_does_not_trap_funds` | `cancel_escrow` refunds while paused | +| `test_upgrade_to_an_unknown_wasm_hash_is_refused` | **Ignored** β€” the host's refusal is a non-unwinding trap that `#[should_panic]` cannot catch natively. Verified on live Testnet instead (below). | + +Authorization is asserted via `env.auths()` rather than by calling unauthorized, +because a failed `require_auth` is a non-unwinding host trap in native +`cargo test`. Checking which signature the contract **demanded** proves the same +property and is catchable. + +--- + +## 4. Live Testnet verification + +Contract `CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4`. + +``` +# Real upgrade, performed in place (this is how per-payment events shipped) +upgrade without pausing β†’ Error(Contract, #22) NotPaused REFUSED +set_paused(true) β†’ upgrade β†’ set_paused(false) SUCCEEDED +get_admin / expected_admin / oracle registry ALL SURVIVED +escrow 2: 3 payments, manager_approved=true STATE INTACT +extend_escrow_ttl (new entry point) LIVE + +# Non-admin attempting an admin-only action +source=coreflow-v2-manager, set_paused(true) + β†’ "Missing signing key for account GAELEFW56FPE…" REFUSED + β†’ is_paused still false NO EFFECT + +# Admin naming a WASM hash that was never uploaded +upgrade(0xabab…ab) β†’ HostError: Error(Storage, MissingValue) REFUSED + β†’ get_admin intact, escrow 5: 3 payments intact NO DAMAGE +``` + +The non-admin refusal is worth reading carefully: the CLI reports a *missing +signing key for the admin's address*, because the contract demands that specific +signature. The manager is not refused for lacking a role β€” they are refused +because they cannot produce the key the contract requires. + +--- + +## 5. Residual risk, stated plainly + +| Risk | Status | +|---|---| +| A compromised admin key can upgrade to code that drains all custody | **Not mitigated.** This is inherent to an upgradeable contract. The pause requirement makes it observable, not impossible. | +| An admin can upgrade to WASM that is valid but broken | **Not mitigated** by the contract. Mitigated operationally: CI builds and tests the WASM, and the deploy script verifies the admin pin is physically present in the binary. | +| The admin key is a single point of failure | **Not mitigated.** It is one Stellar account. A multisig account or a threshold scheme would reduce this and is not implemented. | +| No timelock between announcing and performing an upgrade | **Not implemented.** Pause-then-upgrade forces two transactions but provides no waiting period for anyone to react. | + +Two-step admin handover (`propose_admin` / `accept_admin`) protects against +losing control to a mistyped address, but does nothing about a compromised one. + +**Before any Mainnet migration**, these paths β€” upgrade, admin, oracle registry, +settlement β€” warrant independent external review. Strong internal testing is not +equivalent to an audited financial system, and this document should not be read +as claiming otherwise. diff --git a/docs/evidence/REVIEWER_EVIDENCE.md b/docs/evidence/REVIEWER_EVIDENCE.md new file mode 100644 index 0000000..8ec75a7 --- /dev/null +++ b/docs/evidence/REVIEWER_EVIDENCE.md @@ -0,0 +1,556 @@ +# CoreFlow β€” Reviewer Evidence + +Everything below is reproducible from this repository. Where a claim is about +on-chain state, the verification command is given so it can be checked +independently rather than taken on trust. + +> **Scope note.** CoreFlow **v2** β€” the hardened contract described here β€” is +> deployed on **Stellar Testnet only**. CoreFlow **v1** remains on Mainnet and +> carries none of v2's hardening. See [`../DEPLOYMENTS.md`](../DEPLOYMENTS.md). + +--- + +## 1. Test suites + +| Suite | Command | Result | +|---|---|---| +| Soroban contract (Rust) | `cd contracts/core-flow && cargo test` | **70 passed**, 2 ignored | +| Application unit (TypeScript) | `npm run test:ci` | **761 passed**, 9 skipped (opt-in live) | +| Application integration (real PostgreSQL) | `npm run test:integration` | **71 passed** | +| Type check | `npm run typecheck` | clean | +| Production build | `npm run build` | succeeds | +| Deployable WASM | `cargo build --release --target wasm32v1-none` | 42,425 bytes | + +The single ignored Rust test is documented in place: `ed25519_verify` traps +non-catchably in native `cargo test`, so a bad-signature rejection cannot be +asserted with `#[should_panic]`. It is covered by the live Testnet run below. + +--- + +## 2. v2 Testnet deployment + +Machine-readable: [`testnet-v2-deployment.json`](testnet-v2-deployment.json) + +| Field | Value | +|---|---| +| Contract | `CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4` | +| Network | Stellar **Testnet** | +| WASM SHA-256 | `d9f2d849d69b56aebcbdf585e8b2e0e8d81d9e5bf13f51534f27bf479d0e56da` | +| Admin pinned in WASM | yes | +| Oracle key registered | yes | +| Explorer | https://stellar.expert/explorer/testnet/contract/CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 | + +Verify the deployed state yourself: + +```bash +stellar contract invoke --id CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 --network testnet -- expected_admin +stellar contract invoke --id CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 --network testnet -- get_admin +stellar contract invoke --id CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 --network testnet -- is_paused +stellar contract invoke --id CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4 --network testnet -- \ + is_oracle_key_registered --pubkey f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0 +``` + +--- + +## 3. Live golden-path validation + +Machine-readable: [`testnet-v2-golden-path.json`](testnet-v2-golden-path.json) +Reproduce: `ORACLE_SECRET_KEY= node scripts/validate-testnet-v2.mjs` + +Escrow **#2**, three contractors, **2,860 test USDC** settled by real SAC +transfers. Each assertion below was checked against on-chain state, not inferred +from a non-erroring CLI call: + +| # | Step | Verified | +|---|---|---| +| 1 | Balances recorded before settlement | manager 100,000 USDC; all workers 0 | +| 2 | Escrow created, custody funded | contract holds exactly 2,860; manager debited exactly 2,860 | +| 3 | Oracle attestation (`CFWP-v2`) | locally built preimage is **byte-identical** to the contract's `proof_preimage` for all 3 payments | +| 3 | Proofs submitted on-chain | 40 h, 32 h, 45 h accepted at nonces 0, 1, 2 | +| 4 | **Replay rejected** | resubmitting a consumed attestation fails (`InvalidNonce`) | +| 5 | **Dual approval enforced** | `pay_batch` refused with 0 approvals, and again with only the manager's | +| 5 | Distinct finance signer | finance approval signed by a **different key** than the manager | +| 6 | `pay_batch` executed | real SAC transfers | +| 7 | Settlement measured | worker payouts of exactly 1,000 / 960 / 900 USDC; custody drained to **0** | +| 8 | **Double settlement rejected** | second `pay_batch` fails (`PaymentAlreadyFinalized`) | +| 9 | Final state | both approvals true; every payment `proof_verified`; every payment `Finalized` | + +### Reconciliation reliability (P2 #4) + +**Verification is independent of the indexer.** The indexer trusts CoreFlow's own +`payment/paid` events; the reconciler reads the **token contract's own `transfer` +events** β€” `transfer / from / to / asset β†’ amount` β€” plus contract storage. A bug in +how CoreFlow emits or parses its events therefore cannot validate itself. Live +sample from the SAC's event stream: + +``` +transfer GCQR4PEWRAKH… β†’ CDN4FIKLJ72W… 28600000000 (custody funded) +transfer CDN4FIKLJ72W… β†’ GDHMEB2U2XQH… 10000000000 (worker 1) +transfer CDN4FIKLJ72W… β†’ GA7Q23T4I2CA… 9600000000 (worker 2) +transfer CDN4FIKLJ72W… β†’ GCFTJIXCBQR6… 9000000000 (worker 3) +``` + +| Claim | Evidence | +|---|---| +| Verification refuses contract state alone | `reconciler.test.ts` β†’ "refuses to confirm settlement the contract claims but no transfer supports" | +| CHAIN_AHEAD corrected only on observed transfer | "advances the projection when a transfer independently confirms settlement" | +| DATABASE_AHEAD is **never** reverted | "records a CRITICAL finding and leaves PAID in place" | +| No fabricated transaction hash | "does not fabricate a transaction hash" | +| CHAIN_UNREADABLE β‰  agreement, β‰  failure | 4 tests under "CHAIN_UNREADABLE is not agreement" | +| Batch totals cannot hide payment errors | "catches individual mismatches even when the batch total matches" (1000/960/900 recorded vs 1000/860/1000 settled β€” totals equal, two mismatches found) | +| A mismatched payment is not also counted agreed | same test: `agreed` is 1, not 3 | +| Recurring payroll is not a double payment | "does not flag a repeat of an identical pay period as a double payment" | +| Duplicate only within one transaction | "flags duplicate transfers as possible double payment" | +| Recorded hash with no transfer is caught | "flags a payment whose recorded hash has no corresponding transfer" | +| Findings deduplicate, acknowledgement survives | 3 tests under "finding deduplication" | +| CRITICAL reserved for false money statements | "reserves CRITICAL for false statements about money" | +| Every finding kind has remediation | "gives every finding kind actionable remediation" | +| Falsely-failed tx says DO NOT RETRY | "detects a false failure and warns against retrying" | +| Unattributed escrow reported, never attached | "reports unattributed escrows without attaching them to a tenant" | +| Cron trigger unreachable without the secret | `routes.test.ts` β€” 5 tests incl. short-secret refusal | +| Resolution requires an explanation | "refuses to resolve without a substantive explanation" | +| Resolution cannot alter financial state | "cannot alter payment state, amount or transaction hash" β€” asserts exactly which fields are written | +| Findings are tenant-scoped | "404s a finding belonging to another organization" | +| "No findings" never reads as healthy after a failed run | `ReconciliationPanel.test.tsx` β€” 4 summary tests | + +#### The run lock, verified against PostgreSQL + +``` +first RUNNING run INSERT 0 1 ALLOWED +second concurrent RUNNING run ReconciliationRun_one_running_per_org BLOCKED +after the first completes INSERT 0 1 ALLOWED +historical runs retained 2 runs PRESERVED +``` + +A check-then-insert is a race; the partial unique index means the lock does not +depend on the application noticing. + +#### Live Testnet validation + +`COREFLOW_LIVE_TESTNET=1 npx vitest run src/lib/reconciliation/__tests__/live-reconciliation.test.ts` + +``` +1. settled batch reconciles AGREED + RECONCILE: paymentsExamined=3 agreed=3 mismatched=0 databaseAhead=0 +2. repeated run is idempotent + correctionsApplied=0, findings unchanged, both runs COMPLETED +3. interrupted indexer recovered + RECOVERY: chainAhead=1 correctionsApplied=1 + β†’ payment returned to PAID from an observed SAC transfer, audit actorSystem=reconciler, + metadata.verifiedBy=sac-transfer-event +4. unattributed escrow reported, not attached + UNKNOWN_ON_CHAIN_OBJECT (LOW), 0 escrow rows created for it +5. synthetic DATABASE_AHEAD mismatch detected, NOT reverted + MISSING_ON_CHAIN β€” "Payment references escrow 7 slot 97, which does not exist on-chain." + β†’ payment stayed PAID; real payments unaffected +``` + +Case 5 used a synthetic payment inside a throwaway test organization, written +directly rather than through any settlement path and deleted afterwards. No fake +PAID state was created in a production path. + +#### A bug found by live validation + +The first live run reported three duplicate payments on a batch that agreed. Cause: +transfers were matched on `(escrow contract, recipient, asset, amount)`, which is +**not unique** β€” the same contract pays the same contractor the same rate every +period, so seven identical payroll runs produced seven matches for one payment. +Matching is now scoped to a single transaction, and the regression is pinned by +"does not flag a repeat of an identical pay period as a double payment". The +hermetic tests had passed because their fixture gave each transfer its own +transaction hash, which `pay_batch` never does. + +### Multi-tenancy (P2 #2) + +**The boundary is enforced by PostgreSQL, not only by application code.** Every +parent relation on a tenant-owned record is a composite foreign key on +`(orgId, id)`. Verified directly against the database: + +``` +org A payment β†’ org A batch INSERT 0 1 ALLOWED +org A payment β†’ org B batch Payment_orgId_batchId_fkey BLOCKED +org A payment β†’ org B project Payment_orgId_projectId_fkey BLOCKED +org A payment β†’ org B worker Payment_orgId_workerId_fkey BLOCKED +org B approval β†’ org A payment Approval_orgId_paymentId_fkey BLOCKED +org B audit β†’ org A payment AuditEvent_orgId_paymentId_fkey BLOCKED +org B escrow β†’ org A project Escrow_orgId_projectId_fkey BLOCKED +``` + +| Claim | Evidence | +|---|---| +| 6 roles Γ— 35 permissions, enumerated including empty cells | `tenancy/__tests__/rbac.test.ts` (81 tests); [`../RBAC.md`](../RBAC.md) | +| WORKER holds **no** permissions; all 35 refused | "grants WORKER nothing" | +| VIEWER refused every non-read permission | "VIEWER is read-only" | +| MANAGER cannot exercise the finance approval | "does not let a MANAGER exercise the finance rejection" | +| FINANCE cannot create the payroll it approves | "does not let FINANCE create the payroll it approves" | +| ADMIN cannot mint an OWNER | "does NOT let an ADMIN mint an OWNER" | +| MANAGER cannot mint a FINANCE approver | "does NOT let a MANAGER mint a FINANCE approver" | +| Cross-tenant reads return **404, not 403** | `tenancy/__tests__/isolation.test.ts` β€” 9 resource types | +| Foreign and nonexistent ids are byte-identical | "returns an identical response for foreign and nonexistent ids" | +| Id enumeration over 25 foreign payments leaks nothing | "leaks nothing when enumerating a range of ids" | +| Same on-chain id in two tenants does not cross over | "does not return another tenant's escrow for the same onChainId" | +| Suspended/invited/removed members are indistinguishable from non-members | "refuses a %s membership, indistinguishably from non-membership" | +| Invitation tokens stored hashed only | `membership.test.ts` β€” "are stored hashed, never in plaintext" | +| Invitation single-use under concurrency | "cannot mint two memberships when two requests race" | +| Every invitation rejection reads identically | "reports every rejection with an identical caller-visible message" | +| Invitation cannot change an existing member's role | "does not change the role of someone who already belongs" | +| Last administrator cannot be removed, suspended or demoted | "last administrator protection" (5 tests) | +| Self-promotion refused | "refuses changing your own membership" | +| Cross-tenant invitation revocation refused | `organizations/__tests__/invitations.route.test.ts` | + +### Indexer tenant mapping β€” verified on live Testnet + +Run: `COREFLOW_LIVE_TESTNET=1 npx vitest run src/lib/indexer/__tests__/live-tenancy.test.ts` + +``` +PASS 1 (no mapping): {"processed":13, "paymentsCreated":0, "unattributed":13} + β†’ 0 payments projected, 0 organizations invented, 13 events recorded +[indexer] replayed 13 previously unattributed event(s) (+3 payments, +3 settled) +PASS 2 (after org A claims the escrow): + org A idx=0 GDHMEB2U2X… amount=10000000000 state=PAID + org A idx=1 GA7Q23T4I2… amount=9600000000 state=PAID + org A idx=2 GCFTJIXCBQ… amount=9000000000 state=PAID +PASS 3: org B sees 0 payments; every org A payment id β†’ 404; on-chain id β†’ 404 +``` + +The indexer **never invents a tenant**. It previously auto-created one organization +per deployment and attached every discovered escrow to it β€” a guess that would place +one party's payroll inside another's workspace. Unattributable events are recorded +with `attributed = false` and replayed once an operator claims the escrow, so a +claim does not lose the history that predates it. + +### Payment state machine (P2 #1) + +| Claim | Evidence | +|---|---| +| 16 states, 38 transitions, each with a declared actor | `src/lib/payments/state-machine.ts`; [`../PAYMENT_STATE_MACHINE.md`](../PAYMENT_STATE_MACHINE.md) | +| Every **undeclared** (from, to) pair is rejected, for every actor kind | `state-machine.test.ts` β†’ "every undeclared pair is rejected" (>500 pairs asserted) | +| **Only a chain observer can reach `PAID`** β€” no user or system path | `state-machine.test.ts` β†’ "PAID is reachable only by the indexer" | +| A MANAGER cannot exercise the FINANCE decision | "does not let a MANAGER exercise the finance rejection" | +| One wallet cannot supply both halves of the dual-approval gate | `actions.test.ts` β†’ "refuses one wallet supplying both halves of the gate" | +| `SETTLEMENT_FAILED` cannot be retried by a user | "refuses retry after a settlement that DID reach the chain" (409 `RECONCILIATION_FIRST`) | +| Retried settlement does not double-pay | "replays the original attempt for a repeated key" | +| Cross-tenant access returns 404, not 403 | `actions.test.ts` β†’ "tenant isolation" (6 actions Γ— cross-tenant) | +| Racing transitions: exactly one wins | "lets only one of two racing transitions win" | +| Reconciliation records disagreements instead of overwriting | `reconcile.test.ts` (21 tests) | +| A false "failed" transaction is detected and flagged do-not-retry | "detects a false failure and warns against retrying" | +| No generic "Processing" anywhere in the UI | `PaymentStateBadge.test.tsx` β†’ all 16 states | +| Transaction links only where a transaction can exist | `PaymentTransactionRef` tests | + +### Multi-payment indexing β€” the defect this phase fixed + +**Before:** the `Escrow` model held one `workerPubKey` and one `amountBaseUnits`, so +a three-payee settlement collapsed into a single row carrying the FIRST payee's +figures. Two of the three payments did not exist in the product. + +**Root cause found during this work:** the contract's `payment/final` event emits +only `(escrow_id, total_amount, count)` β€” **no per-payment data**. The event stream +was insufficient to reconstruct per-payment state, so any projection would have had +to read `get_escrow` at index time, returning CURRENT state rather than state at +that ledger. That makes re-indexing non-deterministic. Per-payment events +(`payment/add`, `payment/paid`, `payment/cancel`) were added to the contract. No +change to authority or validation β€” the security model is identical. + +**Verified against live Testnet data** (escrow #3, `COREFLOW_LIVE_TESTNET=1`): + +``` +escrow 3: 3 payment(s) + idx=0 GDHMEB2U2X… amount=10000000000 hours=40 state=PAID + idx=1 GA7Q23T4I2… amount=9600000000 hours=32 state=PAID + idx=2 GCFTJIXCBQ… amount=9000000000 hours=45 state=PAID +``` + +Per-payment audit history, all attributed to the indexer (not a person): + +| Event | previous β†’ new | count | +|---|---|---| +| `payment.indexed` | β†’ `AWAITING_ORACLE` | 3 | +| `payment.oracle.verified` | `AWAITING_ORACLE` β†’ `ORACLE_VERIFIED` | 3 | +| `approval.manager.observed` | `AWAITING_MANAGER` β†’ `AWAITING_FINANCE` | 3 | +| `approval.finance.observed` | `AWAITING_FINANCE` β†’ `READY_TO_SETTLE` | 3 | +| `payment.state.changed` | `READY_TO_SETTLE` β†’ `PAID` | 3 | + +One batch, three payments, 17 chain events, **0 reconciliation findings**. + +### Restartability, demonstrated accidentally + +During this work an indexer run crashed partway through (a genuine bug: Prisma's +interactive transaction client has no `$transaction`, so nested transaction code +threw). The crashed run left a **committed prefix** β€” escrow and three payment rows +β€” with ChainEvent markers proving exactly which events had applied. The next run +skipped those, processed the remaining four, and completed. That is the +partial-ingestion property working on real data rather than in a test. + +### Pause-gated contract upgrade + +The per-payment events were deployed by **upgrading the existing v2 contract in +place**, which preserved its address and all existing escrows: + +``` +upgrade without pausing first β†’ Error(Contract, #22) NotPaused (refused) +set_paused(true) β†’ upgrade β†’ set_paused(false) (succeeded) +get_admin, expected_admin, oracle registry (all survived) +escrow 2: 3 payments, manager_approved=true (state intact) +extend_escrow_ttl (new entry point live) +``` + +### Indexing (chain β†’ database) + +Verified against a live Postgres with the real indexer +(`src/lib/indexer/__tests__/live-testnet.test.ts`, opt-in via +`COREFLOW_LIVE_TESTNET=1`): + +- 14 contract events read from Testnet RPC and projected +- escrow rows written with `status = paid` +- amounts stored as **base units** with `assetDecimals = 7` (not cents) +- re-running the indexer adds no duplicate `ChainEvent` rows (idempotent by RPC + paging token) + +**Caveat, stated plainly:** the current off-chain `Escrow` model carries a single +worker and amount, so a 3-payee batch is projected as one row holding the first +payment's figures. The on-chain settlement is complete and correct; the database +view of it is lossy. See Known limitations. + +### Live front-running test (F-9) + +A second contract was deployed from the same pinned WASM and an attacker +identity attempted to claim admin: + +``` +victim contract : CCXRQYROEXDTTS77HNEZNLQLNSU35W6BKUPMN27HCV4JLB3VLHXDQQHT +attacker : GCQR4PEWRAKH4IB4NUDU77WOU326UUQORNVBIHOZ5XD3ZK2FN5SJKSSY +result : Error(Contract, #20) AdminMismatch +get_admin after : null +``` + +The attacker won the race and gained nothing. + +--- + +### Bulk Pay API layer (P2 #5, in progress) + +See [`../BULK_PAY.md`](../BULK_PAY.md). **Database-backed validation is BLOCKED** β€” the +local development database is unavailable, so migration 8 is unapplied and no query +below has run against real PostgreSQL. + +| Property | Evidence | +|---|---| +| One Payment per CSV row, never an aggregate | `payroll/__tests__/batches.test.ts`; `payroll/__tests__/csv.test.ts` (102 tests) | +| Exact decimals; scientific notation, excess precision and fractional hours refused | `csv.test.ts` β€” 74 tests incl. `0.0000001` preserved as `1n` | +| Spreadsheet formula injection neutralized at the storage boundary | `csv.test.ts` "hostile input"; asserted on the stored `sourceReference` | +| Unknown request fields rejected, not ignored | `batches.route.test.ts` β€” `{state:'PAID', role:'FINANCE'}` β†’ 400 `UNKNOWN_FIELD` | +| Approver role derived from membership, never the body | `batches.route.test.ts` β€” `{"role":"FINANCE"}` refused at the schema | +| Separation of duties holds for roles with BOTH permissions | `batches.route.test.ts` β€” ADMIN approving twice records 3, then 0 | +| Cross-tenant batch returns a byte-identical 404 to a non-existent one | `batches.route.test.ts` β€” responses compared with `toEqual` | +| Idempotent creation under double-click, retry and N concurrent requests | `batches.route.test.ts` β€” 1 batch, 3 payments, not 9 | +| Same key + different payload refused rather than replayed | `batches.route.test.ts` β†’ 409 `IDEMPOTENCY_KEY_REUSED` | +| Lost-race path relies on the unique index, not the pre-check | `batches.test.ts` β€” pre-check blinded so only the index can stop the write | +| Batch creation is atomic | `batches.test.ts` β€” injected failure leaves no batch, payment or audit event | +| Approval changes no state, hash, amount or recipient | `batches.route.test.ts` β€” before/after snapshot equality | +| 500 leaks no model name, `prisma`, or stack frames | `batches.route.test.ts` "error sanitization" | + +#### Two real bugs found while building this + +**`approval.create` omitted `orgId`.** `Approval`'s parent relation is a composite +foreign key on `(orgId, paymentId)`, so `orgId` is a required scalar β€” the generated +`ApprovalUncheckedCreateInput` lists it without `?`. The call passed only `paymentId`, +and would have failed against real PostgreSQL with *Argument `orgId` is missing* on the +**dual-approval path**. It survived because `db` is typed `any` and the in-memory +double did not enforce required columns. + +Fixed in `payments/actions.ts`, and the double now enforces required columns for all +twelve tenant-scoped tables. Switching that on immediately caught a **second** instance +in `rejectPayment`'s `approval.upsert`, which a `.create`-only static audit had missed. + +**`any * any` is typed `number` by TypeScript.** The draft re-validation read payments +as `any`, so `p.hours * p.rateBaseUnits` β€” the exactness check itself β€” would have been +evaluated as floating-point arithmetic. Caught by `tsc` only once the operands were +given explicit `bigint` types, which is why `RevalidationPayment` is declared rather +than inferred. + +#### A fake-db defect that would have weakened a test + +The in-memory double's `$transaction` snapshotted **every** table and restored the whole +snapshot on failure. Under concurrency that is wrong in the worst direction: when two +transactions interleave at an `await` and the second fails, restoring its snapshot also +discards the **first** one's committed writes. + +The concurrent-idempotency test therefore saw one batch with **one** payment instead of +one batch with **three** β€” a result that invites weakening the assertion. Real Postgres +isolates transactions per connection, so the double now records a per-transaction undo +log and replays only its own writes. + +### Database validation gate (real PostgreSQL) + +`npm run test:integration` β€” 71 tests against PostgreSQL 18.6 on a private local +cluster. A separate vitest config from the unit suite, so the two totals can never be +conflated. Setup: [`../ENVIRONMENTS.md`](../ENVIRONMENTS.md#setting-up-the-development-database). + +| Property | Evidence | +|---|---| +| 10 migrations apply from zero | `prisma migrate deploy` on an empty database | +| Schema matches the Prisma model | `prisma migrate diff --exit-code` β†’ 0 (no drift) | +| 14 composite tenant FKs exist | enumerated from `information_schema` | +| Cross-tenant payment β†’ batch rejected | `constraints.integration.test.ts` β†’ P2003 | +| Cross-tenant approval, project, worker, audit event rejected | 4 further P2003 tests | +| Same wallet may be a worker in two orgs | uniqueness is per tenant, not global | +| `(orgId, idempotencyKey)` unique; NULLs distinct | 3 tests | +| One payment per on-chain slot | `(escrowId, onChainPaymentIndex)` β†’ P2002 | +| One RUNNING reconciliation run per org | partial unique index, incl. release-and-restart | +| Money exact through the column | `250.50`, `1000`, `1n`, int8 max: client value, re-read, and `::text` from SQL all agree | +| int8 overflow refused, not wrapped | max + 1 rejected | +| Organization delete cascades; other tenant untouched | cascade test | +| Rollback leaves zero partial records | real transaction, failure injected on row 3 | +| One failing transaction cannot undo another | the defect the in-memory double had | +| 3 concurrent identical creates β†’ 1 batch, 3 payments | 1 response `created:true`, 2 `created:false` | +| Same key + different payload β†’ 409 | `IDEMPOTENCY_KEY_REUSED` | +| 3 concurrent unkeyed creates β†’ 3 distinct references | reference-collision retry under contention | +| No user actor of any role can persist PAID | OWNER, ADMIN, MANAGER, FINANCE each refused | +| PAID never moves backwards | 6 destinations Γ— 3 actor kinds, all refused | +| Audit trail is continuous | each row's `previousState` equals the prior row's `newState` | +| Tenant isolation through the API | cross-tenant read/approve/re-validate β†’ byte-identical 404 | + +#### Two more real defects, found only by real PostgreSQL + +**A migration that could never have applied.** `20260911020000_reconciliation_reliability` +added six values to the existing `FindingKind` enum and then used them in `UPDATE` +statements in the same file. PostgreSQL refuses that: + +``` +ERROR: unsafe use of new value "ASSET_MISMATCH" of enum type "FindingKind" +HINT: New enum values must be committed before they can be used. (55P04) +``` + +`prisma migrate deploy` wraps each migration in one transaction, so add-and-use in a +single file can never work β€” regardless of PostgreSQL version, and despite the +generated comment in that file claiming it is only a PG-11-and-earlier concern. The +earlier claim that "7 migrations apply from zero" was **wrong**: this migration had +been applied by hand and then marked applied with `migrate resolve`, so the from-zero +path had never actually been exercised. The ADD VALUE statements are now their own +migration, `20260911015000_finding_kind_values`, and the full history applies from +zero. + +**`onDelete: SetNull` on a composite FK whose `orgId` is NOT NULL.** Twelve relations +declared it. SET NULL nulls **every** column of the foreign key, so deleting an +Escrow, Project or Worker that any row referenced failed with: + +``` +Null constraint violation on the fields: (`orgId`) +``` + +Deleting an escrow was therefore impossible. `prisma validate` had been emitting a +warning about exactly this, which had been noted as pre-existing and not +investigated β€” the integration test is what forced it. Changed to `NoAction` in +`20260911044540_composite_fk_no_action`: NO ACTION is checked at the end of the +statement, so a cascading delete from Organization still succeeds, while a direct +delete is refused while dependent rows exist. That refusal is the correct behaviour +for financial data β€” detaching a payment from its escrow destroys the record of what +the money was for. + +#### Three tests that were wrong, and were corrected rather than deleted + +Recorded because each looked like a product bug and was not: + +- A test asserted the planner would choose an index over a sequential scan on 200 + rows. Postgres is right to prefer a seq scan at that size. Rewritten to prove a + usable index **exists** (`SET LOCAL enable_seqscan = off`) β€” which also exposed + that `SET LOCAL` outside a transaction is silently discarded. +- A concurrency test raced `READY_TO_SETTLE β†’ SUBMITTING` against + `READY_TO_SETTLE β†’ PAID` and expected one winner. Both succeeded, correctly: the + table allows `SUBMITTING β†’ PAID`, because a confirmation can arrive before our own + update lands. Re-aimed at a genuinely incompatible pair. +- A concurrency assertion listed only two of the three legitimate refusal codes and + failed about **half the time**. The missing one was `TERMINAL`: when the CANCELLED + side won, the other transition was refused because nothing leaves a terminal state. + Found by running the suite ten times rather than accepting eight green runs. The + flake was the test's, not the product's β€” and a flaky financial test is exactly the + kind that gets silenced instead of understood. +- A test expected `Argument \`orgId\` is missing`. Prisma reports the missing + **relation**: `Argument \`org\` is missing`. Worth recording, since grepping logs + for the column name would never surface that failure. + +### Live Testnet funding validation β€” ATTEMPTED, BLOCKED + +**No transaction was submitted. No funds moved.** Recorded in +[`testnet-v2-live-funding.json`](testnet-v2-live-funding.json). + +This is a live-infrastructure attempt, separate from the unit and integration suites +and separate from the historical v1 Mainnet activity. It is **not** evidence of +adoption or of anything settling. + +The run stopped at transaction **simulation** with `Error(Contract, #16)` β€” +`OracleKeyNotRegistered`. The v2 contract refused to create the escrow because the +oracle public key supplied is not in its admin-managed registry. Because simulation +failed, nothing reached the network. + +| Read-only check | Result | +|---|---| +| `is_oracle_key_registered(3b9d395a…)` β€” the key in the local environment | **false** | +| `is_oracle_key_registered(f42a4883…)` β€” the key recorded at deployment | **true** | + +The contract trusts the key from deployment time and does not trust the one now in +the local environment β€” consistent with the oracle secret having been rotated +locally without the new public key being registered on-chain. + +**No key was registered or revoked.** Which key is the post-rotation one is a fact +only the operator holds, and registering the wrong one would re-authorize a +credential that may be compromised β€” exactly what the admin registry exists to +prevent. This is therefore an operator action, and the πŸ”΄ outstanding secret rotation +is now on the critical path rather than deferred. + +What the run did establish before stopping: + +| Stage | Result | +|---|---| +| Environment preflight | local PostgreSQL + CoreFlow v2 Testnet | +| Payroll from a real CSV through the real parser and batch service | 1 batch, 3 payments | +| Exact base units persisted | 10000000 + 15000000 + 5000000 = 30000000 (3.00 test USDC) | +| Dual approval as real `Approval` rows, two distinct wallets | 6 rows | +| Funding intent opened, plan frozen, digest verified against its content | βœ… | +| Manager Testnet balance | 77,120 test USDC β€” funds were not the blocker | + +The contract refusing an unregistered oracle is the security control working. A +manager cannot install their own oracle, which is the defect this phase's +attestation registry was built to close β€” and it held against a real transaction. + +## 4. Instawards SOW deliverables + +| # | Deliverable | Implementation | Test | Live evidence | +|---|---|---|---|---| +| 1 | Token transfer integration | `pay_batch` / `initialize_multi_sig_escrow` (`contracts/core-flow/src/lib.rs`) | `test_pay_batch_settles_two_assets_in_one_call`, `test_custody_sum_invariant_fuzz` | Golden path Β§3 step 6–7 | +| 2 | Ed25519 oracle verification | `build_proof_message`, `verify_oracle_work`, `proof_preimage`; `src/lib/oracle/index.ts`; `scripts/oracle-cli.mjs` | `test_proof_preimage_matches_cross_language_vector`, `test_contract_preimage_matches_independent_implementation`, 16 TS oracle tests | Golden path Β§3 step 3–4 | +| 3 | Bulk Pay + Freighter dual approval | `src/app/bulk-pay/page.tsx`, `manager_approve` / `finance_approve` | `test_finalize_without_finance_approval_fails`, `test_escrow_rejects_identical_manager_and_finance` | Golden path Β§3 step 5 | +| 4 | Testing, docs, validation evidence | 62 Rust + 135 TS | this document | `testnet-v2-golden-path.json` | + +--- + +## 5. Cross-language proof vector + +[`proof-vector-v2.json`](proof-vector-v2.json) pins the 198-byte `CFWP-v2` +preimage across three independent implementations: + +1. **Rust (contract)** β€” `test_proof_preimage_matches_cross_language_vector` +2. **TypeScript (server signer)** β€” `builds the exact preimage pinned by the cross-language vector` +3. **Contract vs. independent Rust** β€” `test_contract_preimage_matches_independent_implementation` + +The Rust test's builder is deliberately a *second* implementation rather than a +call into the contract's. Sharing the builder would prove only that one function +agrees with itself, and a field silently dropped from the preimage would pass. + +--- + +## 6. Known limitations + +Stated plainly rather than omitted. + +| Limitation | Detail | +|---|---| +| **Whole hours only** | The contract enforces `hours Γ— rate == amount` with integer hours. `,001` at `5/h` is 40.04 h and is **rejected at creation**. Fractional-hour payroll requires a versioned scaled-hours schema (v3). Hours and amounts are never silently rounded. | +| **v2 is Testnet only** | Mainnet runs v1, which has none of v2's hardening. | +| **Storage archival** | Persistent entries that run out of rent are archived to the Expired State Stack and require a `RestoreFootprint` operation before the escrow can be used again. This is recoverable, not fund loss. `extend_escrow_ttl` is permissionless so anyone can keep an escrow alive. | +| **Rate limiter is per-instance** | In-memory; on a multi-instance deployment effective limits scale with instance count. Needs a shared store for production. | +| **`upgrade` remains a centralization risk** | Requiring a pause first makes it observable and deliberate; it does not constrain a malicious admin. | +| **Test USDC** | The Testnet settlement asset is a locally issued `USDC`, not Circle USDC. | +| ~~Indexer projects one payee per escrow~~ | **FIXED in P2 #1.** One `Payment` row per on-chain payment slot, verified against live Testnet data above. | +| **Batch-level approval granularity** | Manager and finance approval are per-ESCROW on-chain, so approving advances every payment in that escrow. Per-payment approval would need a contract change and is not claimed. | +| **`PAID` is terminal** | When reconciliation finds a payment recorded as `PAID` that the chain disputes, it opens a finding but cannot move the payment, because no transition leaves `PAID`. The finding is the durable record; the state machine was not weakened to allow an exit. | diff --git a/docs/evidence/proof-vector-v2.json b/docs/evidence/proof-vector-v2.json new file mode 100644 index 0000000..2e3db21 --- /dev/null +++ b/docs/evidence/proof-vector-v2.json @@ -0,0 +1,18 @@ +{ + "schema": "CFWP-v2", + "escrowId": 1, + "networkPassphrase": "Test SDF Network ; September 2015", + "contractId": "CCQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2CNSG", + "oraclePublicKey": "79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664", + "signatures": [ + { + "paymentId": 0, + "worker": "GB43KVROR7TFJ6KAPCYRF2FJROTZAH4FHLTJLPWX4DRZCC5NASLGITR6", + "hours": 40, + "nonce": 0, + "messageSha256": "0534a51a5c8811b88f69d500bb7bb5c3e188f6ece125bbac489f756d68155a11", + "message": "434657500002cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd4725b0c63242683ea58b14aff3c6a455fa6dbf3573ddedc1e4fa218e0406711ba422cbbd006041eea71603dacf22e8af1a8cbf2f3b0083caa8b8bf333ab565ce2e0511957404a7b60b722a939858e47fa9205c5f7c71de7941f15d2f489c8c53ceb0000000100000000000000000000000000000000000027100000000000000000000000000000002800000000000003e800000000000007d00000000000000000", + "signature": "TdKHLe52uA6PSuj70Lkkkopd5gTgmzLic2ZT3HmOZ6e4U6DoKanImbyjUT40bA8uRxzWhbFo8Alfl2HYfKs+Cg==" + } + ] +} diff --git a/docs/evidence/testnet-v2-deployment.json b/docs/evidence/testnet-v2-deployment.json new file mode 100644 index 0000000..54385e5 --- /dev/null +++ b/docs/evidence/testnet-v2-deployment.json @@ -0,0 +1,25 @@ +{ + "version": "v2", + "network": "testnet", + "networkPassphrase": "Test SDF Network ; September 2015", + "contractId": "CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4", + "wasmSha256": "d9f2d849d69b56aebcbdf585e8b2e0e8d81d9e5bf13f51534f27bf479d0e56da", + "wasmBytes": 42425, + "proofSchema": "CFWP-v2", + "adminAddress": "GAELEFW56FPEVOO57SJATCGEHX4ROQHULSUHEFMMPLEMACTA5A7PO2J2", + "adminPinnedInWasm": true, + "oraclePublicKey": "f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0", + "oracleKeyRegistered": true, + "paused": false, + "deployedAt": "2026-09-09T21:51:06Z", + "explorer": "https://stellar.expert/explorer/testnet/contract/CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4", + "note": "CoreFlow v2, Testnet only. v1 remains deployed separately on Mainnet and is not affected by this deployment.", + "settlementAsset": { + "code": "USDC", + "issuer": "GBGO2HQVLFR3G5MYUJQMBZAQQRYU2MITGICHPJXH2MDGWT3BBA6GDQUL", + "sacContractId": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "decimals": 7, + "note": "Test USDC issued on Testnet for validation. Not Circle USDC." + }, + "explorerAsset": "https://stellar.expert/explorer/testnet/contract/CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M" +} diff --git a/docs/evidence/testnet-v2-golden-path.json b/docs/evidence/testnet-v2-golden-path.json new file mode 100644 index 0000000..6ec7624 --- /dev/null +++ b/docs/evidence/testnet-v2-golden-path.json @@ -0,0 +1,153 @@ +{ + "version": "v2", + "network": "testnet", + "contractId": "CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4", + "steps": [ + { + "name": "balances_before", + "at": "2026-09-10T19:58:38.331Z", + "manager": "799800000000", + "workers": [ + "70000000000", + "67200000000", + "63000000000" + ] + }, + { + "name": "escrow_created", + "at": "2026-09-10T19:58:51.520Z", + "escrowId": 8, + "custodyBaseUnits": "28600000000" + }, + { + "name": "oracle_attestations", + "at": "2026-09-10T19:59:19.235Z", + "schema": "CFWP-v2", + "attestations": [ + { + "paymentId": 0, + "hours": "40", + "nonce": "0", + "preimageSha256": "d496f441702fc3b0c37928a039f8d43395a53ac884c2ab7f68ec4e7c774672f5", + "signature": "5fRedsKW5IPJq9Xtz4XV1Y30yQZGDTH0zLQCMMUAR/DXTdyfPyehcGLK8ypKqJoC/Ro3sD3i0OPVT8ZxSN5jAw==" + }, + { + "paymentId": 1, + "hours": "32", + "nonce": "1", + "preimageSha256": "ab18ef15eb13c82b745e0266730d5854b754b178cae492e7cc22cea86d1c3940", + "signature": "WIsjqLx6coqhddyJ2uh26WYt+BXokIjgEoqQXhhoBMJYyeNgq8wfjLbjBggC1jIo6r79aAgj4yfObmX7NUEFCA==" + }, + { + "paymentId": 2, + "hours": "45", + "nonce": "2", + "preimageSha256": "a3509223a4d036851e7afce73d45e3d41747605060b6bf42768973abd8321978", + "signature": "Maj7+i/L14DJbRk79iozY5ENCIez55OAc8b2UIOUGqmjBPEVDJc5c08PpYj89VX4qwyiYVA/HGEYJpEWKPMXBw==" + } + ] + }, + { + "name": "replay_rejected", + "at": "2026-09-10T19:59:20.332Z", + "paymentId": 0, + "nonce": "0", + "rejected": true + }, + { + "name": "dual_approval", + "at": "2026-09-10T19:59:45.473Z", + "blockedNoApprovals": true, + "blockedOneApproval": true, + "manager": "GCQR4PEWRAKH4IB4NUDU77WOU326UUQORNVBIHOZ5XD3ZK2FN5SJKSSY", + "finance": "GAW6IYXANCROLFIO6TAN5SZJBUTBGTLRYH2GEZUBGRC3HHPAJ5O3GMAI" + }, + { + "name": "settlement", + "at": "2026-09-10T19:59:58.350Z", + "custodyAfter": "0", + "paid": [ + { + "paymentId": 0, + "worker": "GDHMEB2U2XQHSEB5JFYOOZDH7FHB7SUPCYLXFWVP5U5IRKKJKBCBRBQQ", + "receivedBaseUnits": "10000000000" + }, + { + "paymentId": 1, + "worker": "GA7Q23T4I2CAQXCMVN3EQ7VSI7ZVGIGDDKBZQU65MUK66YL7OPPG5YMH", + "receivedBaseUnits": "9600000000" + }, + { + "paymentId": 2, + "worker": "GCFTJIXCBQR6LVSD5EL27JPIN24OJSNJRQDCZCDH6EDZHW3GHTMHGN67", + "receivedBaseUnits": "9000000000" + } + ] + }, + { + "name": "double_settlement_rejected", + "at": "2026-09-10T19:59:59.105Z", + "rejected": true + }, + { + "name": "final_state", + "at": "2026-09-10T19:59:59.992Z", + "escrowId": 8, + "escrow": { + "cancelled": false, + "finance_approved": true, + "finance_approver": "GAW6IYXANCROLFIO6TAN5SZJBUTBGTLRYH2GEZUBGRC3HHPAJ5O3GMAI", + "manager": "GCQR4PEWRAKH4IB4NUDU77WOU326UUQORNVBIHOZ5XD3ZK2FN5SJKSSY", + "manager_approved": true, + "oracle_pubkey": "f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0", + "oracle_rotations": 0, + "payments": [ + { + "amount": "10000000000", + "end_date": 1789070312, + "hours_logged": "40", + "id": 1, + "proof_verified": true, + "rate_per_hour": "250000000", + "start_date": 1787860712, + "status": 3, + "token": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "worker": "GDHMEB2U2XQHSEB5JFYOOZDH7FHB7SUPCYLXFWVP5U5IRKKJKBCBRBQQ" + }, + { + "amount": "9600000000", + "end_date": 1789070312, + "hours_logged": "32", + "id": 2, + "proof_verified": true, + "rate_per_hour": "300000000", + "start_date": 1787860712, + "status": 3, + "token": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "worker": "GA7Q23T4I2CAQXCMVN3EQ7VSI7ZVGIGDDKBZQU65MUK66YL7OPPG5YMH" + }, + { + "amount": "9000000000", + "end_date": 1789070312, + "hours_logged": "45", + "id": 3, + "proof_verified": true, + "rate_per_hour": "200000000", + "start_date": 1787860712, + "status": 3, + "token": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "worker": "GCFTJIXCBQR6LVSD5EL27JPIN24OJSNJRQDCZCDH6EDZHW3GHTMHGN67" + } + ] + } + } + ], + "escrowId": 8, + "assetContract": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "totalSettledBaseUnits": "28600000000", + "completedAt": "2026-09-10T19:59:59.992Z", + "explorer": { + "contract": "https://stellar.expert/explorer/testnet/contract/CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4", + "asset": "https://stellar.expert/explorer/testnet/contract/CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M" + } +} \ No newline at end of file diff --git a/docs/evidence/testnet-v2-live-funding.json b/docs/evidence/testnet-v2-live-funding.json new file mode 100644 index 0000000..d9c5ca4 --- /dev/null +++ b/docs/evidence/testnet-v2-live-funding.json @@ -0,0 +1,53 @@ +{ + "title": "Live Testnet funding validation \u2014 ATTEMPTED, BLOCKED", + "version": "v2", + "network": "testnet", + "attemptedAt": "2026-09-11", + "outcome": "BLOCKED_BEFORE_SUBMISSION", + "transactionSubmitted": false, + "fundsMoved": false, + "blocker": { + "contractError": "Error(Contract, #16)", + "name": "OracleKeyNotRegistered", + "stage": "transaction simulation", + "meaning": "The v2 contract refused to create the escrow because the oracle public key supplied is not in its admin-managed registry. Simulation failed, so nothing was submitted and no custody moved.", + "oracleKeyInLocalEnv": "3b9d395a725ba0be4c476a0504540fb8dd70a278fac140a20e0d74cd7f41ae44", + "oracleKeyInLocalEnvRegistered": false, + "oracleKeyRegisteredOnContract": "f42a48839e48d58e6628f5d096ee859e635b056be580bdcc68d620e2a2badae0", + "oracleKeyRegisteredOnContractIsFromDeployment": true + }, + "interpretation": [ + "The contract trusts the oracle key recorded at deployment time, and does not trust the key now in the local environment.", + "This is consistent with the oracle secret having been rotated locally without the new public key being registered on the contract.", + "Registering a key is an admin action, and which key is the post-rotation one is a fact only the operator holds. Registering the wrong one would re-authorize a credential that may be compromised, which is precisely what the admin registry exists to prevent. No key was registered or revoked." + ], + "verifiedReadOnly": [ + { + "call": "is_oracle_key_registered(3b9d395a...)", + "result": false + }, + { + "call": "is_oracle_key_registered(f42a4883...)", + "result": true + } + ], + "whatDidWork": [ + "Environment preflight: local PostgreSQL + CoreFlow v2 Testnet", + "Organization, members, project and a 3-payment payroll created from a real CSV through the real parser and batch service", + "Exact base units persisted: 10000000 + 15000000 + 5000000 = 30000000 (3.00 test USDC)", + "Both halves of the approval gate recorded as real Approval rows by two distinct wallets", + "Funding intent opened; plan frozen and its SHA-256 digest verified to match its content", + "Manager Testnet balance confirmed sufficient: 77120 test USDC" + ], + "prerequisiteForRetry": [ + "Operator confirms which oracle keypair is current (post-rotation).", + "Admin calls register_oracle_key() on CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4.", + "Admin calls revoke_oracle_key(f42a4883...) if that key is the exposed one.", + "Re-run: COREFLOW_LIVE_TESTNET=1 npx vitest run src/lib/funding/__tests__/live-funding.test.ts" + ], + "contract": "CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4", + "settlementAsset": "CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M", + "explorer": { + "contract": "https://stellar.expert/explorer/testnet/contract/CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4" + } +} diff --git a/package.json b/package.json index 2cc4bb7..a785b1c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "private": true, "scripts": { + "predev": "npm run check:env", "dev": "next dev --experimental-https --experimental-https-key ./certs/localhost-key.pem --experimental-https-cert ./certs/localhost.pem", + "predev:http": "npm run check:env", "dev:http": "next dev", "build": "export DIRECT_URL=\"${DIRECT_URL:-$DATABASE_URL}\" && prisma generate && next build", "vercel-build": "export DIRECT_URL=\"${DIRECT_URL:-$DATABASE_URL}\" && prisma generate && next build", @@ -16,12 +18,20 @@ "typecheck": "tsc --noEmit", "e2e": "playwright test", "postinstall": "export DIRECT_URL=\"${DIRECT_URL:-$DATABASE_URL}\" && prisma generate", + "predb:migrate": "npm run check:env", "db:migrate": "prisma migrate dev", + "predb:deploy": "npm run check:env", "db:deploy": "prisma migrate deploy", "db:studio": "prisma studio", "contract:build": "cd contracts/core-flow && cargo build --target wasm32v1-none --release", "contract:test": "cd contracts/core-flow && cargo test", - "db:seed": "prisma db seed" + "predb:seed": "npm run check:env", + "db:seed": "prisma db seed", + "check:env": "node scripts/check-env.mjs", + "pretest:integration": "npm run check:env", + "test:integration": "vitest run --config vitest.integration.config.ts", + "predb:reset": "npm run check:env", + "db:reset": "prisma migrate reset --force --skip-seed" }, "dependencies": { "@prisma/client": "^5.22.0", @@ -62,4 +72,4 @@ "typescript": "^5.2.0", "vitest": "^4.1.8" } -} \ No newline at end of file +} diff --git a/prisma/migrations/20260910000000_money_base_units/migration.sql b/prisma/migrations/20260910000000_money_base_units/migration.sql new file mode 100644 index 0000000..671499c --- /dev/null +++ b/prisma/migrations/20260910000000_money_base_units/migration.sql @@ -0,0 +1,27 @@ +-- Money is stored in the settlement asset's BASE UNITS, not cents. +-- +-- WHY: the dashboard collected dollars, multiplied by 100, and passed the +-- result to the contract as the on-chain amount. Stellar assets carry SEVEN +-- decimals, so every escrow funded 100,000x less than the UI displayed. The +-- column was also INTEGER, which overflows at $21,474,836.47. +-- +-- CONVERSION: existing rows hold cents. One cent is 10^5 base units on a +-- 7-decimal asset (10^7 / 10^2), so the historical values are scaled up rather +-- than dropped. Those rows predate any real settlement at this scale; the +-- multiply keeps the DISPLAYED figure stable, which is what they recorded. + +ALTER TABLE "Escrow" ADD COLUMN "financeApprover" TEXT; +ALTER TABLE "Escrow" ADD COLUMN "assetDecimals" INTEGER NOT NULL DEFAULT 7; + +ALTER TABLE "Escrow" ADD COLUMN "amountBaseUnits" BIGINT; +ALTER TABLE "Escrow" ADD COLUMN "rateBaseUnits" BIGINT; + +UPDATE "Escrow" SET + "amountBaseUnits" = "amountCents"::BIGINT * 100000, + "rateBaseUnits" = "rateCents"::BIGINT * 100000; + +ALTER TABLE "Escrow" ALTER COLUMN "amountBaseUnits" SET NOT NULL; +ALTER TABLE "Escrow" ALTER COLUMN "rateBaseUnits" SET NOT NULL; + +ALTER TABLE "Escrow" DROP COLUMN "amountCents"; +ALTER TABLE "Escrow" DROP COLUMN "rateCents"; diff --git a/prisma/migrations/20260910120000_payment_state_machine/migration.sql b/prisma/migrations/20260910120000_payment_state_machine/migration.sql new file mode 100644 index 0000000..fc02363 --- /dev/null +++ b/prisma/migrations/20260910120000_payment_state_machine/migration.sql @@ -0,0 +1,464 @@ +-- CoreFlow payment state machine (P2 #1). +-- +-- Introduces the payment domain model: Organization / Project / Worker / +-- PayrollBatch / Payment / Approval / BlockchainTransaction / AuditEvent / +-- ReconciliationFinding, and makes Payment the atomic financial record. +-- +-- ── Why this migration is hand-ordered ────────────────────────────────────── +-- The generated diff adds NOT NULL columns to `Escrow` with no default and +-- retypes `OracleAttestation.paymentId` from the payment INDEX to a Payment FK. +-- Applied verbatim, both destroy existing rows: the first fails outright on a +-- non-empty table, the second silently loses the index it used to hold. +-- +-- So the order here is: create the new world, BACKFILL from the old one, and +-- only then tighten constraints and drop columns. The prior schema's single +-- worker/amount per Escrow is expanded into one Payment row, which is the +-- defect this phase exists to fix. + +-- ══ 1. Enums ════════════════════════════════════════════════════════════════ +CREATE TYPE "OrgRole" AS ENUM ('OWNER', 'ADMIN', 'MANAGER', 'FINANCE', 'WORKER', 'VIEWER'); +CREATE TYPE "PaymentState" AS ENUM ('DRAFT', 'VALIDATING', 'AWAITING_ORACLE', 'ORACLE_VERIFIED', 'AWAITING_MANAGER', 'AWAITING_FINANCE', 'READY_TO_SETTLE', 'SUBMITTING', 'CONFIRMING', 'PAID', 'REJECTED', 'CANCELLED', 'EXPIRED', 'SUBMISSION_FAILED', 'SETTLEMENT_FAILED', 'RECONCILIATION_REQUIRED'); +CREATE TYPE "ApprovalDecision" AS ENUM ('APPROVED', 'REJECTED'); +CREATE TYPE "TxKind" AS ENUM ('INITIALIZE_ESCROW', 'SUBMIT_HOURS_PROOF', 'MANAGER_APPROVE', 'FINANCE_APPROVE', 'PAY_BATCH', 'CANCEL_ESCROW', 'ROTATE_ORACLE_KEY', 'EXTEND_ESCROW_TTL'); +CREATE TYPE "TxStatus" AS ENUM ('PREPARING', 'SIMULATING', 'AWAITING_SIGNATURE', 'SUBMITTED', 'CONFIRMED', 'FAILED', 'EXPIRED', 'CANCELLED'); +CREATE TYPE "FindingKind" AS ENUM ('DB_PAID_CHAIN_NOT', 'CHAIN_PAID_DB_NOT', 'AMOUNT_MISMATCH', 'RECIPIENT_MISMATCH', 'MISSING_ON_CHAIN', 'ORPHAN_ON_CHAIN', 'FAILED_TX_ACTUALLY_SUCCEEDED'); + +-- ══ 2. New tables ═══════════════════════════════════════════════════════════ +CREATE TABLE "Organization" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Organization_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "OrgMember" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "role" "OrgRole" NOT NULL DEFAULT 'VIEWER', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "OrgMember_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "Project" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "code" TEXT NOT NULL, + "archivedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Project_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "Worker" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "userId" TEXT, + "walletAddress" TEXT NOT NULL, + "displayName" TEXT, + "email" TEXT, + "archivedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Worker_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "PayrollBatch" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "projectId" TEXT, + "reference" TEXT NOT NULL, + "periodStart" TIMESTAMP(3), + "periodEnd" TIMESTAMP(3), + "sourceFilename" TEXT, + "sourceRowCount" INTEGER, + "uploadedBy" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PayrollBatch_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "Payment" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "batchId" TEXT NOT NULL, + "escrowId" TEXT, + "projectId" TEXT, + "workerId" TEXT, + "recipientAddress" TEXT NOT NULL, + "onChainPaymentIndex" INTEGER, + "assetContractId" TEXT, + "assetCode" TEXT NOT NULL DEFAULT 'USDC', + "assetDecimals" INTEGER NOT NULL DEFAULT 7, + "amountBaseUnits" BIGINT NOT NULL, + "rateBaseUnits" BIGINT NOT NULL, + "hours" BIGINT NOT NULL, + "periodStart" TIMESTAMP(3), + "periodEnd" TIMESTAMP(3), + "state" "PaymentState" NOT NULL DEFAULT 'DRAFT', + "stateUpdatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "stateReason" TEXT, + "settledAt" TIMESTAMP(3), + "settlementTxHash" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Payment_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "Approval" ( + "id" TEXT NOT NULL, + "paymentId" TEXT NOT NULL, + "role" "OrgRole" NOT NULL, + "decision" "ApprovalDecision" NOT NULL, + "actorAddress" TEXT NOT NULL, + "reason" TEXT, + "txHash" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Approval_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "BlockchainTransaction" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "paymentId" TEXT, + "escrowId" TEXT, + "kind" "TxKind" NOT NULL, + "status" "TxStatus" NOT NULL DEFAULT 'PREPARING', + "idempotencyKey" TEXT NOT NULL, + "attempt" INTEGER NOT NULL DEFAULT 1, + "hash" TEXT, + "ledger" INTEGER, + "resultCode" TEXT, + "errorMessage" TEXT, + "contractId" TEXT, + "network" TEXT NOT NULL DEFAULT 'testnet', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "submittedAt" TIMESTAMP(3), + "confirmedAt" TIMESTAMP(3), + + CONSTRAINT "BlockchainTransaction_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "AuditEvent" ( + "id" TEXT NOT NULL, + "orgId" TEXT, + "type" TEXT NOT NULL, + "actorAddress" TEXT, + "actorUserId" TEXT, + "actorSystem" TEXT, + "paymentId" TEXT, + "batchId" TEXT, + "escrowId" TEXT, + "previousState" TEXT, + "newState" TEXT, + "txHash" TEXT, + "metadata" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AuditEvent_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "ReconciliationFinding" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "paymentId" TEXT, + "kind" "FindingKind" NOT NULL, + "dbState" TEXT, + "chainState" TEXT, + "detail" TEXT, + "metadata" JSONB, + "detectedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "resolvedAt" TIMESTAMP(3), + "resolvedBy" TEXT, + "resolution" TEXT, + + CONSTRAINT "ReconciliationFinding_pkey" PRIMARY KEY ("id") +); + +-- ══ 2b. Indexes and unique constraints on the new tables ═══════════════════ +-- Created BEFORE the backfill below, because the inserts rely on ON CONFLICT +-- targets that do not exist until their unique indexes do. +CREATE UNIQUE INDEX "Organization_slug_key" ON "Organization"("slug"); +CREATE INDEX "OrgMember_userId_idx" ON "OrgMember"("userId"); +CREATE INDEX "OrgMember_orgId_role_idx" ON "OrgMember"("orgId", "role"); +CREATE UNIQUE INDEX "OrgMember_orgId_userId_key" ON "OrgMember"("orgId", "userId"); +CREATE INDEX "Project_orgId_idx" ON "Project"("orgId"); +CREATE UNIQUE INDEX "Project_orgId_code_key" ON "Project"("orgId", "code"); +CREATE INDEX "Worker_orgId_idx" ON "Worker"("orgId"); +CREATE UNIQUE INDEX "Worker_orgId_walletAddress_key" ON "Worker"("orgId", "walletAddress"); +CREATE INDEX "PayrollBatch_orgId_idx" ON "PayrollBatch"("orgId"); +CREATE INDEX "PayrollBatch_createdAt_idx" ON "PayrollBatch"("createdAt"); +CREATE UNIQUE INDEX "PayrollBatch_orgId_reference_key" ON "PayrollBatch"("orgId", "reference"); +CREATE INDEX "Payment_orgId_state_idx" ON "Payment"("orgId", "state"); +CREATE INDEX "Payment_batchId_idx" ON "Payment"("batchId"); +CREATE INDEX "Payment_escrowId_idx" ON "Payment"("escrowId"); +CREATE INDEX "Payment_recipientAddress_idx" ON "Payment"("recipientAddress"); +CREATE INDEX "Payment_createdAt_idx" ON "Payment"("createdAt"); +CREATE UNIQUE INDEX "Payment_escrowId_onChainPaymentIndex_key" ON "Payment"("escrowId", "onChainPaymentIndex"); +CREATE INDEX "Approval_paymentId_idx" ON "Approval"("paymentId"); +CREATE UNIQUE INDEX "Approval_paymentId_role_key" ON "Approval"("paymentId", "role"); +CREATE UNIQUE INDEX "BlockchainTransaction_idempotencyKey_key" ON "BlockchainTransaction"("idempotencyKey"); +CREATE UNIQUE INDEX "BlockchainTransaction_hash_key" ON "BlockchainTransaction"("hash"); +CREATE INDEX "BlockchainTransaction_orgId_status_idx" ON "BlockchainTransaction"("orgId", "status"); +CREATE INDEX "BlockchainTransaction_paymentId_idx" ON "BlockchainTransaction"("paymentId"); +CREATE INDEX "BlockchainTransaction_escrowId_idx" ON "BlockchainTransaction"("escrowId"); +CREATE INDEX "BlockchainTransaction_hash_idx" ON "BlockchainTransaction"("hash"); +CREATE INDEX "AuditEvent_orgId_createdAt_idx" ON "AuditEvent"("orgId", "createdAt"); +CREATE INDEX "AuditEvent_paymentId_idx" ON "AuditEvent"("paymentId"); +CREATE INDEX "AuditEvent_batchId_idx" ON "AuditEvent"("batchId"); +CREATE INDEX "AuditEvent_escrowId_idx" ON "AuditEvent"("escrowId"); +CREATE INDEX "AuditEvent_type_idx" ON "AuditEvent"("type"); +CREATE INDEX "ReconciliationFinding_orgId_resolvedAt_idx" ON "ReconciliationFinding"("orgId", "resolvedAt"); +CREATE INDEX "ReconciliationFinding_paymentId_idx" ON "ReconciliationFinding"("paymentId"); + +-- ══ 3. Additive column changes (safe on non-empty tables) ══════════════════ +ALTER TABLE "ChainEvent" ADD COLUMN "contractId" TEXT, +ADD COLUMN "network" TEXT NOT NULL DEFAULT 'testnet', +ADD COLUMN "payload" JSONB, +ADD COLUMN "paymentIndex" INTEGER, +ADD COLUMN "txHash" TEXT; + +ALTER TABLE "Invitation" ADD COLUMN "orgId" TEXT, +ADD COLUMN "orgRole" "OrgRole"; + +-- ══ 4. A home for pre-existing records ══════════════════════════════════════ +-- Rows written before organizations existed have to belong to one. A single +-- explicitly-named tenant is created to hold them, rather than inventing a +-- plausible-looking company name that would read as real customer data. +INSERT INTO "Organization" ("id", "name", "slug", "createdAt", "updatedAt") +VALUES ('org_legacy_default', 'Legacy (pre-organization records)', 'legacy', NOW(), NOW()) +ON CONFLICT ("slug") DO NOTHING; + +-- Every existing user becomes an ADMIN of that tenant, preserving the access +-- they already had. Narrowing it would lock people out of their own records. +INSERT INTO "OrgMember" ("id", "orgId", "userId", "role", "createdAt", "updatedAt") +SELECT 'ogm_' || "User"."id", 'org_legacy_default', "User"."id", + CASE WHEN "User"."role" = 'ADMIN' THEN 'OWNER'::"OrgRole" ELSE 'ADMIN'::"OrgRole" END, + NOW(), NOW() +FROM "User" +ON CONFLICT ("orgId", "userId") DO NOTHING; + +-- ══ 5. Escrow: widen before tightening ══════════════════════════════════════ +-- Added nullable, backfilled, then constrained. `id` casts Int -> TEXT in place, +-- so escrow rows and their onChainId survive. +ALTER TABLE "Escrow" + ADD COLUMN "orgId" TEXT, + ADD COLUMN "projectId" TEXT, + ADD COLUMN "contractId" TEXT, + ADD COLUMN "network" TEXT NOT NULL DEFAULT 'testnet', + ADD COLUMN "managerAddress" TEXT, + ADD COLUMN "financeApproverAddress" TEXT, + ADD COLUMN "oraclePublicKey" TEXT, + ADD COLUMN "oracleRotations" INTEGER NOT NULL DEFAULT 0, + ADD COLUMN "cancelled" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "totalAmountBaseUnits" BIGINT NOT NULL DEFAULT 0; + +UPDATE "Escrow" SET + "orgId" = 'org_legacy_default', + -- Legacy rows predate per-escrow contract tracking. They belong to the v1 + -- Mainnet deployment, which is the only contract that existed when they were + -- written; recording that is more honest than leaving it blank. + "contractId" = COALESCE("contractId", 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW'), + "managerAddress" = COALESCE("managerAddress", ''), + "financeApproverAddress" = COALESCE("financeApproverAddress", COALESCE("financeApprover", '')), + "cancelled" = ("status" = 'cancelled'), + "totalAmountBaseUnits" = COALESCE("amountBaseUnits", 0); + +ALTER TABLE "Escrow" + ALTER COLUMN "orgId" SET NOT NULL, + ALTER COLUMN "contractId" SET NOT NULL, + ALTER COLUMN "managerAddress" SET NOT NULL, + ALTER COLUMN "financeApproverAddress" SET NOT NULL; + +-- ══ 6. Expand each legacy Escrow into a batch + ONE Payment ════════════════ +-- This is the lossy-projection fix applied backwards over history: the old +-- schema could only express one payee per escrow, so each legacy escrow yields +-- exactly one Payment carrying the figures it actually held. +INSERT INTO "PayrollBatch" + ("id", "orgId", "reference", "sourceFilename", "sourceRowCount", "createdAt", "updatedAt") +SELECT 'bat_legacy_' || "Escrow"."id", + 'org_legacy_default', + 'LEGACY-' || LPAD("Escrow"."id"::text, 5, '0'), + NULL, 1, "Escrow"."createdAt", NOW() +FROM "Escrow" +ON CONFLICT ("orgId", "reference") DO NOTHING; + +INSERT INTO "Payment" ( + "id", "orgId", "batchId", "escrowId", "recipientAddress", "onChainPaymentIndex", + "assetContractId", "assetCode", "assetDecimals", + "amountBaseUnits", "rateBaseUnits", "hours", + "state", "stateUpdatedAt", "settledAt", "createdAt", "updatedAt" +) +SELECT + 'pay_legacy_' || "Escrow"."id", + 'org_legacy_default', + 'bat_legacy_' || "Escrow"."id", + "Escrow"."id"::text, + COALESCE("Escrow"."workerPubKey", ''), + 0, + "Escrow"."tokenAddress", + COALESCE("Escrow"."currency", 'USDC'), + COALESCE("Escrow"."assetDecimals", 7), + COALESCE("Escrow"."amountBaseUnits", 0), + COALESCE("Escrow"."rateBaseUnits", 1), + -- Whole hours implied by amount / rate. Zero when the rate is unusable, + -- rather than a rounded guess at work nobody attested to. + CASE WHEN COALESCE("Escrow"."rateBaseUnits", 0) > 0 + THEN COALESCE("Escrow"."amountBaseUnits", 0) / "Escrow"."rateBaseUnits" + ELSE 0 END, + -- Legacy status strings map onto the new lifecycle. Anything unrecognised + -- becomes RECONCILIATION_REQUIRED so it surfaces for a human instead of + -- being quietly assumed healthy. + CASE "Escrow"."status" + WHEN 'paid' THEN 'PAID'::"PaymentState" + WHEN 'cancelled' THEN 'CANCELLED'::"PaymentState" + WHEN 'rejected' THEN 'REJECTED'::"PaymentState" + WHEN 'ready' THEN 'READY_TO_SETTLE'::"PaymentState" + WHEN 'pending_finance' THEN 'AWAITING_FINANCE'::"PaymentState" + WHEN 'pending_manager' THEN 'AWAITING_MANAGER'::"PaymentState" + WHEN 'pending_hours' THEN 'AWAITING_ORACLE'::"PaymentState" + ELSE 'RECONCILIATION_REQUIRED'::"PaymentState" + END, + NOW(), + CASE WHEN "Escrow"."status" = 'paid' THEN "Escrow"."updatedAt" ELSE NULL END, + "Escrow"."createdAt", + NOW() +FROM "Escrow" +ON CONFLICT ("escrowId", "onChainPaymentIndex") DO NOTHING; + +-- Record the migration itself in the audit history, so the provenance of these +-- rows is visible rather than something a reader has to deduce. +INSERT INTO "AuditEvent" + ("id", "orgId", "type", "actorSystem", "paymentId", "escrowId", "newState", "metadata", "createdAt") +SELECT 'aud_mig_' || "Payment"."id", "Payment"."orgId", 'payment.migrated', 'migration', + "Payment"."id", "Payment"."escrowId", "Payment"."state"::text, + jsonb_build_object('migration', '20260910120000_payment_state_machine', + 'note', 'Expanded from the single-payee Escrow schema'), + NOW() +FROM "Payment" +WHERE "Payment"."id" LIKE 'pay_legacy_%'; + +-- ══ 6b. Detach TimeLog before the Escrow key is retyped ════════════════════ +-- Escrow's primary key cannot be dropped while a foreign key depends on it, and +-- TimeLog.escrowId must change type alongside it. +ALTER TABLE "TimeLog" DROP CONSTRAINT "TimeLog_escrowId_fkey"; +ALTER TABLE "TimeLog" ALTER COLUMN "escrowId" DROP NOT NULL, +ALTER COLUMN "escrowId" SET DATA TYPE TEXT; + +-- ══ 7. Escrow: retype the key and drop superseded columns ═══════════════════ +ALTER TABLE "Escrow" DROP CONSTRAINT "Escrow_pkey", +DROP COLUMN "amountBaseUnits", +DROP COLUMN "currency", +DROP COLUMN "financeApprover", +DROP COLUMN "rateBaseUnits", +DROP COLUMN "rejectionReason", +DROP COLUMN "status", +DROP COLUMN "workerPubKey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "Escrow_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "Escrow_id_seq"; + +-- ══ 8. OracleAttestation: preserve the payment index before retyping ═══════ +DROP INDEX "OracleAttestation_escrowOnChainId_paymentId_nonce_key"; + +ALTER TABLE "OracleAttestation" + ADD COLUMN "onChainPaymentIndex" INTEGER, + ADD COLUMN "hours" BIGINT, + ADD COLUMN "contractId" TEXT, + ADD COLUMN "preimageSha256" TEXT, + ADD COLUMN "schema" TEXT NOT NULL DEFAULT 'CFWP-v2'; + +-- The old `paymentId` column held the on-chain payment INDEX, not a Payment FK. +-- Copy it across BEFORE the type change, or the index is lost silently. +UPDATE "OracleAttestation" SET + "onChainPaymentIndex" = "paymentId", + "hours" = COALESCE("hoursLogged", 0), + -- Pre-existing attestations were produced under the v1 32-byte message, which + -- bound no network, contract, payee, asset, amount or period. Labelling them + -- CFWP-v2 would overstate their guarantees. + "schema" = 'v1-legacy'; + +ALTER TABLE "OracleAttestation" + ALTER COLUMN "onChainPaymentIndex" SET NOT NULL, + ALTER COLUMN "hours" SET NOT NULL; + +ALTER TABLE "OracleAttestation" DROP COLUMN "hoursLogged"; +ALTER TABLE "OracleAttestation" DROP COLUMN "paymentId"; +ALTER TABLE "OracleAttestation" ADD COLUMN "paymentId" TEXT; +ALTER TABLE "OracleAttestation" ALTER COLUMN "nonce" SET DATA TYPE BIGINT; + +-- Re-link attestations to the Payment rows created above. +UPDATE "OracleAttestation" oa SET "paymentId" = p."id" +FROM "Payment" p +JOIN "Escrow" e ON e."id" = p."escrowId" +WHERE e."onChainId" = oa."escrowOnChainId" + AND p."onChainPaymentIndex" = oa."onChainPaymentIndex"; + +-- ══ 9. IndexerCursor: per (contract, network) ══════════════════════════════ +-- A single global cursor conflates deployments, and v1/v2 escrow ids overlap. +ALTER TABLE "IndexerCursor" + ADD COLUMN "contractId" TEXT, + ADD COLUMN "network" TEXT; + +UPDATE "IndexerCursor" SET + "contractId" = COALESCE("contractId", 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW'), + "network" = COALESCE("network", 'public'); + +ALTER TABLE "IndexerCursor" + ALTER COLUMN "contractId" SET NOT NULL, + ALTER COLUMN "network" SET NOT NULL; + +ALTER TABLE "IndexerCursor" DROP CONSTRAINT "IndexerCursor_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "IndexerCursor_pkey" PRIMARY KEY ("id"); + +-- ══ 11. Indexes on altered tables ════════════════════════════════════════ +CREATE INDEX "ChainEvent_escrowOnChainId_idx" ON "ChainEvent"("escrowOnChainId"); +CREATE INDEX "ChainEvent_contractId_network_idx" ON "ChainEvent"("contractId", "network"); +CREATE INDEX "Escrow_orgId_idx" ON "Escrow"("orgId"); +CREATE INDEX "Escrow_contractId_network_idx" ON "Escrow"("contractId", "network"); +CREATE UNIQUE INDEX "IndexerCursor_contractId_network_key" ON "IndexerCursor"("contractId", "network"); +CREATE INDEX "OracleAttestation_paymentId_idx" ON "OracleAttestation"("paymentId"); +CREATE UNIQUE INDEX "OracleAttestation_escrowOnChainId_onChainPaymentIndex_nonce_key" ON "OracleAttestation"("escrowOnChainId", "onChainPaymentIndex", "nonce"); +CREATE INDEX "TimeLog_escrowId_idx" ON "TimeLog"("escrowId"); + +-- ══ 12. Foreign keys ═══════════════════════════════════════════════════════ +ALTER TABLE "OrgMember" ADD CONSTRAINT "OrgMember_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "OrgMember" ADD CONSTRAINT "OrgMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Project" ADD CONSTRAINT "Project_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Worker" ADD CONSTRAINT "Worker_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Worker" ADD CONSTRAINT "Worker_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Escrow" ADD CONSTRAINT "Escrow_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Escrow" ADD CONSTRAINT "Escrow_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "PayrollBatch" ADD CONSTRAINT "PayrollBatch_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "PayrollBatch" ADD CONSTRAINT "PayrollBatch_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "PayrollBatch"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_escrowId_fkey" FOREIGN KEY ("escrowId") REFERENCES "Escrow"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_workerId_fkey" FOREIGN KEY ("workerId") REFERENCES "Worker"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Approval" ADD CONSTRAINT "Approval_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "OracleAttestation" ADD CONSTRAINT "OracleAttestation_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_escrowId_fkey" FOREIGN KEY ("escrowId") REFERENCES "Escrow"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "PayrollBatch"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_escrowId_fkey" FOREIGN KEY ("escrowId") REFERENCES "Escrow"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "ReconciliationFinding" ADD CONSTRAINT "ReconciliationFinding_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "ReconciliationFinding" ADD CONSTRAINT "ReconciliationFinding_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260911000000_multi_tenancy/migration.sql b/prisma/migrations/20260911000000_multi_tenancy/migration.sql new file mode 100644 index 0000000..76d9ac6 --- /dev/null +++ b/prisma/migrations/20260911000000_multi_tenancy/migration.sql @@ -0,0 +1,186 @@ +-- CoreFlow multi-tenancy (P2 #2). +-- +-- Makes the organization boundary a DATABASE constraint rather than an +-- application convention. +-- +-- ── What this migration is for ────────────────────────────────────────────── +-- Before it, `Payment.batchId` was a plain foreign key: it guaranteed the batch +-- existed, not that the batch belonged to the payment's organization. A bug or a +-- crafted request could attach a payment in org A to a batch, escrow, project or +-- worker in org B, and no constraint would object. Tenant isolation rested +-- entirely on every query remembering to filter β€” which is exactly the thing a +-- security boundary must not depend on. +-- +-- Every parent relation on tenant-owned records becomes a COMPOSITE foreign key +-- on (orgId, id). Cross-tenant attachment is now rejected by PostgreSQL. +-- +-- Also: `Approval` and `OracleAttestation` gain an explicit owner (they were +-- reachable only by joining through Payment); `AuditEvent.orgId` becomes +-- REQUIRED (a null-org audit row is invisible to every scoped query, i.e. to +-- everyone); `Invitation` becomes org-scoped with a hashed token and per-org +-- email uniqueness. +-- +-- Hand-ordered: the generated diff adds NOT NULL columns to populated tables and +-- drops Invitation.token before anything can be derived from it. + +-- ══ 1. Membership lifecycle ═════════════════════════════════════════════════ +CREATE TYPE "MembershipStatus" AS ENUM ('INVITED', 'ACTIVE', 'SUSPENDED', 'REMOVED'); + +ALTER TABLE "OrgMember" + ADD COLUMN "status" "MembershipStatus" NOT NULL DEFAULT 'ACTIVE', + ADD COLUMN "invitedBy" TEXT, + ADD COLUMN "invitedAt" TIMESTAMP(3), + ADD COLUMN "activatedAt" TIMESTAMP(3), + ADD COLUMN "suspendedAt" TIMESTAMP(3), + ADD COLUMN "removedAt" TIMESTAMP(3); + +-- Pre-existing memberships were created by the legacy migration and are in use, +-- so they are ACTIVE from their creation date rather than retroactively INVITED. +UPDATE "OrgMember" SET "activatedAt" = "createdAt" WHERE "activatedAt" IS NULL; + +-- ══ 2. Approval: add its owner, derived from the payment ════════════════════ +ALTER TABLE "Approval" ADD COLUMN "orgId" TEXT; + +UPDATE "Approval" a SET "orgId" = p."orgId" +FROM "Payment" p WHERE p."id" = a."paymentId"; + +-- An approval whose payment vanished cannot be attributed to a tenant. Deleting +-- it would destroy an approval record; there is nowhere safe to put it, so the +-- migration fails loudly rather than inventing an owner. +DO $$ +DECLARE orphans INT; +BEGIN + SELECT count(*) INTO orphans FROM "Approval" WHERE "orgId" IS NULL; + IF orphans > 0 THEN + RAISE EXCEPTION 'Cannot migrate: % Approval row(s) have no resolvable organization. Resolve these manually before migrating.', orphans; + END IF; +END $$; + +ALTER TABLE "Approval" ALTER COLUMN "orgId" SET NOT NULL; + +-- ══ 3. OracleAttestation: add its owner ════════════════════════════════════ +ALTER TABLE "OracleAttestation" ADD COLUMN "orgId" TEXT; + +UPDATE "OracleAttestation" oa SET "orgId" = p."orgId" +FROM "Payment" p WHERE p."id" = oa."paymentId"; + +-- Attestations predating the payment model have no link. They belong to the +-- legacy tenant created by the previous migration, which is where every other +-- pre-organization record already lives. +UPDATE "OracleAttestation" SET "orgId" = 'org_legacy_default' +WHERE "orgId" IS NULL + AND EXISTS (SELECT 1 FROM "Organization" WHERE "id" = 'org_legacy_default'); + +DELETE FROM "OracleAttestation" WHERE "orgId" IS NULL; + +ALTER TABLE "OracleAttestation" ALTER COLUMN "orgId" SET NOT NULL; + +-- ══ 4. AuditEvent.orgId becomes required ═══════════════════════════════════ +UPDATE "AuditEvent" SET "orgId" = 'org_legacy_default' +WHERE "orgId" IS NULL + AND EXISTS (SELECT 1 FROM "Organization" WHERE "id" = 'org_legacy_default'); + +-- An audit row with no tenant is unreadable by any scoped query. Rather than +-- keep invisible history, unattributable rows are removed and the count is +-- reported so the loss is not silent. +DO $$ +DECLARE orphans INT; +BEGIN + SELECT count(*) INTO orphans FROM "AuditEvent" WHERE "orgId" IS NULL; + IF orphans > 0 THEN + RAISE NOTICE 'Removing % AuditEvent row(s) with no resolvable organization.', orphans; + DELETE FROM "AuditEvent" WHERE "orgId" IS NULL; + END IF; +END $$; + +ALTER TABLE "AuditEvent" ALTER COLUMN "orgId" SET NOT NULL; + +-- ══ 5. Invitation: org-scoped, hashed token, per-org email ═════════════════ +DROP INDEX "Invitation_email_key"; +DROP INDEX "Invitation_token_idx"; +DROP INDEX "Invitation_token_key"; + +ALTER TABLE "Invitation" + ADD COLUMN "tokenHash" TEXT, + ADD COLUMN "revokedAt" TIMESTAMP(3), + ADD COLUMN "revokedBy" TEXT, + ADD COLUMN "invitedBy" TEXT; + +-- Derive the hash from the existing plaintext token BEFORE dropping it, so live +-- invitation links keep working. sha256 matches what the application computes. +-- Built-in sha256() (PostgreSQL 11+), NOT pgcrypto's digest(). A migration that +-- requires an extension fails on any managed Postgres where the role cannot +-- CREATE EXTENSION β€” which is most of them. +UPDATE "Invitation" SET "tokenHash" = encode(sha256(convert_to("token", 'UTF8')), 'hex') +WHERE "tokenHash" IS NULL AND "token" IS NOT NULL; + +UPDATE "Invitation" SET "orgId" = 'org_legacy_default' +WHERE "orgId" IS NULL + AND EXISTS (SELECT 1 FROM "Organization" WHERE "id" = 'org_legacy_default'); +UPDATE "Invitation" SET "orgRole" = 'VIEWER' WHERE "orgRole" IS NULL; + +-- Any invitation still unattributable is revoked rather than carried forward: an +-- invitation that cannot name its organization must not be acceptable. +DELETE FROM "Invitation" WHERE "orgId" IS NULL OR "tokenHash" IS NULL; + +ALTER TABLE "Invitation" DROP COLUMN "token"; +ALTER TABLE "Invitation" + ALTER COLUMN "tokenHash" SET NOT NULL, + ALTER COLUMN "orgId" SET NOT NULL, + ALTER COLUMN "orgRole" SET NOT NULL; + +-- ══ 6. Composite-key targets, before any FK references them ════════════════ +CREATE UNIQUE INDEX "Escrow_orgId_id_key" ON "Escrow"("orgId", "id"); +CREATE UNIQUE INDEX "Payment_orgId_id_key" ON "Payment"("orgId", "id"); +CREATE UNIQUE INDEX "PayrollBatch_orgId_id_key" ON "PayrollBatch"("orgId", "id"); +CREATE UNIQUE INDEX "Project_orgId_id_key" ON "Project"("orgId", "id"); +CREATE UNIQUE INDEX "Worker_orgId_id_key" ON "Worker"("orgId", "id"); + +-- ══ 7. Remaining indexes ═══════════════════════════════════════════════════ +CREATE UNIQUE INDEX "Invitation_tokenHash_key" ON "Invitation"("tokenHash"); +CREATE UNIQUE INDEX "Invitation_orgId_email_key" ON "Invitation"("orgId", "email"); +CREATE INDEX "Approval_orgId_idx" ON "Approval"("orgId"); +CREATE INDEX "Invitation_tokenHash_idx" ON "Invitation"("tokenHash"); +CREATE INDEX "Invitation_orgId_idx" ON "Invitation"("orgId"); +CREATE INDEX "OracleAttestation_orgId_idx" ON "OracleAttestation"("orgId"); +CREATE INDEX "OrgMember_orgId_status_idx" ON "OrgMember"("orgId", "status"); + +-- ══ 8. Replace single-column FKs with composite ones ═══════════════════════ +-- From here, PostgreSQL itself rejects a child row pointing at a parent in a +-- different organization. Note MATCH SIMPLE semantics: when the optional id is +-- NULL the constraint is satisfied, which is the intended behaviour for optional +-- parents. +ALTER TABLE "Approval" DROP CONSTRAINT "Approval_paymentId_fkey"; +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_batchId_fkey"; +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_escrowId_fkey"; +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_orgId_fkey"; +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_paymentId_fkey"; +ALTER TABLE "BlockchainTransaction" DROP CONSTRAINT "BlockchainTransaction_escrowId_fkey"; +ALTER TABLE "BlockchainTransaction" DROP CONSTRAINT "BlockchainTransaction_paymentId_fkey"; +ALTER TABLE "Escrow" DROP CONSTRAINT "Escrow_projectId_fkey"; +ALTER TABLE "OracleAttestation" DROP CONSTRAINT "OracleAttestation_paymentId_fkey"; +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_batchId_fkey"; +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_escrowId_fkey"; +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_projectId_fkey"; +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_workerId_fkey"; +ALTER TABLE "PayrollBatch" DROP CONSTRAINT "PayrollBatch_projectId_fkey"; +ALTER TABLE "ReconciliationFinding" DROP CONSTRAINT "ReconciliationFinding_paymentId_fkey"; + +ALTER TABLE "Escrow" ADD CONSTRAINT "Escrow_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "PayrollBatch" ADD CONSTRAINT "PayrollBatch_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_batchId_fkey" FOREIGN KEY ("orgId", "batchId") REFERENCES "PayrollBatch"("orgId", "id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_workerId_fkey" FOREIGN KEY ("orgId", "workerId") REFERENCES "Worker"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Approval" ADD CONSTRAINT "Approval_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Approval" ADD CONSTRAINT "Approval_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "OracleAttestation" ADD CONSTRAINT "OracleAttestation_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "OracleAttestation" ADD CONSTRAINT "OracleAttestation_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_batchId_fkey" FOREIGN KEY ("orgId", "batchId") REFERENCES "PayrollBatch"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "ReconciliationFinding" ADD CONSTRAINT "ReconciliationFinding_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "Invitation" ADD CONSTRAINT "Invitation_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260911010000_chain_event_attribution/migration.sql b/prisma/migrations/20260911010000_chain_event_attribution/migration.sql new file mode 100644 index 0000000..184d55e --- /dev/null +++ b/prisma/migrations/20260911010000_chain_event_attribution/migration.sql @@ -0,0 +1,14 @@ +-- Records whether a chain event could be attributed to an organization. +-- +-- The chain knows nothing about CoreFlow organizations. An escrow created outside +-- the app β€” by the CLI, a validation script, or another client β€” has no tenant +-- mapping, and guessing one would silently place another party's payroll inside a +-- customer's workspace. Unattributable events are now recorded rather than either +-- dropped or mis-assigned, and surfaced for an operator to claim. +-- +-- Existing rows default to true: they were ingested under the previous model, +-- which only ever projected escrows it had already attributed. + +ALTER TABLE "ChainEvent" ADD COLUMN "attributed" BOOLEAN NOT NULL DEFAULT true; + +CREATE INDEX "ChainEvent_attributed_idx" ON "ChainEvent"("attributed"); diff --git a/prisma/migrations/20260911015000_finding_kind_values/migration.sql b/prisma/migrations/20260911015000_finding_kind_values/migration.sql new file mode 100644 index 0000000..96d7711 --- /dev/null +++ b/prisma/migrations/20260911015000_finding_kind_values/migration.sql @@ -0,0 +1,26 @@ +-- New FindingKind values, committed BEFORE anything uses them. +-- +-- These were originally in 20260911020000_reconciliation_reliability, alongside +-- the UPDATE statements that assign severities by kind. Real PostgreSQL refuses +-- that: a value added to an existing enum cannot be USED until the transaction +-- that added it commits. +-- +-- ERROR: unsafe use of new value "ASSET_MISMATCH" of enum type "FindingKind" +-- HINT: New enum values must be committed before they can be used. (55P04) +-- +-- `prisma migrate deploy` wraps each migration in one transaction, so add-and-use +-- in a single file can never work β€” regardless of PostgreSQL version, and despite +-- the generated comment in that file suggesting it is only a PG-11-and-earlier +-- concern. Splitting the ADD VALUE statements into their own migration makes the +-- commit boundary explicit. +-- +-- IF NOT EXISTS so this is safe to apply to a database that already has some of +-- these values, which is the situation any database migrated by hand is in. + +-- AlterEnum +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'ASSET_MISMATCH'; +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'UNKNOWN_ON_CHAIN_OBJECT'; +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'MISSING_PAYMENT_EVENT'; +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'DUPLICATE_PAYMENT_EVENT'; +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'CHAIN_UNREADABLE'; +ALTER TYPE "FindingKind" ADD VALUE IF NOT EXISTS 'OTHER'; diff --git a/prisma/migrations/20260911020000_reconciliation_reliability/migration.sql b/prisma/migrations/20260911020000_reconciliation_reliability/migration.sql new file mode 100644 index 0000000..dca4ea9 --- /dev/null +++ b/prisma/migrations/20260911020000_reconciliation_reliability/migration.sql @@ -0,0 +1,126 @@ +-- Reconciliation reliability (P2 #4). +-- +-- Turns reconciliation from a function into an operated system: runs with +-- heartbeats and locking, a granular finding taxonomy, severity, and an auditable +-- finding lifecycle. +-- +-- ── Why the taxonomy is granular ──────────────────────────────────────────── +-- A single "MISMATCH" value is useless. An unreadable RPC, a settled-but- +-- unrecorded payment, and a database claiming a payment that never settled demand +-- completely different responses β€” retry, catch up, and stop trusting the record. +-- Collapsing them forces an operator to re-derive the distinction from free text. +-- +-- ── Why runs are recorded ─────────────────────────────────────────────────── +-- So an operator can answer "when did CoreFlow last reconcile this organization, +-- and did that run finish?". A reconciler whose last run silently died is worse +-- than none, because the absence of findings reads as health. + +-- CreateEnum +CREATE TYPE "FindingStatus" AS ENUM ('OPEN', 'ACKNOWLEDGED', 'INVESTIGATING', 'RESOLVED'); + +-- CreateEnum +CREATE TYPE "FindingSeverity" AS ENUM ('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'); + +-- CreateEnum +CREATE TYPE "ReconcileOutcome" AS ENUM ('AGREED', 'CHAIN_AHEAD', 'DATABASE_AHEAD', 'CHAIN_UNREADABLE', 'MISMATCHED', 'UNKNOWN_ON_CHAIN_OBJECT', 'ORPHANED_DATABASE_OBJECT'); + +-- CreateEnum +CREATE TYPE "RunStatus" AS ENUM ('RUNNING', 'COMPLETED', 'FAILED', 'STALE'); + +-- AlterEnum +-- The new FindingKind values are added by the PRECEDING migration, +-- 20260911015000_finding_kind_values, and deliberately not here. +-- +-- PostgreSQL refuses to let a value added to an existing enum be used until the +-- adding transaction commits (55P04), and `migrate deploy` runs each migration in +-- one transaction. Since the statements below assign severities BY KIND, the +-- values must already be committed by the time this migration runs. + +-- DropIndex +DROP INDEX "ReconciliationFinding_orgId_resolvedAt_idx"; + +-- AlterTable +ALTER TABLE "Invitation" ALTER COLUMN "orgRole" SET DEFAULT 'VIEWER'; + +-- AlterTable +ALTER TABLE "ReconciliationFinding" ADD COLUMN "acknowledgedAt" TIMESTAMP(3), +ADD COLUMN "acknowledgedBy" TEXT, +ADD COLUMN "escrowOnChainId" INTEGER, +ADD COLUMN "lastObservedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "observationCount" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "paymentIndex" INTEGER, +ADD COLUMN "remediation" TEXT, +ADD COLUMN "runId" TEXT, +ADD COLUMN "severity" "FindingSeverity" NOT NULL DEFAULT 'MEDIUM', +ADD COLUMN "status" "FindingStatus" NOT NULL DEFAULT 'OPEN', +ADD COLUMN "txHash" TEXT; + +-- CreateTable +CREATE TABLE "ReconciliationRun" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "correlationId" TEXT NOT NULL, + "scope" TEXT NOT NULL, + "contractId" TEXT, + "network" TEXT, + "status" "RunStatus" NOT NULL DEFAULT 'RUNNING', + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completedAt" TIMESTAMP(3), + "heartbeatAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "escrowsExamined" INTEGER NOT NULL DEFAULT 0, + "paymentsExamined" INTEGER NOT NULL DEFAULT 0, + "agreed" INTEGER NOT NULL DEFAULT 0, + "mismatched" INTEGER NOT NULL DEFAULT 0, + "unreadable" INTEGER NOT NULL DEFAULT 0, + "chainAhead" INTEGER NOT NULL DEFAULT 0, + "databaseAhead" INTEGER NOT NULL DEFAULT 0, + "findingsOpened" INTEGER NOT NULL DEFAULT 0, + "correctionsApplied" INTEGER NOT NULL DEFAULT 0, + "errorMessage" TEXT, + + CONSTRAINT "ReconciliationRun_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ReconciliationRun_correlationId_key" ON "ReconciliationRun"("correlationId"); + +-- CreateIndex +CREATE INDEX "ReconciliationRun_orgId_startedAt_idx" ON "ReconciliationRun"("orgId", "startedAt"); + +-- CreateIndex +CREATE INDEX "ReconciliationRun_status_idx" ON "ReconciliationRun"("status"); + +-- CreateIndex +CREATE INDEX "ReconciliationFinding_orgId_status_idx" ON "ReconciliationFinding"("orgId", "status"); + +-- CreateIndex +CREATE INDEX "ReconciliationFinding_orgId_severity_status_idx" ON "ReconciliationFinding"("orgId", "severity", "status"); + +-- CreateIndex +CREATE INDEX "ReconciliationFinding_runId_idx" ON "ReconciliationFinding"("runId"); + +-- AddForeignKey +ALTER TABLE "ReconciliationRun" ADD CONSTRAINT "ReconciliationRun_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReconciliationFinding" ADD CONSTRAINT "ReconciliationFinding_runId_fkey" FOREIGN KEY ("runId") REFERENCES "ReconciliationRun"("id") ON DELETE SET NULL ON UPDATE CASCADE; + + +-- ── Backfill: a finding that was already resolved is RESOLVED, not OPEN ───── +-- The new `status` column defaults to OPEN. Applied blindly that would reopen +-- every historical finding, burying genuine problems in noise on the first run. +UPDATE "ReconciliationFinding" +SET "status" = 'RESOLVED' +WHERE "resolvedAt" IS NOT NULL; + +-- Existing findings were observed once, when they were detected. +UPDATE "ReconciliationFinding" +SET "lastObservedAt" = "detectedAt" +WHERE "lastObservedAt" IS NULL OR "lastObservedAt" < "detectedAt"; + +-- Severity for pre-existing findings, by kind. DB_PAID_CHAIN_NOT is CRITICAL +-- because it means the product may be making a false statement about money. +UPDATE "ReconciliationFinding" SET "severity" = 'CRITICAL' +WHERE "kind" IN ('DB_PAID_CHAIN_NOT', 'FAILED_TX_ACTUALLY_SUCCEEDED'); +UPDATE "ReconciliationFinding" SET "severity" = 'HIGH' +WHERE "kind" IN ('AMOUNT_MISMATCH', 'RECIPIENT_MISMATCH', 'ASSET_MISMATCH', 'DUPLICATE_PAYMENT_EVENT'); diff --git a/prisma/migrations/20260911030000_reconciliation_run_lock/migration.sql b/prisma/migrations/20260911030000_reconciliation_run_lock/migration.sql new file mode 100644 index 0000000..982dc50 --- /dev/null +++ b/prisma/migrations/20260911030000_reconciliation_run_lock/migration.sql @@ -0,0 +1,19 @@ +-- One RUNNING reconciliation run per organization, enforced by the database. +-- +-- ── Why a partial unique index ────────────────────────────────────────────── +-- The application checks for an active run before starting one, but a check +-- followed by an insert is a race: two workers triggered by the same cron tick can +-- both pass the check and both insert. The consequence is not a double payment β€” +-- corrections are compare-and-swap β€” but duplicated findings, doubled RPC cost, and +-- two runs reporting contradictory summaries for the same moment. +-- +-- A partial unique index makes PostgreSQL refuse the second insert outright, so the +-- lock does not depend on the application noticing. Prisma's schema language cannot +-- express a WHERE clause on a unique index, hence raw SQL. +-- +-- COMPLETED, FAILED and STALE rows are deliberately excluded: historical runs must +-- accumulate, and only the live one is exclusive. + +CREATE UNIQUE INDEX "ReconciliationRun_one_running_per_org" + ON "ReconciliationRun" ("orgId") + WHERE "status" = 'RUNNING'; diff --git a/prisma/migrations/20260911040000_payroll_batch_idempotency/migration.sql b/prisma/migrations/20260911040000_payroll_batch_idempotency/migration.sql new file mode 100644 index 0000000..acf3e40 --- /dev/null +++ b/prisma/migrations/20260911040000_payroll_batch_idempotency/migration.sql @@ -0,0 +1,30 @@ +-- Batch-creation idempotency, enforced by the database. +-- +-- Both columns are nullable and added to a table that may already hold rows, so +-- this is additive only: no backfill, no NOT NULL, nothing to order carefully. +-- +-- The unique index relies on Postgres treating NULLs as DISTINCT, so any number +-- of batches created WITHOUT an idempotency key coexist, while two requests +-- carrying the same key can only ever produce one row. + +ALTER TABLE "PayrollBatch" ADD COLUMN "idempotencyKey" TEXT; +ALTER TABLE "PayrollBatch" ADD COLUMN "sourceChecksum" TEXT; + +-- Distinguishes a genuine retry (same key, same payload) from a key collision +-- (same key, different payload). The latter is refused: returning the original +-- batch would hand back something other than what the caller just described. +ALTER TABLE "PayrollBatch" ADD COLUMN "idempotencyFingerprint" TEXT; + +CREATE UNIQUE INDEX "PayrollBatch_orgId_idempotencyKey_key" + ON "PayrollBatch"("orgId", "idempotencyKey"); + +-- Supports "have I uploaded this exact file already?" without scanning a tenant's +-- whole payroll history. +CREATE INDEX "PayrollBatch_orgId_sourceChecksum_idx" + ON "PayrollBatch"("orgId", "sourceChecksum"); + +-- Per-row free text from the uploaded CSV. Nullable and additive. +-- +-- Stored already neutralized against spreadsheet formula injection, so every +-- read path inherits the protection instead of each having to reapply it. +ALTER TABLE "Payment" ADD COLUMN "sourceReference" TEXT; diff --git a/prisma/migrations/20260911044540_composite_fk_no_action/migration.sql b/prisma/migrations/20260911044540_composite_fk_no_action/migration.sql new file mode 100644 index 0000000..be0277c --- /dev/null +++ b/prisma/migrations/20260911044540_composite_fk_no_action/migration.sql @@ -0,0 +1,96 @@ +-- Composite tenant foreign keys: ON DELETE SET NULL -> NO ACTION. +-- +-- SET NULL was unusable on these relations and had never been exercised. It nulls +-- EVERY column of the foreign key, and the first column is `orgId`, which is +-- NOT NULL. So deleting an Escrow, Project or Worker that any row referenced +-- failed with: +-- +-- Null constraint violation on the fields: (`orgId`) +-- +-- Found by an integration test against real PostgreSQL (`prisma validate` had been +-- warning about it; the unit suite, using an in-memory double, could not see it). +-- +-- NO ACTION rather than RESTRICT: NO ACTION is checked at the END of the +-- statement, so a cascading delete from Organization β€” which removes parent and +-- child in the same statement β€” still succeeds. RESTRICT is checked immediately +-- and would reject it depending on evaluation order. +-- +-- The resulting behaviour for a DIRECT delete is to refuse it while dependent rows +-- exist. That is correct for financial data: detaching a payment from its escrow, +-- project or worker destroys the record of what was paid for, and the payment must +-- outlive an attempt to tidy up around it. +-- +-- Every statement below only re-declares a referential action. No data is touched, +-- no column is added or dropped, and the constraint columns are unchanged. + +-- DropForeignKey +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_orgId_batchId_fkey"; + +-- DropForeignKey +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_orgId_escrowId_fkey"; + +-- DropForeignKey +ALTER TABLE "AuditEvent" DROP CONSTRAINT "AuditEvent_orgId_paymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "BlockchainTransaction" DROP CONSTRAINT "BlockchainTransaction_orgId_escrowId_fkey"; + +-- DropForeignKey +ALTER TABLE "BlockchainTransaction" DROP CONSTRAINT "BlockchainTransaction_orgId_paymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "Escrow" DROP CONSTRAINT "Escrow_orgId_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "OracleAttestation" DROP CONSTRAINT "OracleAttestation_orgId_paymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_orgId_escrowId_fkey"; + +-- DropForeignKey +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_orgId_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "Payment" DROP CONSTRAINT "Payment_orgId_workerId_fkey"; + +-- DropForeignKey +ALTER TABLE "PayrollBatch" DROP CONSTRAINT "PayrollBatch_orgId_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "ReconciliationFinding" DROP CONSTRAINT "ReconciliationFinding_orgId_paymentId_fkey"; + +-- AddForeignKey +ALTER TABLE "Escrow" ADD CONSTRAINT "Escrow_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PayrollBatch" ADD CONSTRAINT "PayrollBatch_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_projectId_fkey" FOREIGN KEY ("orgId", "projectId") REFERENCES "Project"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_orgId_workerId_fkey" FOREIGN KEY ("orgId", "workerId") REFERENCES "Worker"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OracleAttestation" ADD CONSTRAINT "OracleAttestation_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_batchId_fkey" FOREIGN KEY ("orgId", "batchId") REFERENCES "PayrollBatch"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_orgId_escrowId_fkey" FOREIGN KEY ("orgId", "escrowId") REFERENCES "Escrow"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReconciliationFinding" ADD CONSTRAINT "ReconciliationFinding_orgId_paymentId_fkey" FOREIGN KEY ("orgId", "paymentId") REFERENCES "Payment"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; diff --git a/prisma/migrations/20260911052247_funding_transaction_batch_link/migration.sql b/prisma/migrations/20260911052247_funding_transaction_batch_link/migration.sql new file mode 100644 index 0000000..3b0524f --- /dev/null +++ b/prisma/migrations/20260911052247_funding_transaction_batch_link/migration.sql @@ -0,0 +1,19 @@ +-- Link a blockchain transaction to the batch it acts for. +-- +-- Needed for funding idempotency. initialize_multi_sig_escrow creates the escrow +-- AND pulls custody in ONE atomic invocation, so submitting it twice produces two +-- funded escrows and charges the manager twice. The contract has no idempotency of +-- its own, so the question "has this batch already been funded, or is an attempt in +-- flight?" must be answered off-chain -- and escrowId cannot answer it, because it +-- is null until the escrow the attempt is creating exists. +-- +-- Nullable and additive: a payment-level transaction has no batch of its own. + +-- AlterTable +ALTER TABLE "BlockchainTransaction" ADD COLUMN "batchId" TEXT; + +-- CreateIndex +CREATE INDEX "BlockchainTransaction_batchId_idx" ON "BlockchainTransaction"("batchId"); + +-- AddForeignKey +ALTER TABLE "BlockchainTransaction" ADD CONSTRAINT "BlockchainTransaction_orgId_batchId_fkey" FOREIGN KEY ("orgId", "batchId") REFERENCES "PayrollBatch"("orgId", "id") ON DELETE NO ACTION ON UPDATE CASCADE; diff --git a/prisma/migrations/20260911121635_funding_plan_immutable/migration.sql b/prisma/migrations/20260911121635_funding_plan_immutable/migration.sql new file mode 100644 index 0000000..13d5744 --- /dev/null +++ b/prisma/migrations/20260911121635_funding_plan_immutable/migration.sql @@ -0,0 +1,16 @@ +-- Persist the funding plan a transaction was prepared for, immutably. +-- +-- Confirmation must compare chain evidence against the plan as it stood WHEN THE +-- WALLET WAS OPENED, not against a freshly recomputed one. Configuration can move +-- under a pending transaction -- a changed settlement asset, a different finance +-- approver, an edited payment -- and a recomputed plan would quietly agree with +-- whatever the chain happened to contain. +-- +-- planDigest is SHA-256 over the canonical form, so tampering with the JSON is +-- detectable rather than merely unlikely. +-- +-- Nullable and additive. Money inside the JSON is stored as decimal STRINGS. + +-- AlterTable +ALTER TABLE "BlockchainTransaction" ADD COLUMN "plan" JSONB, +ADD COLUMN "planDigest" TEXT; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2f77ed1..29b2641 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,5 +1,17 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema +// CoreFlow data model. +// +// ── Authority, stated once ─────────────────────────────────────────────────── +// THE CHAIN IS AUTHORITATIVE for settlement: token movement, contract +// authorization, contract state, and final transaction results. This database is +// a PROJECTION of that truth plus the workflow metadata the chain does not hold +// (organizations, CSV provenance, assignments, search, audit views). +// +// Consequences encoded below: +// * `Payment.state` is a projection, never an assertion. Only the indexer β€” +// reading the contract's event log β€” may move a payment to PAID. +// * A disagreement between this database and the chain is recorded as a +// `ReconciliationFinding` and surfaced, never silently overwritten. +// * Money is BigInt base units only. No Float, no Decimal-as-Float, no cents. generator client { provider = "prisma-client-js" @@ -13,52 +25,102 @@ datasource db { directUrl = env("DIRECT_URL") } -/// Two-role access control: ADMIN (full access) and EMPLOYEE (restricted). +// ─── Identity & tenancy ─────────────────────────────────────────────────────── + +/// Legacy platform-wide role. Retained so existing sessions and the admin +/// bootstrap path keep working; per-organization authority lives in `OrgRole`. enum Role { ADMIN EMPLOYEE } -model Escrow { - id Int @id @default(autoincrement()) - onChainId Int? @unique // The Soroban contract escrow ID (can be null if pending creation) - workerPubKey String - amountCents Int - rateCents Int - currency String @default("USDC") - tokenAddress String? // Stellar Asset Contract address used for custody/settlement - status String @default("pending_manager") // pending_manager, pending_finance, ready, paid, cancelled, rejected - managerApproved Boolean @default(false) - financeApproved Boolean @default(false) - rejectionReason String? // Reason provided by Admin if hours/escrow rejected - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - timeLogs TimeLog[] +/// Authority WITHIN an organization. Every payment operation is authorized +/// against this, never against the platform-wide `Role`. +/// +/// MANAGER and FINANCE are deliberately separate and neither implies the other: +/// the product's core claim is separation of duties, and a role that could do +/// both would make the dual-approval gate vacuous in the same way a single +/// signer holding both on-chain keys would. +enum OrgRole { + OWNER + ADMIN + MANAGER + FINANCE + WORKER + VIEWER +} - @@index([createdAt]) +/// Membership lifecycle. Only ACTIVE grants any authority. +/// +/// SUSPENDED is kept distinct from REMOVED so access can be revoked without +/// destroying the record of who held what β€” an audit trail that says a role +/// "never existed" after an incident is worse than no trail at all. +enum MembershipStatus { + INVITED + ACTIVE + SUSPENDED + REMOVED } -model TimeLog { - id Int @id @default(autoincrement()) - escrowId Int - escrow Escrow @relation(fields: [escrowId], references: [id]) - hoursLogged Int - paymentId Int // Represents the schedule index from Soroban - txHash String @unique // Enforce uniqueness to prevent double-counting hours off-chain - createdAt DateTime @default(now()) +model Organization { + id String @id @default(cuid()) + name String + slug String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members OrgMember[] + projects Project[] + workers Worker[] + escrows Escrow[] + batches PayrollBatch[] + payments Payment[] + approvals Approval[] + attestations OracleAttestation[] + transactions BlockchainTransaction[] + auditEvents AuditEvent[] + findings ReconciliationFinding[] + reconciliationRuns ReconciliationRun[] + invitations Invitation[] } -// ─── Authentication Models ──────────────────────────────────────────────────── +model OrgMember { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + role OrgRole @default(VIEWER) + status MembershipStatus @default(ACTIVE) + + /// Who granted this membership, for audit. + invitedBy String? + invitedAt DateTime? + activatedAt DateTime? + suspendedAt DateTime? + removedAt DateTime? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + /// One membership row per user per organization. + @@unique([orgId, userId]) + @@index([userId]) + @@index([orgId, role]) + @@index([orgId, status]) +} model User { id String @id @default(cuid()) walletAddress String @unique - /// ADMIN or EMPLOYEE β€” stored as a Prisma enum for type safety + /// Platform-wide role. Organization authority comes from OrgMember. role Role @default(EMPLOYEE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - sessions Session[] + + sessions Session[] + memberships OrgMember[] + workerProfiles Worker[] } model Session { @@ -74,62 +136,778 @@ model Session { @@index([expiresAt]) } -model Invitation { - id String @id @default(cuid()) - email String @unique - role Role @default(EMPLOYEE) - token String @unique - expiresAt DateTime - usedAt DateTime? - createdAt DateTime @default(now()) +model Project { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + name String + code String + archivedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - @@index([token]) - @@index([email]) + escrows Escrow[] + batches PayrollBatch[] + payments Payment[] + + @@unique([orgId, code]) + @@index([orgId]) + /// Referenced by composite foreign keys, so a child cannot point at a + /// parent in another organization. + @@unique([orgId, id]) +} + +/// A payee. Distinct from `User`: a worker may be paid without ever signing in, +/// and the same wallet may be a worker in several organizations. +model Worker { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + /// Optional link to a platform account, when the payee has one. + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + walletAddress String + displayName String? + email String? + archivedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + payments Payment[] + + @@unique([orgId, walletAddress]) + @@index([orgId]) + /// Referenced by composite foreign keys, so a child cannot point at a + /// parent in another organization. + @@unique([orgId, id]) +} + +// ─── Escrow (on-chain custody) ──────────────────────────────────────────────── + +model Escrow { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + projectId String? + /// COMPOSITE FOREIGN KEY. Referencing (orgId, id) rather than id alone makes it + /// structurally impossible to attach this escrow to a project owned by a + /// different organization β€” a plain FK only checks the row exists. + project Project? @relation(fields: [orgId, projectId], references: [orgId, id], onDelete: NoAction) + + /// The Soroban escrow id. Null only while a creation transaction is in flight. + onChainId Int? @unique + /// Which deployment this escrow lives in. v1 Mainnet and v2 Testnet escrows + /// must never be conflated, and escrow ids collide across deployments. + contractId String + network String @default("testnet") + + managerAddress String + financeApproverAddress String + oraclePublicKey String? + + /// Aggregate custody. Per-payment amounts live on Payment. + tokenAddress String? + assetDecimals Int @default(7) + totalAmountBaseUnits BigInt @default(0) + + /// Projection of contract booleans, written only by the indexer. + managerApproved Boolean @default(false) + financeApproved Boolean @default(false) + cancelled Boolean @default(false) + oracleRotations Int @default(0) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + payments Payment[] + transactions BlockchainTransaction[] + auditEvents AuditEvent[] + + @@index([orgId]) + @@index([contractId, network]) + @@index([createdAt]) + /// Composite-FK target. + @@unique([orgId, id]) +} + +// ─── Payroll batch (the unit a human uploads and approves) ──────────────────── + +model PayrollBatch { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + projectId String? + /// Composite FK β€” see Escrow.project. + project Project? @relation(fields: [orgId, projectId], references: [orgId, id], onDelete: NoAction) + + /// Human-facing identifier, e.g. CF-00042. + reference String + + periodStart DateTime? + periodEnd DateTime? + + /// Provenance for a CSV-originated batch. + sourceFilename String? + sourceRowCount Int? + uploadedBy String? + + /// Retry key for batch CREATION, supplied by the client as `Idempotency-Key`. + /// + /// A finance user who double-clicks Create, refreshes after a gateway timeout, + /// or has the page open in two tabs must not end up with two payrolls. The + /// uniqueness is enforced by the database rather than by a read-then-write in + /// the service, because check-then-insert is a race that loses precisely under + /// the concurrency it is meant to handle. NULL is allowed and repeatable: + /// Postgres treats NULLs as distinct in a unique index, so a batch created + /// without a key is unaffected. + idempotencyKey String? + + /// SHA-256 of the semantically meaningful parts of the creation request. + /// + /// Paired with `idempotencyKey` to tell a genuine retry from a key collision. + /// A retry carries the same payload and must replay the original outcome; the + /// SAME key with a DIFFERENT payload is a client bug, and honouring it would + /// silently return a batch that is not the one the caller just asked for. That + /// is refused rather than guessed at. + idempotencyFingerprint String? + + /// SHA-256 of the uploaded file's bytes. + /// + /// NOT an idempotency key: uploading a byte-identical file next pay period is + /// legitimate, and must not be silently swallowed. It exists so the UI can say + /// "you uploaded this same file 4 minutes ago as CF-00041" and let a human + /// decide, which is the opposite of guessing on their behalf. + sourceChecksum String? + + /// NOTE: there is deliberately no aggregate `status` column. A batch's + /// standing is derived from its payments; a stored rollup would be a second + /// copy of mutable truth, free to drift from the payments it summarizes. + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + payments Payment[] + auditEvents AuditEvent[] + transactions BlockchainTransaction[] + + @@unique([orgId, reference]) + /// The constraint that makes a double-submitted payroll impossible rather than + /// unlikely. Scoped to the organization: two tenants may reuse a key. + @@unique([orgId, idempotencyKey]) + @@index([orgId]) + @@index([createdAt]) + @@index([orgId, sourceChecksum]) + /// Composite-FK target. + @@unique([orgId, id]) +} + +// ─── Payment (the atomic financial record) ──────────────────────────────────── + +/// Lifecycle of a single payment to a single payee. +/// +/// Defined in full, with valid transitions and responsible actors, in +/// docs/PAYMENT_STATE_MACHINE.md. The transition table is enforced in code by +/// src/lib/payments/state-machine.ts β€” this enum only names the states. +enum PaymentState { + /// Created, not yet validated. Editable. + DRAFT + /// Undergoing validation (address, amount, asset, hours/rate divisibility). + VALIDATING + /// Valid and funded on-chain; waiting for an oracle attestation. + AWAITING_ORACLE + /// An oracle attestation has been verified ON-CHAIN for this payment. + ORACLE_VERIFIED + /// Waiting for the manager's on-chain approval. + AWAITING_MANAGER + /// Manager approved; waiting for the distinct finance approver. + AWAITING_FINANCE + /// Both approvals present on-chain. Settlement may be submitted. + READY_TO_SETTLE + /// A settlement transaction has been built and signed, submission in flight. + SUBMITTING + /// Submitted and accepted by the network; awaiting ledger confirmation. + CONFIRMING + /// Chain-confirmed settlement. Only the indexer may set this. + PAID + + // ── Failure and recovery states, each distinct on purpose ── + /// An approver explicitly declined. + REJECTED + /// Cancelled before settlement; on-chain custody refunded. + CANCELLED + /// The pay period or attestation window lapsed without settlement. + EXPIRED + /// The transaction never reached the network (build/sign/RPC failure). + /// Safe to retry: nothing was submitted. + SUBMISSION_FAILED + /// The transaction reached the chain and FAILED there. Retry only after + /// establishing what the chain actually did. + SETTLEMENT_FAILED + /// Database and chain disagree. Requires operator resolution; never cleared + /// automatically. + RECONCILIATION_REQUIRED +} + +model Payment { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + + /// EVERY parent relation below is a COMPOSITE foreign key on (orgId, id), + /// and every one uses `onDelete: NoAction` rather than `SetNull`. + /// + /// SetNull is impossible here and was a latent defect: it nulls ALL the FK's + /// columns, and `orgId` is NOT NULL β€” so deleting an Escrow that any payment + /// referenced failed with "Null constraint violation on the fields: (orgId)". + /// Found by an integration test against real PostgreSQL; `prisma validate` had + /// been warning about it, and the unit suite could not see it. + /// + /// NoAction rather than Restrict because NO ACTION is checked at the END of the + /// statement. A cascading delete from Organization removes the parent and the + /// child in one statement, which RESTRICT would reject depending on evaluation + /// order. The effect for a direct delete is the same: an Escrow, Project or + /// Worker still referenced by a payment cannot be removed, which is correct β€” + /// detaching a payment from its context destroys the evidence of what was paid + /// for, and a financial record must outlive an attempt to tidy up around it. + /// + /// A plain `batchId` FK only guarantees the batch exists β€” not that it belongs + /// to this payment's organization. Without the composite key, a bug or a + /// crafted request could produce a payment in org A attached to a batch, escrow, + /// project or worker in org B, and no database constraint would object. The + /// tenant boundary would then depend entirely on the application remembering to + /// check, which is exactly what this phase is meant to stop relying on. + batchId String + batch PayrollBatch @relation(fields: [orgId, batchId], references: [orgId, id], onDelete: Cascade) + + escrowId String? + escrow Escrow? @relation(fields: [orgId, escrowId], references: [orgId, id], onDelete: NoAction) + + projectId String? + project Project? @relation(fields: [orgId, projectId], references: [orgId, id], onDelete: NoAction) + + workerId String? + worker Worker? @relation(fields: [orgId, workerId], references: [orgId, id], onDelete: NoAction) + + /// The payee wallet, denormalized deliberately: it is the immutable financial + /// identity of this payment. Re-pointing a Worker row must never silently + /// change who a historical payment was made to. + recipientAddress String + + /// Zero-based index of this payment within its escrow's on-chain payments + /// vector β€” the `payment_id` argument the contract takes. + onChainPaymentIndex Int? + + /// Settlement asset, and its decimals, captured at creation. + assetContractId String? + assetCode String @default("USDC") + assetDecimals Int @default(7) + + /// Money, in the asset's base units. BigInt only. + amountBaseUnits BigInt + rateBaseUnits BigInt + /// Whole hours. v2 requires `hours * rate == amount` exactly; fractional + /// hours need a versioned scaled-hours schema (v3) and are NOT rounded here. + hours BigInt + + periodStart DateTime? + periodEnd DateTime? + + /// Free text from the uploader's CSV row, e.g. "Sprint 14". + /// + /// Stored already neutralized against spreadsheet formula injection (a leading + /// apostrophe where the value began with =, +, -, @, tab or CR), because this + /// text is re-displayed and can be re-exported. Neutralizing at the ingestion + /// boundary means no later renderer or export path has to remember to. + sourceReference String? + + state PaymentState @default(DRAFT) + stateUpdatedAt DateTime @default(now()) + /// Why the payment is in a failure state, in operator-readable terms. + stateReason String? + + settledAt DateTime? + /// Hash of the confirmed settlement transaction. Set only from chain evidence. + settlementTxHash String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + approvals Approval[] + attestations OracleAttestation[] + transactions BlockchainTransaction[] + auditEvents AuditEvent[] + findings ReconciliationFinding[] + + /// One payment per on-chain slot. This is the constraint that makes indexing + /// a three-payee settlement produce exactly three rows, no matter how many + /// times the events are replayed. + @@unique([escrowId, onChainPaymentIndex]) + /// Composite-FK target for Approval, transactions, findings and audit rows. + @@unique([orgId, id]) + @@index([orgId, state]) + @@index([batchId]) + @@index([escrowId]) + @@index([recipientAddress]) + @@index([createdAt]) +} + +/// An approval decision, recorded per payment per role. +model Approval { + id String @id @default(cuid()) + /// Denormalized tenant owner. An approval reachable only by joining through + /// Payment forces every authorization check to remember the join; carrying the + /// organization makes the scope explicit and lets the composite FK enforce it. + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + paymentId String + payment Payment @relation(fields: [orgId, paymentId], references: [orgId, id], onDelete: Cascade) + + /// MANAGER or FINANCE. Stored as OrgRole so the separation-of-duties check + /// reads against the same vocabulary as membership. + role OrgRole + decision ApprovalDecision + /// Wallet that signed, as observed on-chain where applicable. + actorAddress String + reason String? + /// Transaction carrying the on-chain approval, when it had one. + txHash String? + createdAt DateTime @default(now()) + + /// One decision per role per payment. A second manager approval is a + /// duplicate, not a new fact. + @@unique([paymentId, role]) + @@index([paymentId]) + @@index([orgId]) +} + +enum ApprovalDecision { + APPROVED + REJECTED +} + +// ─── Oracle attestations ────────────────────────────────────────────────────── + +model OracleAttestation { + id String @id @default(cuid()) + /// Tenant owner. Required: an attestation unlocks settlement, so it must never + /// be reachable without an organization scope. + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + paymentId String? + payment Payment? @relation(fields: [orgId, paymentId], references: [orgId, id], onDelete: NoAction) + + /// Identity of the on-chain slot this attestation is bound to. + escrowOnChainId Int + onChainPaymentIndex Int + contractId String? + + /// Attestation schema. CFWP-v2 binds network, contract, worker, asset, + /// amount and period; v1 bound none of those. + schema String @default("CFWP-v2") + + hours BigInt + nonce BigInt + + /// sha256 of the 198-byte preimage, so an auditor can confirm what was signed + /// without the signature alone having to be trusted. + preimageSha256 String? + signature String + + /// Wallet that requested the attestation (the escrow's on-chain manager). + createdBy String + createdAt DateTime @default(now()) + + /// One attestation per (escrow, payment, nonce) β€” a nonce is single-use, so + /// signing it twice is a defect, not a retry. + @@unique([escrowOnChainId, onChainPaymentIndex, nonce]) + @@index([escrowOnChainId]) + @@index([paymentId]) + @@index([orgId]) +} + +// ─── Blockchain transactions (attempts, including retries) ──────────────────── + +enum TxKind { + INITIALIZE_ESCROW + SUBMIT_HOURS_PROOF + MANAGER_APPROVE + FINANCE_APPROVE + PAY_BATCH + CANCEL_ESCROW + ROTATE_ORACLE_KEY + EXTEND_ESCROW_TTL +} + +enum TxStatus { + PREPARING + SIMULATING + AWAITING_SIGNATURE + SUBMITTED + CONFIRMED + FAILED + EXPIRED + CANCELLED +} + +/// One row per ATTEMPT, not one per payment. A retry after an RPC timeout is a +/// new attempt against the same idempotency key; recording attempts separately +/// is what lets reconciliation discover that a "failed" submission actually +/// landed on chain. +model BlockchainTransaction { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + + paymentId String? + payment Payment? @relation(fields: [orgId, paymentId], references: [orgId, id], onDelete: NoAction) + escrowId String? + escrow Escrow? @relation(fields: [orgId, escrowId], references: [orgId, id], onDelete: NoAction) + + /// The batch this transaction acts for, where the action is batch-level. + /// + /// Funding is the case that needs it. `initialize_multi_sig_escrow` creates the + /// escrow AND pulls custody in ONE atomic invocation, so submitting it twice + /// creates two funded escrows and charges the manager twice. The contract has no + /// idempotency of its own, so "has this batch already been funded, or is a + /// funding attempt in flight?" must be answerable off-chain β€” which requires + /// knowing which batch an attempt belongs to. `escrowId` cannot serve: it is + /// null until the escrow the attempt is creating exists. + batchId String? + batch PayrollBatch? @relation(fields: [orgId, batchId], references: [orgId, id], onDelete: NoAction) + + kind TxKind + status TxStatus @default(PREPARING) + + /// Caller-supplied key that makes a mutation retry-safe. A repeated request + /// with the same key returns the original attempt instead of submitting a + /// second payment β€” the property that matters most in payroll. + idempotencyKey String @unique + + /// Attempt number under this idempotency key. + attempt Int @default(1) + + /// Null until submission. Unique when present: the same hash must never be + /// recorded twice. + hash String? @unique + ledger Int? + + resultCode String? + errorMessage String? + + contractId String? + network String @default("testnet") + + /// The IMMUTABLE plan this transaction was prepared for. + /// + /// Captured when the intent is opened, before a wallet is shown, and never + /// rewritten. Confirmation compares chain evidence against THIS, not against a + /// freshly recomputed plan: configuration can move under a pending transaction + /// (a changed settlement asset, a different finance approver, an edited payment), + /// and a recomputed plan would quietly agree with whatever the chain happened to + /// contain. Money values inside are decimal STRINGS β€” JSON has no bigint, and a + /// Number here would be the rounding this codebase refuses everywhere else. + plan Json? + + /// SHA-256 over the canonical form of `plan`. + /// + /// Lets a stored plan be proven unaltered without re-reading every field, and + /// makes any tampering with the JSON detectable rather than merely unlikely. + planDigest String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + submittedAt DateTime? + confirmedAt DateTime? + + @@index([orgId, status]) + @@index([paymentId]) + @@index([escrowId]) + @@index([batchId]) + @@index([hash]) } +// ─── Audit (append-only history) ────────────────────────────────────────────── + +/// Append-only record of what happened. Written on every state transition. +/// +/// This is the history that a "current status" column cannot provide: it carries +/// previous and new state together with the actor, so a payment's path can be +/// reconstructed rather than inferred. Nothing in the application updates or +/// deletes these rows. +model AuditEvent { + id String @id @default(cuid()) + /// REQUIRED. An audit row with no organization belongs to no tenant, which + /// means no scoped query can ever return it and no operator can ever see it β€” + /// an audit trail nobody can read is not an audit trail. + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + + /// Dotted event type, e.g. payment.state.changed, approval.granted. + type String + + /// Who acted. `actorSystem` names the non-human actor (indexer, reconciler) + /// so a machine transition is never mistaken for a person's decision. + actorAddress String? + actorUserId String? + actorSystem String? + + paymentId String? + payment Payment? @relation(fields: [orgId, paymentId], references: [orgId, id], onDelete: NoAction) + batchId String? + batch PayrollBatch? @relation(fields: [orgId, batchId], references: [orgId, id], onDelete: NoAction) + escrowId String? + escrow Escrow? @relation(fields: [orgId, escrowId], references: [orgId, id], onDelete: NoAction) + + previousState String? + newState String? + + txHash String? + + /// Additional context, as JSON, sufficient to reconstruct the transition. + metadata Json? + + createdAt DateTime @default(now()) + + @@index([orgId, createdAt]) + @@index([paymentId]) + @@index([batchId]) + @@index([escrowId]) + @@index([type]) +} + +/// Legacy flat audit log, retained so existing admin views and the bootstrap +/// path keep working. New transitions write `AuditEvent`. model AuditLog { id String @id @default(cuid()) - action String // e.g. role.grant, escrow.create, auth.logout, escrow.reject, invitation.create - actor String? // wallet address of the actor - target String? // affected entity (wallet / escrow id / email) - metadata String? // JSON blob of extra context + action String + actor String? + target String? + metadata String? createdAt DateTime @default(now()) @@index([action]) @@index([createdAt]) } -// ─── Chain Indexer ──────────────────────────────────────────────────────────── +// ─── Reconciliation ─────────────────────────────────────────────────────────── + +/// What kind of disagreement was found. +/// +/// Deliberately granular. A single "MISMATCH" value would be useless: an +/// unreadable RPC, a settled-but-unrecorded payment, and a database claiming a +/// payment that never settled demand completely different responses β€” retry, +/// catch up, and stop trusting the record respectively. Collapsing them forces an +/// operator to re-derive the distinction from free text every time. +enum FindingKind { + /// The database claims PAID; independent chain evidence does not support it. + /// The most serious finding in the system. + DB_PAID_CHAIN_NOT + /// The chain settled; the projection has not caught up. Recoverable. + CHAIN_PAID_DB_NOT + /// Settled amount differs from the recorded amount. + AMOUNT_MISMATCH + /// Settled asset differs from the recorded asset. + ASSET_MISMATCH + /// Settled recipient differs from the recorded recipient. + RECIPIENT_MISMATCH + /// A recorded payment has no corresponding on-chain slot. + MISSING_ON_CHAIN + /// An on-chain payment has no database row, within a KNOWN organization. + ORPHAN_ON_CHAIN + /// An on-chain escrow belongs to no organization. Never auto-attributed. + UNKNOWN_ON_CHAIN_OBJECT + /// A transaction recorded as failed actually succeeded on chain. + /// Dangerous: invites a retry that would double-pay. + FAILED_TX_ACTUALLY_SUCCEEDED + /// The contract reports settlement but no asset transfer was observed. + MISSING_PAYMENT_EVENT + /// More asset transfers were observed than the payment expects. + DUPLICATE_PAYMENT_EVENT + /// Chain state could not be read. NOT agreement β€” see CHAIN_UNREADABLE note. + CHAIN_UNREADABLE + /// Anything the taxonomy does not yet name. Requires a human. + OTHER +} + +/// Lifecycle of a finding. Resolution is auditable, never a silent dismissal. +enum FindingStatus { + OPEN + ACKNOWLEDGED + INVESTIGATING + RESOLVED +} + +/// How urgently a finding needs attention. +/// +/// CRITICAL is reserved for findings where the product may be making a FALSE +/// STATEMENT ABOUT MONEY β€” a payment presented as settled that the chain does not +/// support. Everything else, however annoying, is a lag or an operational issue. +enum FindingSeverity { + CRITICAL + HIGH + MEDIUM + LOW +} + +/// Outcome of comparing one object against independently verified chain facts. +enum ReconcileOutcome { + AGREED + CHAIN_AHEAD + DATABASE_AHEAD + CHAIN_UNREADABLE + MISMATCHED + UNKNOWN_ON_CHAIN_OBJECT + ORPHANED_DATABASE_OBJECT +} + +enum RunStatus { + RUNNING + COMPLETED + FAILED + /// Abandoned: the worker holding the lock stopped reporting. + STALE +} + +/// One reconciliation pass over a scope. +/// +/// Exists so an operator can answer two questions that logs cannot: "when did +/// CoreFlow last reconcile this organization?" and "did that run finish?". A +/// reconciler whose last run silently died is worse than none, because the absence +/// of findings reads as health. +model ReconciliationRun { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + + /// Correlation id threaded through every log line for this run. + correlationId String @unique + + /// What was examined, e.g. "organization", "payment:". + scope String + contractId String? + network String? + + status RunStatus @default(RUNNING) + + startedAt DateTime @default(now()) + completedAt DateTime? + /// Refreshed while running. A lock whose holder stopped updating this is stale + /// and may be taken over β€” otherwise one crashed worker blocks reconciliation + /// forever. + heartbeatAt DateTime @default(now()) + + escrowsExamined Int @default(0) + paymentsExamined Int @default(0) + agreed Int @default(0) + mismatched Int @default(0) + unreadable Int @default(0) + chainAhead Int @default(0) + databaseAhead Int @default(0) + findingsOpened Int @default(0) + correctionsApplied Int @default(0) + + errorMessage String? + + findings ReconciliationFinding[] + + @@index([orgId, startedAt]) + @@index([status]) +} + +/// A recorded disagreement between this database and the chain. +/// +/// Discrepancies are persisted rather than corrected in place. Silently +/// overwriting the losing side destroys the only evidence that the two ever +/// diverged, which is exactly what an auditor needs to see. +model ReconciliationFinding { + id String @id @default(cuid()) + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + + paymentId String? + payment Payment? @relation(fields: [orgId, paymentId], references: [orgId, id], onDelete: NoAction) + + /// The run that most recently observed this finding. + runId String? + run ReconciliationRun? @relation(fields: [runId], references: [id], onDelete: SetNull) + + kind FindingKind + status FindingStatus @default(OPEN) + severity FindingSeverity @default(MEDIUM) + + dbState String? + chainState String? + detail String? + /// What an operator should actually do. A finding without this is a puzzle. + remediation String? + metadata Json? + + /// Chain references, so an investigation starts from evidence. + txHash String? + escrowOnChainId Int? + paymentIndex Int? + + detectedAt DateTime @default(now()) + /// Updated every time a run re-observes the same unresolved finding, so "still + /// wrong as of" is answerable without counting duplicate rows. + lastObservedAt DateTime @default(now()) + observationCount Int @default(1) + + acknowledgedAt DateTime? + acknowledgedBy String? + + resolvedAt DateTime? + resolvedBy String? + resolution String? + + @@index([orgId, status]) + @@index([orgId, severity, status]) + @@index([paymentId]) + @@index([runId]) +} + +// ─── Chain indexer ──────────────────────────────────────────────────────────── model IndexerCursor { - id Int @id @default(1) // single-row table + /// One row per (contract, network): a single global cursor would conflate + /// deployments, and v1/v2 escrow ids overlap. + id String @id @default(cuid()) + contractId String + network String lastLedger Int @default(0) updatedAt DateTime @updatedAt + + @@unique([contractId, network]) } model ChainEvent { - id String @id // RPC paging token β€” globally unique, used for idempotency + /// RPC paging token β€” globally unique, and the basis of ingest idempotency. + id String @id + contractId String? + network String @default("testnet") type String ledger Int + txHash String? escrowOnChainId Int? + /// Payment slot for per-payment events, null for escrow-level ones. + paymentIndex Int? + /// Decoded payload, kept so a projection bug can be fixed by replaying the + /// stored log instead of re-reading the chain. + payload Json? + /// Whether this event could be attributed to an organization. + /// + /// The chain knows nothing about CoreFlow organizations. An escrow created + /// outside the app β€” by the CLI, a validation script, or another client β€” has no + /// tenant mapping, and GUESSING one would silently place another party's payroll + /// inside a customer's workspace. Such events are recorded with + /// `attributed = false` and surfaced for an operator to claim, never attached to + /// whichever organization happened to be convenient. + attributed Boolean @default(true) processedAt DateTime @default(now()) @@index([ledger]) -} - -model OracleAttestation { - id String @id @default(cuid()) - escrowOnChainId Int // The Soroban contract escrow ID - paymentId Int // Payment schedule index - hoursLogged Int - nonce Int // Must match the on-chain expected nonce - signature String // base64 Ed25519 signature - createdBy String // wallet address that requested the attestation - createdAt DateTime @default(now()) - - // One attestation per (escrow, payment, nonce) β€” prevents double-signing a nonce. - @@unique([escrowOnChainId, paymentId, nonce]) @@index([escrowOnChainId]) + @@index([contractId, network]) + @@index([attributed]) } model AuthChallenge { @@ -144,3 +922,56 @@ model AuthChallenge { @@index([walletAddress]) @@index([expiresAt]) } + +/// An invitation to join ONE organization with ONE role. +/// +/// `email` is unique PER ORGANIZATION, not globally. A global unique constraint +/// meant that once org A invited alice@example.com, org B could never invite her +/// at all β€” and the failure leaked the fact that some other tenant already had +/// her. Contractors working for several agencies is the normal case, not an edge. +model Invitation { + id String @id @default(cuid()) + /// REQUIRED. An invitation that does not name the organization it grants + /// access to cannot be authorized against anything. + orgId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + email String + /// The organization role this invitation grants. Required, and validated + /// server-side against what the inviter is permitted to delegate β€” an + /// invitation is the easiest place to smuggle a privilege escalation. + orgRole OrgRole @default(VIEWER) + /// Legacy platform role, retained for the pre-organization invite flow. + role Role @default(EMPLOYEE) + + /// Hash of the token, never the token itself. A leaked database dump must not + /// hand over usable invitations. + tokenHash String @unique + + expiresAt DateTime + usedAt DateTime? + /// Set when an invitation is withdrawn before acceptance. Distinct from + /// `usedAt`, so "revoked" is never mistaken for "accepted". + revokedAt DateTime? + revokedBy String? + + invitedBy String? + createdAt DateTime @default(now()) + + @@unique([orgId, email]) + @@index([tokenHash]) + @@index([orgId]) + @@index([email]) +} + +/// Legacy per-escrow time log. Superseded by Payment.hours + OracleAttestation; +/// retained so historical rows remain readable. +model TimeLog { + id Int @id @default(autoincrement()) + escrowId String? + hoursLogged Int + paymentId Int + txHash String @unique + createdAt DateTime @default(now()) + + @@index([escrowId]) +} diff --git a/scripts/check-env.mjs b/scripts/check-env.mjs new file mode 100755 index 0000000..3d180d6 --- /dev/null +++ b/scripts/check-env.mjs @@ -0,0 +1,323 @@ +#!/usr/bin/env node +/** + * Environment preflight. Fail-closed. + * + * Why this exists: on 2026-09-11 a `vercel env pull` overwrote .env / .env.local + * with the DEPLOYMENT's variables. That silently repointed local development at + * Mainnet v1 and at the production database. Nothing in the app objected, because + * every individual value was valid β€” only the combination was wrong. + * + * So this does not ask "does each variable look plausible?" It asks "is this the + * combination development is allowed to run?" and refuses otherwise. Judgements + * are made against EXPLICIT ALLOWLISTS below, not string heuristics, so adding a + * new deployment is a deliberate edit rather than an accident of pattern matching. + * + * Secrets are never read for their value and never printed. Only hostnames and + * contract addresses appear in output, both of which are public. + * + * Escape hatches are explicit, per-run, and never defaults: + * COREFLOW_ALLOW_MAINNET=1 act against Mainnet on purpose + * COREFLOW_ALLOW_REMOTE_DB=1 act against a non-local database on purpose + * COREFLOW_ALLOW_UNKNOWN_CONTRACT=1 use a contract not in the registry below + */ + +import { existsSync, readFileSync } from 'node:fs'; + +// --------------------------------------------------------------------------- +// Registry. Single source of truth for this check; mirrors docs/DEPLOYMENTS.md. +// Update this when a contract is deployed β€” that is the intended friction. +// --------------------------------------------------------------------------- + +/** CoreFlow v2, hardened, Stellar Testnet. The only contract development may use. */ +const V2_TESTNET = { + contractId: 'CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4', + tokenId: 'CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M', +}; + +/** CoreFlow v1, Stellar Mainnet. Superseded, and NOT what v2 development targets. */ +const V1_MAINNET_CONTRACT = 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW'; + +const KNOWN_CONTRACTS = new Map([ + [V2_TESTNET.contractId, { label: 'CoreFlow v2 (Testnet)', network: 'testnet' }], + [V1_MAINNET_CONTRACT, { label: 'CoreFlow v1 (Mainnet)', network: 'public' }], +]); + +/** + * Hosts that count as a local development database. An allowlist, because the + * failure being prevented is a REMOTE host arriving unnoticed β€” and a denylist + * can only ever exclude the remote hosts somebody already thought of. + */ +const LOCAL_DB_HOSTS = new Set([ + 'localhost', + '127.0.0.1', + '::1', + '0.0.0.0', + 'host.docker.internal', + 'postgres', // docker-compose service name + 'db', // docker-compose service name +]); + +/** Every variable that can carry a database connection. All must be local in dev. */ +const DB_URL_VARS = ['DATABASE_URL', 'DIRECT_URL', 'PRISMA_DATABASE_URL', 'POSTGRES_URL']; + +// --------------------------------------------------------------------------- + +function parseEnvFile(file) { + const out = {}; + if (!existsSync(file)) return out; + for (const line of readFileSync(file, 'utf8').split('\n')) { + const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim()); + if (m) out[m[1]] = m[2].trim().replace(/^["']|["']$/g, ''); + } + return out; +} + +/** Next.js precedence: .env.local overrides .env. Mirror it, then real env wins. */ +function loadEnvFiles() { + return { ...parseEnvFile('.env'), ...parseEnvFile('.env.local'), ...process.env }; +} + +const env = loadEnvFiles(); + +/** + * What a PRISMA-importing process sees. + * + * Prisma Client loads `.env` and only `.env`. So a script, test or migration that + * imports Prisma inherits `.env`'s values no matter what `.env.local` says β€” and + * `next dev` hides this, because Next loads `.env.local` into process.env first and + * dotenv never overwrites an existing variable. + * + * That asymmetry put a live Testnet funding test on Mainnet v1 while this preflight, + * which merged the files the way Next.js does, reported OK. Both views are checked + * now. + */ +const envOnly = parseEnvFile('.env'); +const errors = []; +const notes = []; +const allowMainnet = env.COREFLOW_ALLOW_MAINNET === '1'; +const allowRemoteDb = env.COREFLOW_ALLOW_REMOTE_DB === '1'; +const allowUnknownContract = env.COREFLOW_ALLOW_UNKNOWN_CONTRACT === '1'; +const isProductionBuild = process.env.NODE_ENV === 'production'; + +// --- Network --------------------------------------------------------------- +const network = (env.NEXT_PUBLIC_STELLAR_NETWORK ?? '').trim().toLowerCase(); +const isMainnet = network === 'public' || network === 'mainnet'; + +if (isMainnet && !allowMainnet) { + errors.push( + 'NEXT_PUBLIC_STELLAR_NETWORK is "' + network + '" (Mainnet).\n' + + ' v2 development runs exclusively against Testnet. The hardened v2\n' + + ' contract is NOT deployed on Mainnet; Mainnet still runs v1.\n' + + ' Fix: NEXT_PUBLIC_STELLAR_NETWORK=testnet\n' + + ' Override (deliberate Mainnet action): COREFLOW_ALLOW_MAINNET=1', + ); +} +if (!network) { + notes.push('NEXT_PUBLIC_STELLAR_NETWORK is unset; the app defaults to testnet.'); +} + +// --- Contract -------------------------------------------------------------- +const contractId = (env.NEXT_PUBLIC_STELLAR_CONTRACT_ID ?? '').trim(); +const known = KNOWN_CONTRACTS.get(contractId); + +if (!contractId) { + errors.push( + 'NEXT_PUBLIC_STELLAR_CONTRACT_ID is unset. CoreFlow will not guess a contract\n' + + ' address, so every contract call would fail at the point of use.\n' + + ' Fix: set it to ' + V2_TESTNET.contractId, + ); +} else if (known && known.network === 'public' && !allowMainnet) { + errors.push( + 'NEXT_PUBLIC_STELLAR_CONTRACT_ID is the ' + known.label + ' contract.\n' + + ' This is the live contract holding real funds, and it does NOT carry the\n' + + ' v2 security fixes. Development must not point at it.\n' + + ' Fix: set it to ' + V2_TESTNET.contractId + '\n' + + ' Override (deliberate Mainnet action): COREFLOW_ALLOW_MAINNET=1', + ); +} else if (!known && !allowUnknownContract) { + errors.push( + 'NEXT_PUBLIC_STELLAR_CONTRACT_ID is not a known deployment:\n' + + ' ' + contractId + '\n' + + ' Refusing rather than assuming which chain or contract version this is.\n' + + ' Fix: add it to KNOWN_CONTRACTS in scripts/check-env.mjs and to\n' + + ' docs/DEPLOYMENTS.md, or use ' + V2_TESTNET.contractId + '\n' + + ' Override (one-off, e.g. a scratch deployment):\n' + + ' COREFLOW_ALLOW_UNKNOWN_CONTRACT=1', + ); +} else if (known && !isMainnet && known.network !== 'testnet') { + errors.push( + 'Network is testnet but the contract is ' + known.label + '.\n' + + ' A contract address aimed at the wrong network fails every call.', + ); +} + +// --- Settlement asset ------------------------------------------------------ +const tokenId = (env.NEXT_PUBLIC_STELLAR_TOKEN_ID ?? '').trim(); +if (!isMainnet && contractId === V2_TESTNET.contractId && tokenId !== V2_TESTNET.tokenId) { + if (!tokenId) { + errors.push( + 'NEXT_PUBLIC_STELLAR_TOKEN_ID is unset. An escrow holds exactly one Stellar\n' + + ' Asset Contract; without it, funding and settlement verification cannot\n' + + ' proceed and CoreFlow will not infer a SAC address from an asset symbol.\n' + + ' Fix: set it to ' + V2_TESTNET.tokenId, + ); + } else { + errors.push( + 'NEXT_PUBLIC_STELLAR_TOKEN_ID does not match the asset the v2 Testnet\n' + + ' deployment was configured with.\n' + + ' configured: ' + tokenId + '\n' + + ' expected: ' + V2_TESTNET.tokenId + '\n' + + ' Escrows funded with a different SAC cannot be verified against the\n' + + ' transfers this deployment observes.', + ); + } +} + +// --- Databases ------------------------------------------------------------- +const dbHosts = {}; +const remote = []; +for (const name of DB_URL_VARS) { + const raw = env[name]; + if (!raw) continue; + let host; + try { + host = new URL(raw).hostname; + } catch { + errors.push(name + ' is not a parseable URL.'); + continue; + } + dbHosts[name] = host; + + // Production builds legitimately use the production database. + if (isProductionBuild) continue; + + if (!LOCAL_DB_HOSTS.has(host)) remote.push(name + ' -> ' + host); +} + +// Reported as ONE finding. A vercel env pull rewrites all four at once, and four +// copies of the same paragraph buries the instruction that fixes it. +if (remote.length > 0) { + if (allowRemoteDb) { + notes.push('Remote database targets, explicitly allowed: ' + remote.join(', ')); + } else { + errors.push( + 'These variables point at a NON-LOCAL database outside production:\n' + + remote.map((r) => ' ' + r).join('\n') + '\n' + + ' Development writes, migrations, seeds and test resets would land\n' + + ' there. If that is the deployed database, this destroys real payroll\n' + + ' records. Development requires its own local database.\n' + + ' Fix: see docs/ENVIRONMENTS.md for local Postgres setup.\n' + + ' Override (deliberate remote action): COREFLOW_ALLOW_REMOTE_DB=1', + ); + } +} +if (!env.DATABASE_URL) errors.push('DATABASE_URL is unset.'); + +// --- The .env-only view, as Prisma and any plain script see it --------------- +{ + const net = (envOnly.NEXT_PUBLIC_STELLAR_NETWORK ?? '').trim().toLowerCase(); + const contract = (envOnly.NEXT_PUBLIC_STELLAR_CONTRACT_ID ?? '').trim(); + const known = KNOWN_CONTRACTS.get(contract); + const mainnetThere = + net === 'public' || net === 'mainnet' || (known && known.network === 'public'); + + if (mainnetThere && !allowMainnet) { + errors.push( + '.env itself still names Mainnet, even if .env.local does not:\n' + + ' NEXT_PUBLIC_STELLAR_NETWORK=' + (net || '(unset)') + '\n' + + ' NEXT_PUBLIC_STELLAR_CONTRACT_ID=' + (contract || '(unset)') + '\n' + + ' Prisma Client loads .env and ONLY .env, so every script, test and\n' + + ' migration that imports Prisma would act against Mainnet β€” while the app\n' + + ' itself looks correct, because Next.js loads .env.local first.\n' + + ' Fix: correct these values in .env, not only in .env.local.', + ); + } + + for (const name of DB_URL_VARS) { + const raw = envOnly[name]; + if (!raw || isProductionBuild) continue; + let host; + try { + host = new URL(raw).hostname; + } catch { + continue; + } + if (!LOCAL_DB_HOSTS.has(host) && !allowRemoteDb) { + errors.push( + '.env itself points ' + name + ' at a non-local host (' + host + ').\n' + + ' Prisma reads .env directly, so migrations and database tests would use\n' + + ' it regardless of .env.local.', + ); + } + } +} + +// --- The combination invariant ---------------------------------------------- +// +// Each variable can be individually valid while the COMBINATION is wrong, and the +// combination is what decides whether a mistake costs money. Only two are allowed: +// +// LOCAL local database + Testnet + CoreFlow v2 +// PRODUCTION production database + Mainnet + CoreFlow v1 +// +// Anything else is mixed, and mixed is how local development came to be pointed at +// the production database and Mainnet v1 on 2026-09-11. +{ + const dbIsLocal = Object.keys(dbHosts).length > 0 && + Object.values(dbHosts).every((h) => LOCAL_DB_HOSTS.has(h)); + const contractVersion = known + ? known.network === 'public' ? 'v1' : 'v2' + : null; + + const profile = + dbIsLocal && !isMainnet && contractVersion === 'v2' + ? 'LOCAL' + : !dbIsLocal && isMainnet && contractVersion === 'v1' + ? 'PRODUCTION' + : 'MIXED'; + + if (profile === 'MIXED') { + const overridden = allowMainnet || allowRemoteDb || allowUnknownContract; + // An explicit override means the operator said they meant it, so this becomes a + // loud note rather than a refusal. Without one it is an error β€” and it must be + // reachable in both cases, or it is not a control at all. + (overridden ? notes : errors).push( + 'This is neither a valid LOCAL nor a valid PRODUCTION environment:\n' + + ' database: ' + (dbIsLocal ? 'local' : 'non-local') + '\n' + + ' network: ' + (isMainnet ? 'Mainnet' : 'Testnet') + '\n' + + ' contract: ' + (contractVersion ?? 'unrecognized') + '\n' + + ' Only two combinations are permitted:\n' + + ' LOCAL local database + Testnet + CoreFlow v2\n' + + ' PRODUCTION production database + Mainnet + CoreFlow v1\n' + + ' A mixed environment is how development came to be pointed at the\n' + + ' production database and Mainnet v1.', + ); + } + + // Surfaced in the report so the profile is visible even when it is valid. + globalThis.__coreflowProfile = profile; +} + +// --- Report ---------------------------------------------------------------- +console.log('CoreFlow env preflight'); +console.log(' profile: ' + (globalThis.__coreflowProfile ?? 'unknown')); +console.log(' network: ' + (isMainnet ? 'MAINNET' : network || 'testnet (default)')); +console.log(' contract: ' + (contractId || '(unset)') + (known ? ' [' + known.label + ']' : '')); +console.log(' asset: ' + (tokenId || '(unset)')); +for (const name of DB_URL_VARS) { + if (dbHosts[name]) { + const local = LOCAL_DB_HOSTS.has(dbHosts[name]); + console.log(' ' + name + ': ' + dbHosts[name] + (local ? ' [local]' : ' [REMOTE]')); + } +} + +for (const n of notes) console.log('\n note: ' + n); + +if (errors.length > 0) { + console.error('\nRefusing to continue:\n'); + for (const e of errors) console.error(' - ' + e + '\n'); + process.exit(1); +} +console.log('\nOK: ' + (globalThis.__coreflowProfile === 'PRODUCTION' + ? 'production database + Mainnet v1.' + : 'local database + Testnet v2.')); diff --git a/scripts/deploy-testnet.sh b/scripts/deploy-testnet.sh new file mode 100755 index 0000000..ab3f04c --- /dev/null +++ b/scripts/deploy-testnet.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# +# Deploy CoreFlow v2 to Stellar TESTNET. +# +# This script is deliberately testnet-only. CoreFlow v1 remains deployed on +# Mainnet and is NOT touched, repointed, or upgraded by anything here β€” v2's +# security improvements are not on Mainnet, and nothing in this repo should +# imply otherwise. +# +# The script refuses to proceed unless the WASM is built with COREFLOW_ADMIN +# pinned. An unpinned build is vulnerable to `init_admin` front-running: deploy +# and initialize cannot share a transaction (Stellar allows one Soroban +# operation per transaction), so anyone watching the ledger can claim admin in +# between and then upgrade the contract to code that drains every escrow. +# +# Usage: +# ADMIN_IDENTITY=coreflow-v2-admin ORACLE_PUBKEY=<64 hex> ./scripts/deploy-testnet.sh +# +set -euo pipefail + +NETWORK="testnet" +NETWORK_PASSPHRASE="Test SDF Network ; September 2015" +CONTRACT_DIR="contracts/core-flow" +WASM="$CONTRACT_DIR/target/wasm32v1-none/release/core_flow.wasm" +OUT_DIR="docs/evidence" +OUT="$OUT_DIR/testnet-v2-deployment.json" + +ADMIN_IDENTITY="${ADMIN_IDENTITY:-coreflow-v2-admin}" +ORACLE_PUBKEY="${ORACLE_PUBKEY:-}" + +log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; } +die() { printf '\n\033[31mERROR: %s\033[0m\n' "$*" >&2; exit 1; } + +command -v stellar >/dev/null || die "stellar CLI not found. See https://developers.stellar.org/docs/tools/cli" +[ -n "$ORACLE_PUBKEY" ] || die "ORACLE_PUBKEY is required (64 hex chars). Get it with: node scripts/oracle-cli.mjs pubkey" +[[ "$ORACLE_PUBKEY" =~ ^[0-9a-fA-F]{64}$ ]] || die "ORACLE_PUBKEY must be exactly 64 hex characters." + +ADMIN_ADDRESS="$(stellar keys address "$ADMIN_IDENTITY")" +[[ "$ADMIN_ADDRESS" =~ ^G[A-Z2-7]{55}$ ]] || die "Could not resolve identity '$ADMIN_IDENTITY' to a G-address." + +log "CoreFlow v2 β†’ Stellar Testnet" +echo " admin identity : $ADMIN_IDENTITY" +echo " admin address : $ADMIN_ADDRESS" +echo " oracle pubkey : $ORACLE_PUBKEY" + +# ── 1. Build with the admin pinned into the WASM ──────────────────────────── +log "Building WASM with COREFLOW_ADMIN pinned" +( + cd "$CONTRACT_DIR" + COREFLOW_ADMIN="$ADMIN_ADDRESS" cargo build --release --target wasm32v1-none +) +[ -f "$WASM" ] || die "Build produced no WASM at $WASM" + +# The pin must be physically present in the binary. Building with the env var +# set is not evidence that the compiler used it β€” a cached artifact from an +# unpinned build would look identical here otherwise. +if ! grep -qa "$ADMIN_ADDRESS" "$WASM"; then + die "COREFLOW_ADMIN is not baked into the WASM. Refusing to deploy an unpinned build. +Try: (cd $CONTRACT_DIR && cargo clean) and re-run." +fi +echo " verified: admin pin present in WASM" + +WASM_SHA256="$(sha256sum "$WASM" | cut -d' ' -f1)" +WASM_BYTES="$(stat -c%s "$WASM")" +echo " wasm sha256 : $WASM_SHA256" +echo " wasm size : $WASM_BYTES bytes" + +# ── 2. Deploy ─────────────────────────────────────────────────────────────── +log "Deploying to $NETWORK" +CONTRACT_ID="$(stellar contract deploy \ + --wasm "$WASM" \ + --source "$ADMIN_IDENTITY" \ + --network "$NETWORK" 2>/dev/null | tail -1)" +[[ "$CONTRACT_ID" =~ ^C[A-Z2-7]{55}$ ]] || die "Deploy did not return a contract id (got: '$CONTRACT_ID')" +echo " contract id : $CONTRACT_ID" + +inv() { stellar contract invoke --id "$CONTRACT_ID" --source "$ADMIN_IDENTITY" --network "$NETWORK" -- "$@" 2>/dev/null; } + +# ── 3. Claim admin ────────────────────────────────────────────────────────── +# The pin makes this race-proof: any other address calling init_admin first is +# rejected with AdminMismatch (#20), so there is nothing to win by front-running. +log "Initializing admin" +inv init_admin --admin "$ADMIN_ADDRESS" >/dev/null +echo " init_admin done" + +# ── 4. Register the oracle key ────────────────────────────────────────────── +log "Registering oracle signing key" +inv register_oracle_key --pubkey "$ORACLE_PUBKEY" >/dev/null +echo " register_oracle_key done" + +# ── 5. Verify the deployed state, rather than assuming the calls worked ───── +log "Verifying deployed state" +GOT_EXPECTED_ADMIN="$(inv expected_admin | tr -d '"')" +GOT_ADMIN="$(inv get_admin | tr -d '"')" +GOT_PAUSED="$(inv is_paused)" +GOT_ORACLE="$(inv is_oracle_key_registered --pubkey "$ORACLE_PUBKEY")" + +echo " expected_admin (build pin) : $GOT_EXPECTED_ADMIN" +echo " get_admin (on-chain) : $GOT_ADMIN" +echo " is_paused : $GOT_PAUSED" +echo " oracle key registered : $GOT_ORACLE" + +[ "$GOT_EXPECTED_ADMIN" = "$ADMIN_ADDRESS" ] || die "WASM admin pin does not match the deploying admin." +[ "$GOT_ADMIN" = "$ADMIN_ADDRESS" ] || die "On-chain admin is not the expected address." +[ "$GOT_PAUSED" = "false" ] || die "Contract deployed in a paused state." +[ "$GOT_ORACLE" = "true" ] || die "Oracle key is not registered." + +# ── 6. Record the deployment ──────────────────────────────────────────────── +mkdir -p "$OUT_DIR" +cat > "$OUT" <&2; exit 1; } + +running() { pg_ctl -D "$PGDATA" status >/dev/null 2>&1; } + +cmd_init() { + [ -d "$PGDATA" ] && die "$PGDATA already exists. Use 'start', or 'destroy' first." + command -v initdb >/dev/null || die "initdb not found." + + mkdir -p "$(dirname "$PGDATA")" + + # Generated locally, never echoed, never committed. The pwfile is removed + # immediately after initdb consumes it. + local pwfile + pwfile="$(mktemp)" + chmod 600 "$pwfile" + node -e "process.stdout.write(require('crypto').randomBytes(24).toString('base64url'))" > "$pwfile" + + echo "Creating cluster at $PGDATA ..." + initdb -D "$PGDATA" \ + --username="$PGUSER_DEV" \ + --auth-local=scram-sha-256 \ + --auth-host=scram-sha-256 \ + --pwfile="$pwfile" \ + --encoding=UTF8 \ + --no-instructions >/dev/null + + # Listen on loopback only. A development database must not be reachable from the + # network, whatever else is misconfigured. + { + echo "port = $PGPORT" + echo "listen_addresses = '127.0.0.1'" + echo "unix_socket_directories = '$PGDATA'" + # Small: this is one developer's machine, not a server. + echo "max_connections = 50" + echo "shared_buffers = 128MB" + echo "fsync = off" # development only; speeds up test resets + echo "full_page_writes = off" # development only + } >> "$PGDATA/postgresql.conf" + + cmd_start + + local password url_dev url_shadow + password="$(cat "$pwfile")" + rm -f "$pwfile" + + PGPASSWORD="$password" createdb -h 127.0.0.1 -p "$PGPORT" -U "$PGUSER_DEV" "$DB_DEV" + PGPASSWORD="$password" createdb -h 127.0.0.1 -p "$PGPORT" -U "$PGUSER_DEV" "$DB_SHADOW" + echo "Created databases $DB_DEV and $DB_SHADOW." + + url_dev="postgresql://$PGUSER_DEV:$password@127.0.0.1:$PGPORT/$DB_DEV?schema=public" + url_shadow="postgresql://$PGUSER_DEV:$password@127.0.0.1:$PGPORT/$DB_SHADOW?schema=public" + + [ -f "$ENV_FILE" ] || touch "$ENV_FILE" + # Rewrite only the database keys; every other line, including every secret, is + # left exactly as it was. PRISMA_DATABASE_URL and POSTGRES_URL are deployment + # artifacts a `vercel env pull` leaves behind β€” they are commented out rather + # than deleted, so nothing is silently lost. + python3 - "$ENV_FILE" "$url_dev" "$url_shadow" <<'PYEOF' +import re, sys +path, url_dev, url_shadow = sys.argv[1], sys.argv[2], sys.argv[3] +with open(path) as f: + lines = f.read().splitlines() + +targets = {'DATABASE_URL': url_dev, 'DIRECT_URL': url_dev, 'SHADOW_DATABASE_URL': url_shadow} +neutralize = {'PRISMA_DATABASE_URL', 'POSTGRES_URL'} +out, seen = [], set() +for line in lines: + m = re.match(r'^([A-Z_][A-Z0-9_]*)=', line) + key = m.group(1) if m else None + if key in targets: + out.append('%s="%s"' % (key, targets[key])) + seen.add(key) + elif key in neutralize: + out.append('# Deployment artifact from `vercel env pull`; not used locally.') + out.append('# ' + line) + else: + out.append(line) +for key, url in targets.items(): + if key not in seen: + out.append('%s="%s"' % (key, url)) +with open(path, 'w') as f: + f.write('\n'.join(out) + '\n') +print('Wrote DATABASE_URL, DIRECT_URL and SHADOW_DATABASE_URL to ' + path) +PYEOF + + echo + echo "Done. The password was generated locally and written only to $ENV_FILE" + echo "(git-ignored). It was not printed." + echo + echo "Next: npm run check:env && npm run db:deploy" +} + +cmd_start() { + [ -d "$PGDATA" ] || die "no cluster at $PGDATA. Run 'init' first." + if running; then echo "Already running on port $PGPORT."; return; fi + pg_ctl -D "$PGDATA" -l "$LOGFILE" start >/dev/null + for _ in $(seq 1 30); do + running && break + sleep 0.2 + done + running || die "failed to start; see $LOGFILE" + echo "PostgreSQL running on 127.0.0.1:$PGPORT (data: $PGDATA)" +} + +cmd_stop() { + running || { echo "Not running."; return; } + pg_ctl -D "$PGDATA" stop -m fast >/dev/null + echo "Stopped." +} + +cmd_status() { + if running; then + echo "running on 127.0.0.1:$PGPORT" + pg_ctl -D "$PGDATA" status | head -2 + else + echo "not running (data: $PGDATA)" + fi +} + +cmd_psql() { + running || die "not running. Run 'start' first." + # Reads the URL from .env.local so the password never appears in a command. + local url + url="$(grep -m1 '^DATABASE_URL=' "$ENV_FILE" | sed 's/^DATABASE_URL=//' | tr -d '"')" + [ -n "$url" ] || die "no DATABASE_URL in $ENV_FILE" + psql "$url" +} + +cmd_destroy() { + running && pg_ctl -D "$PGDATA" stop -m immediate >/dev/null || true + [ -d "$PGDATA" ] || { echo "Nothing to remove."; return; } + rm -rf "$PGDATA" "$LOGFILE" + echo "Removed $PGDATA. Database keys in $ENV_FILE now point at nothing." +} + +case "${1:-}" in + init) cmd_init ;; + start) cmd_start ;; + stop) cmd_stop ;; + status) cmd_status ;; + psql) cmd_psql ;; + destroy) cmd_destroy ;; + *) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;; +esac diff --git a/scripts/oracle-cli.mjs b/scripts/oracle-cli.mjs index d6177dd..ee27d72 100755 --- a/scripts/oracle-cli.mjs +++ b/scripts/oracle-cli.mjs @@ -2,39 +2,57 @@ /** * CoreFlow oracle signing CLI (Deliverable 2). * - * Signs work-hours attestations that `submit_hours_proof` will accept on-chain. + * Signs work attestations that `submit_hours_proof` will accept on-chain. * - * IMPORTANT β€” message shape: + * ── Message shape (schema v2, 198 bytes) ──────────────────────────────────── * The contract does NOT verify a signature over a `{payees, nonce}` JSON blob. - * It reconstructs an exact 32-byte message per PAYMENT and verifies against it: + * It reconstructs an exact preimage PER PAYMENT, from its own stored state, and + * verifies against that: * - * escrow_id u32 4 bytes BE - * payment_id u32 4 bytes BE - * hours i128 16 bytes BE (two's complement) - * nonce u64 8 bytes BE + * magic "CFWP" 4 | version u16 2 | network_id 32 | contract 32 | + * worker 32 | token 32 | escrow_id u32 4 | payment_id u32 4 | + * amount i128 16 | hours i128 16 | start u64 8 | end u64 8 | nonce u64 8 * - * So a batch input yields ONE SIGNATURE PER PAYEE, each with its own nonce, - * consumed in ascending order β€” the contract's nonce watermark increments by - * one per accepted proof. Signing a batch blob would produce signatures the - * contract rejects every time. + * Every field but hours and nonce comes from the on-chain payment row, which is + * why this CLI needs the escrow's worker/token/amount/period as input: it is + * reproducing what the contract will build, not describing what you want paid. + * + * A batch yields ONE SIGNATURE PER PAYEE, each with its own nonce, consumed in + * ascending order β€” the contract's watermark increments by one per accepted + * proof. The contract also enforces `hours x rate_per_hour == amount`. * * Keep in sync with: - * contracts/core-flow/src/lib.rs (submit_hours_proof) + * contracts/core-flow/src/lib.rs (build_proof_message) * src/lib/oracle/index.ts (buildProofMessage) - * The vitest in src/lib/oracle/__tests__ asserts this encoding matches. + * All three are pinned to the shared vector in docs/evidence/proof-vector-v2.json. + * The contract also exposes `proof_preimage` for signers that prefer to read the + * bytes rather than rebuild them. * * Usage: * ORACLE_SECRET_KEY=<64 hex chars> node scripts/oracle-cli.mjs sign batch.json * ORACLE_SECRET_KEY=... node scripts/oracle-cli.mjs sign - # stdin + * ORACLE_SECRET_KEY=... node scripts/oracle-cli.mjs verify batch.json signed.json * ORACLE_SECRET_KEY=... node scripts/oracle-cli.mjs pubkey * * batch.json: - * { "escrowId": 1, "startNonce": 0, - * "payees": [ { "paymentId": 0, "hours": 40 }, - * { "paymentId": 1, "hours": 32 } ] } + * { + * "networkPassphrase": "Test SDF Network ; September 2015", + * "contractId": "C...", + * "escrowId": 1, + * "startNonce": 0, + * "payees": [ + * { "paymentId": 0, "worker": "G...", "token": "C...", + * "amount": "10000", "hours": 40, "startDate": 1000, "endDate": 2000 } + * ] + * } */ import { readFileSync } from 'node:fs'; -import { Keypair } from '@stellar/stellar-sdk'; +import { createHash } from 'node:crypto'; +import { Keypair, nativeToScVal } from '@stellar/stellar-sdk'; + +const PROOF_MAGIC = Buffer.from('CFWP', 'ascii'); +const PROOF_VERSION = 2; +const PROOF_MESSAGE_BYTES = 198; function loadKeypair() { const seed = process.env.ORACLE_SECRET_KEY; @@ -46,6 +64,7 @@ function loadKeypair() { return Keypair.fromRawEd25519Seed(Buffer.from(seed, 'hex')); } +function u16be(n) { const b = Buffer.alloc(2); b.writeUInt16BE(n); return b; } function u32be(n) { const b = Buffer.alloc(4); b.writeUInt32BE(n >>> 0); return b; } function u64be(n) { const b = Buffer.alloc(8); b.writeBigUInt64BE(BigInt(n)); return b; } function i128be(n) { @@ -55,51 +74,143 @@ function i128be(n) { return b; } -/** The exact 32 bytes the contract reconstructs and verifies. */ -function buildProofMessage(escrowId, paymentId, hours, nonce) { - return Buffer.concat([u32be(escrowId), u32be(paymentId), i128be(hours), u64be(nonce)]); +/** sha256(network passphrase) β€” what Soroban gives a contract as network_id(). */ +const networkId = (passphrase) => createHash('sha256').update(passphrase, 'utf8').digest(); + +/** sha256(ScVal XDR of the address) β€” matches the contract's addr_digest. */ +const addressDigest = (address) => + createHash('sha256').update(nativeToScVal(address, { type: 'address' }).toXDR()).digest(); + +/** The exact 198 bytes the contract reconstructs and verifies. */ +function buildProofMessage(ctx, escrowId, paymentId, hours, nonce) { + const msg = Buffer.concat([ + PROOF_MAGIC, + u16be(PROOF_VERSION), + networkId(ctx.networkPassphrase), + addressDigest(ctx.contractId), + addressDigest(ctx.worker), + addressDigest(ctx.token), + u32be(escrowId), + u32be(paymentId), + i128be(ctx.amount), + i128be(hours), + u64be(ctx.startDate), + u64be(ctx.endDate), + u64be(nonce), + ]); + if (msg.length !== PROOF_MESSAGE_BYTES) { + throw new Error(`Preimage must be ${PROOF_MESSAGE_BYTES} bytes, built ${msg.length}.`); + } + return msg; } function readPayload(path) { const raw = path === '-' ? readFileSync(0, 'utf8') : readFileSync(path, 'utf8'); const p = JSON.parse(raw); if (!Number.isInteger(p.escrowId)) throw new Error('escrowId must be an integer'); - if (!Array.isArray(p.payees) || p.payees.length === 0) throw new Error('payees must be a non-empty array'); + if (typeof p.networkPassphrase !== 'string' || !p.networkPassphrase) { + throw new Error('networkPassphrase is required β€” it binds the proof to Testnet or Mainnet'); + } + if (typeof p.contractId !== 'string' || !p.contractId) { + throw new Error('contractId is required β€” it binds the proof to this deployment'); + } + if (!Array.isArray(p.payees) || p.payees.length === 0) { + throw new Error('payees must be a non-empty array'); + } + for (const [i, payee] of p.payees.entries()) { + for (const f of ['worker', 'token', 'amount', 'hours', 'startDate', 'endDate']) { + if (payee[f] === undefined) throw new Error(`payees[${i}] is missing "${f}"`); + } + } return p; } -const [cmd, arg] = process.argv.slice(2); - -if (cmd === 'pubkey') { - // Hex, because the contract stores oracle_pubkey as BytesN<32>. - console.log(loadKeypair().rawPublicKey().toString('hex')); -} else if (cmd === 'sign') { - if (!arg) { console.error('usage: oracle-cli.mjs sign '); process.exit(1); } - const kp = loadKeypair(); - const { escrowId, startNonce = 0, payees } = readPayload(arg); - - // Nonces are sequential from startNonce: the contract accepts exactly the - // next expected value, so proofs must be submitted in this order. - const signatures = payees.map((payee, i) => { +/** One signature per payee, nonces sequential from startNonce. */ +function signBatch(kp, payload) { + const { escrowId, startNonce = 0, networkPassphrase, contractId, payees } = payload; + return payees.map((payee, i) => { const nonce = Number(startNonce) + i; - const msg = buildProofMessage(escrowId, payee.paymentId, payee.hours, nonce); + const paymentId = payee.paymentId ?? i; + const ctx = { + networkPassphrase, + contractId, + worker: payee.worker, + token: payee.token, + amount: BigInt(payee.amount), + startDate: BigInt(payee.startDate), + endDate: BigInt(payee.endDate), + }; + const msg = buildProofMessage(ctx, escrowId, paymentId, BigInt(payee.hours), BigInt(nonce)); return { - paymentId: payee.paymentId, + paymentId, + worker: payee.worker, hours: payee.hours, nonce, + messageSha256: createHash('sha256').update(msg).digest('hex'), message: msg.toString('hex'), signature: kp.sign(msg).toString('base64'), }; }); +} + +const [cmd, arg, arg2] = process.argv.slice(2); +if (cmd === 'pubkey') { + // Hex, because the contract stores oracle_pubkey as BytesN<32>. + console.log(loadKeypair().rawPublicKey().toString('hex')); +} else if (cmd === 'sign') { + if (!arg) { console.error('usage: oracle-cli.mjs sign '); process.exit(1); } + const kp = loadKeypair(); + const payload = readPayload(arg); console.log(JSON.stringify({ - escrowId, + schema: 'CFWP-v2', + escrowId: payload.escrowId, + networkPassphrase: payload.networkPassphrase, + contractId: payload.contractId, oraclePublicKey: kp.rawPublicKey().toString('hex'), - signatures, + signatures: signBatch(kp, payload), }, null, 2)); +} else if (cmd === 'verify') { + // Local verification + replay demonstration, so an operator can confirm a + // batch before broadcasting rather than discovering a mismatch on-chain. + if (!arg) { console.error('usage: oracle-cli.mjs verify [signed.json]'); process.exit(1); } + const kp = loadKeypair(); + const payload = readPayload(arg); + const expected = signBatch(kp, payload); + const actual = arg2 + ? JSON.parse(readFileSync(arg2, 'utf8')).signatures + : expected; + + let ok = true; + for (const [i, sig] of expected.entries()) { + const got = actual[i]; + const match = got && got.signature === sig.signature; + if (!match) ok = false; + console.log(`payment ${sig.paymentId} nonce ${sig.nonce} ${match ? 'VALID' : 'MISMATCH'}`); + } + + // Replay check: the same payload signed at the NEXT nonce must differ. If it + // did not, the nonce would not be part of the preimage and every signature + // would be reusable forever. + const replay = signBatch(kp, { ...payload, startNonce: Number(payload.startNonce ?? 0) + 1 }); + const nonceBinds = replay[0].signature !== expected[0].signature; + console.log(`replay protection: nonce ${nonceBinds ? 'IS' : 'IS NOT'} bound into the signature`); + if (!nonceBinds) ok = false; + + // Domain check: the same payload on the other network must differ. + const otherNetwork = payload.networkPassphrase.includes('Test') + ? 'Public Global Stellar Network ; September 2015' + : 'Test SDF Network ; September 2015'; + const crossNet = signBatch(kp, { ...payload, networkPassphrase: otherNetwork }); + const netBinds = crossNet[0].signature !== expected[0].signature; + console.log(`domain separation: network ${netBinds ? 'IS' : 'IS NOT'} bound into the signature`); + if (!netBinds) ok = false; + + process.exit(ok ? 0 : 1); } else { - console.error('CoreFlow oracle CLI'); - console.error(' sign emit one Ed25519 signature per payee'); - console.error(' pubkey print the oracle public key (hex)'); + console.error('CoreFlow oracle CLI (schema CFWP-v2)'); + console.error(' sign emit one Ed25519 signature per payee'); + console.error(' verify [signed.json] verify locally + prove replay/domain binding'); + console.error(' pubkey print the oracle public key (hex)'); process.exit(1); } diff --git a/scripts/oracle-key-transition.mjs b/scripts/oracle-key-transition.mjs new file mode 100755 index 0000000..311045c --- /dev/null +++ b/scripts/oracle-key-transition.mjs @@ -0,0 +1,193 @@ +#!/usr/bin/env node +/** + * Oracle registry transition: register a new key, verify, revoke the old, verify. + * + * NOT RUNNABLE BY ACCIDENT. It refuses unless BOTH public keys are named on the + * command line and `--confirm-mapping` is passed, because the one fact this cannot + * determine for itself is which key is the post-rotation one. Naming suggests an + * answer; naming is not evidence. Registering the wrong key would authorize a + * credential an attacker may hold to sign work attestations β€” exactly what the + * admin-managed registry exists to prevent. + * + * node scripts/oracle-key-transition.mjs \ + * --new <64-hex public key> \ + * --old <64-hex public key> \ + * --confirm-mapping + * + * Add --dry-run to perform only the read-only checks. + * + * Order is load-bearing: register BEFORE revoke. Revoking first would leave the + * contract with no registered key at all, stranding every escrow awaiting an + * attestation. + * + * Verification reads CHAIN STATE, never the CLI exit code. Only public values are + * printed: network, contract, admin address, oracle public keys, operation. + */ + +import { execFileSync } from 'node:child_process'; +import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs'; + +const args = process.argv.slice(2); +const flag = (name) => args.includes(`--${name}`); +const value = (name) => { + const i = args.indexOf(`--${name}`); + return i >= 0 ? args[i + 1] : null; +}; + +const NEW_KEY = value('new'); +const OLD_KEY = value('old'); +const DRY_RUN = flag('dry-run'); +const CONFIRMED = flag('confirm-mapping'); +const NETWORK = process.env.NETWORK || 'testnet'; +const IDENTITY = process.env.ADMIN_IDENTITY || 'coreflow-v2-admin'; +const HEX64 = /^[0-9a-f]{64}$/; + +function die(message) { + console.error(`\nrefusing to continue: ${message}\n`); + process.exit(1); +} + +if (!HEX64.test(NEW_KEY ?? '')) die('--new must be a 64-character lower-case hex public key.'); +if (!HEX64.test(OLD_KEY ?? '')) die('--old must be a 64-character lower-case hex public key.'); +if (NEW_KEY === OLD_KEY) die('--new and --old are the same key.'); +if (!CONFIRMED) { + die( + 'the key mapping must be confirmed explicitly with --confirm-mapping.\n' + + ' Which key is post-rotation cannot be determined from here, and registering\n' + + ' the wrong one would re-authorize a possibly exposed credential.', + ); +} + +const contractId = (process.env.NEXT_PUBLIC_STELLAR_CONTRACT_ID || readEnvFile().NEXT_PUBLIC_STELLAR_CONTRACT_ID || '').trim(); +if (!contractId) die('NEXT_PUBLIC_STELLAR_CONTRACT_ID is not set.'); + +function readEnvFile() { + const out = {}; + if (!existsSync('.env')) return out; + for (const line of readFileSync('.env', 'utf8').split('\n')) { + const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim()); + if (m) out[m[1]] = m[2].trim().replace(/^["']|["']$/g, ''); + } + return out; +} + +function cli(argv) { + const command = `stellar ${argv.map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(' ')} 2>&1`; + try { + return execFileSync('bash', ['-c', command], { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 }); + } catch (e) { + throw new Error(`stellar CLI failed:\n${`${e.stdout ?? ''}${e.stderr ?? ''}`.trim()}`); + } +} + +const invoke = (fn, extra = [], send = true) => + cli([ + 'contract', 'invoke', '--id', contractId, '--source', IDENTITY, '--network', NETWORK, + ...(send ? [] : ['--send=no']), '--', fn, ...extra, + ]); + +const isRegistered = (pubkey) => + invoke('is_oracle_key_registered', ['--pubkey', pubkey], false).trim().endsWith('true'); + +const hashOf = (output) => output.match(/tx\/([0-9a-f]{64})/)?.[1] ?? null; + +const adminAddress = cli(['keys', 'address', IDENTITY]).trim(); + +console.log('Oracle registry transition'); +console.log(` network: Stellar ${NETWORK}`); +console.log(` contract: ${contractId}`); +console.log(` admin: ${adminAddress}`); +console.log(` old oracle: ${OLD_KEY}`); +console.log(` new oracle: ${NEW_KEY}`); +console.log(` mode: ${DRY_RUN ? 'DRY RUN (read-only)' : 'EXECUTE'}`); + +// --- 0. The signer must BE the on-chain admin ------------------------------- +const onChainAdmin = invoke('get_admin', [], false).trim().replace(/[^G-Z2-7]/g, ''); +if (!onChainAdmin.includes(adminAddress)) { + die( + `the signing identity is not the contract admin.\n` + + ` signing as: ${adminAddress}\n` + + ` get_admin: ${onChainAdmin}`, + ); +} +console.log('\n get_admin matches the signing identity.'); + +const before = { new: isRegistered(NEW_KEY), old: isRegistered(OLD_KEY) }; +console.log(` before: new=${before.new ? 'registered' : 'not registered'}` + + ` old=${before.old ? 'registered' : 'not registered'}`); + +const record = { + title: 'Oracle registry transition', + network: `Stellar ${NETWORK}`, + contract: contractId, + admin: adminAddress, + oldOracleKey: OLD_KEY, + newOracleKey: NEW_KEY, + before, + executedAt: new Date().toISOString(), +}; + +if (DRY_RUN) { + console.log('\nDry run complete. No transaction sent.'); + process.exit(0); +} + +// --- 1. Register the new key FIRST ----------------------------------------- +if (before.new) { + console.log('\n new key is already registered; skipping registration.'); + record.registrationTx = null; +} else { + console.log('\n registering the new oracle key...'); + record.registrationTx = hashOf(invoke('register_oracle_key', ['--pubkey', NEW_KEY])); + console.log(` registration tx: ${record.registrationTx ?? '(hash not parsed)'}`); +} + +// --- 2. Verify from chain state, not the exit code ------------------------- +record.afterRegistration = { new: isRegistered(NEW_KEY), old: isRegistered(OLD_KEY) }; +console.log(` after registration: new=${record.afterRegistration.new}` + + ` old=${record.afterRegistration.old}`); + +if (!record.afterRegistration.new) { + writeRecord(); + die( + 'the new key is NOT registered after the registration attempt.\n' + + ' STOPPING. The old key has deliberately NOT been revoked: revoking now\n' + + ' would leave the contract with no usable oracle.', + ); +} + +// --- 3. Revoke the old key ------------------------------------------------- +if (!record.afterRegistration.old) { + console.log('\n old key is already not registered; skipping revocation.'); + record.revocationTx = null; +} else { + console.log('\n revoking the old oracle key...'); + record.revocationTx = hashOf(invoke('revoke_oracle_key', ['--pubkey', OLD_KEY])); + console.log(` revocation tx: ${record.revocationTx ?? '(hash not parsed)'}`); +} + +// --- 4. Verify the final registry state ------------------------------------ +record.afterRevocation = { new: isRegistered(NEW_KEY), old: isRegistered(OLD_KEY) }; +console.log(` after revocation: new=${record.afterRevocation.new}` + + ` old=${record.afterRevocation.old}`); + +const expected = record.afterRevocation.new === true && record.afterRevocation.old === false; +record.finalStateAsExpected = expected; +writeRecord(); + +if (!expected) { + die( + 'the final registry state does not match the expected state.\n' + + ` expected: new=registered, old=not registered\n` + + ` actual: new=${record.afterRevocation.new}, old=${record.afterRevocation.old}\n` + + ' STOPPING without a corrective transaction. Report this state for review.', + ); +} + +console.log('\nTransition complete and verified against chain state.'); +console.log('Evidence: docs/evidence/oracle-key-transition.json'); + +function writeRecord() { + mkdirSync('docs/evidence', { recursive: true }); + writeFileSync('docs/evidence/oracle-key-transition.json', JSON.stringify(record, null, 2) + '\n'); +} diff --git a/scripts/rotate-secrets.mjs b/scripts/rotate-secrets.mjs new file mode 100755 index 0000000..f13781c --- /dev/null +++ b/scripts/rotate-secrets.mjs @@ -0,0 +1,500 @@ +#!/usr/bin/env node +/** + * Secret rotation executor. + * + * Rotates the shared secrets exposed by `prodenv.txt`, and proves the OLD value + * can no longer authenticate. Emits its own evidence record β€” a rotation you + * cannot demonstrate is a rotation you have not finished. + * + * THE VALUE IS NEVER DISPLAYED. A generated secret goes straight from + * `crypto.randomBytes` into the stdin of `vercel env update`, which stores it + * write-only. Nothing is written to a file, echoed, or passed in argv (argv is + * world-readable via `ps`). What this script prints is a FINGERPRINT: + * `sha256(value)` truncated to 12 hex characters. The preimage is 256 bits of + * CSPRNG output, so a fingerprint is not a shortcut to the secret β€” it exists + * so you can confirm the value in Vercel matches the value in `.env` without + * either of them being readable. + * + * Usage: + * node scripts/rotate-secrets.mjs --plan + * node scripts/rotate-secrets.mjs --rotate AUTH_SECRET [--targets production] + * node scripts/rotate-secrets.mjs --remove BOOTSTRAP_SECRET + * node scripts/rotate-secrets.mjs --fingerprint-local AUTH_SECRET + * node scripts/rotate-secrets.mjs --verify-dead CRON_SECRET --url https://… + * node scripts/rotate-secrets.mjs --verify-dead AUTH_SECRET --url https://… + * + * `--verify-dead` reads the OLD secret from stdin, never argv: + * node scripts/rotate-secrets.mjs --verify-dead CRON_SECRET --url https://… < old.txt + * …and shred that file afterwards. + */ + +import { randomBytes, createHash, createHmac } from 'node:crypto'; +import { spawnSync } from 'node:child_process'; +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const EVIDENCE = join(ROOT, 'docs', 'evidence', 'secret-rotation.json'); + +/** Must track src/lib/auth/index.ts:315. */ +const SESSION_COOKIE = 'cf_session'; + +/** Secrets this script may generate. Each entry states the blast radius. */ +const ROTATABLE = { + AUTH_SECRET: { + bytes: 32, + encoding: 'hex', + effect: + 'Invalidates every issued JWT. All users are logged out at redeploy. ' + + 'There is no dual-secret overlap in src/lib/auth/jwt.ts, so this is a ' + + 'hard cutover, not a graceful one.', + verify: 'forged-jwt', + }, + CRON_SECRET: { + bytes: 32, + encoding: 'hex', + effect: + 'Vercel Cron loses access to GET /api/indexer/run and ' + + 'POST /api/reconciliation/run until redeploy. Both read ' + + 'CRON_SECRET || INDEXER_SECRET, so rotate them together or the old one ' + + 'keeps working as a fallback.', + verify: 'bearer', + }, + INDEXER_SECRET: { + bytes: 32, + encoding: 'hex', + effect: + 'Same two endpoints as CRON_SECRET β€” it is the first-choice fallback in ' + + 'src/app/api/indexer/run/route.ts:16. Leaving this at its exposed value ' + + 'while rotating only CRON_SECRET rotates nothing.', + verify: 'bearer', + }, + BOOTSTRAP_SECRET: { + bytes: 32, + encoding: 'hex', + effect: + 'Guards POST /api/admin/bootstrap, which claims the FIRST admin. If an ' + + 'admin already exists, prefer --remove: unset, the endpoint answers 404 ' + + 'and no secret can reach it at all. Rotating keeps a live door.', + verify: 'config-only', + }, +}; + +/** + * ORACLE_SECRET_KEY is deliberately NOT rotatable here, and this is the most + * important guard in the file. + * + * Its public half is derived from the secret (src/lib/oracle/index.ts:74) and + * stored as each escrow's `oracle_pubkey`. The contract accepts attestations + * only from a key its admin has registered. So rotating the secret is not an + * environment change β€” it is an on-chain change, and that registration is the + * action currently blocked on the owner confirming the key mapping. + * + * Generating a third oracle key now would be worse than doing nothing: the + * open question is which of two keys is post-rotation, and a third candidate + * destroys the ability to answer it from the evidence that exists. + */ +const ORACLE_REFUSAL = + 'ORACLE_SECRET_KEY is not rotatable by this script.\n\n' + + ' Its public half is registered ON CHAIN. Rotating the secret produces a\n' + + ' public key the contract does not trust, which is exactly the state that\n' + + ' currently blocks the live funding run (OracleKeyNotRegistered).\n\n' + + ' Generating a third key would also destroy the evidence needed to decide\n' + + ' which of the two existing keys is post-rotation.\n\n' + + ' See docs/ORACLE_KEY_TRANSITION.md. That transition is owner-gated.'; + +const DB_REFUSAL = + 'The database credential is not rotatable by this script.\n\n' + + ' It lives in the Postgres provider, not in Vercel alone: the password must\n' + + ' be changed provider-side first, then DATABASE_URL, DIRECT_URL,\n' + + ' PRISMA_DATABASE_URL and POSTGRES_URL all updated to match. Rotating the\n' + + ' env vars alone breaks the app; rotating the provider alone breaks it too.\n\n' + + ' See docs/SECRET_ROTATION.md for the ordered procedure.'; + +function die(msg) { + console.error(`\nrefusing: ${msg}\n`); + process.exit(1); +} + +/** sha256 β†’ first 12 hex. Safe to print, publish, and paste. */ +function fingerprint(value) { + return createHash('sha256').update(value).digest('hex').slice(0, 12); +} + +function appendEvidence(record) { + mkdirSync(dirname(EVIDENCE), { recursive: true }); + let log = []; + if (existsSync(EVIDENCE)) { + try { + const parsed = JSON.parse(readFileSync(EVIDENCE, 'utf8')); + if (Array.isArray(parsed)) log = parsed; + else if (Array.isArray(parsed.actions)) log = parsed.actions; + } catch { + die(`${EVIDENCE} exists but is not valid JSON; refusing to overwrite it`); + } + } + log.push(record); + writeFileSync(EVIDENCE, `${JSON.stringify(log, null, 2)}\n`); + console.log(` evidence appended β†’ docs/evidence/secret-rotation.json`); +} + +function requireVercelAuth() { + const who = spawnSync('vercel', ['whoami'], { encoding: 'utf8' }); + if (who.status !== 0) { + die( + 'not authenticated to Vercel (`vercel whoami` failed). Run `vercel login`.\n' + + ` ${(who.stderr || who.stdout || '').trim()}` + ); + } + if (!existsSync(join(ROOT, '.vercel', 'project.json'))) { + die('no .vercel/project.json β€” this directory is not linked. Run `vercel link`.'); + } + return who.stdout.trim(); +} + +/** Reads a local env value WITHOUT importing it into this process's env. */ +function readLocalEnvValue(name) { + for (const file of ['.env', '.env.local']) { + const path = join(ROOT, file); + if (!existsSync(path)) continue; + for (const line of readFileSync(path, 'utf8').split('\n')) { + const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/); + if (!m || m[1] !== name) continue; + let v = m[2].trim(); + if ( + (v.startsWith('"') && v.endsWith('"')) || + (v.startsWith("'") && v.endsWith("'")) + ) { + v = v.slice(1, -1); + } + if (v) return { value: v, file }; + } + } + return null; +} + +function plan() { + console.log('\nSecret rotation plan\n====================\n'); + console.log('Rotatable by this script (Vercel env, value never displayed):\n'); + for (const [name, spec] of Object.entries(ROTATABLE)) { + const local = readLocalEnvValue(name); + console.log(` ${name}`); + console.log(` new value : ${spec.bytes} random bytes, ${spec.encoding}`); + console.log( + ` local copy : ${local ? `present in ${local.file} (fp ${fingerprint(local.value)})` : 'absent'}` + ); + console.log(` blast radius: ${spec.effect}`); + console.log(); + } + console.log('Refused, by design:\n'); + console.log(' ORACLE_SECRET_KEY on-chain coupling β€” see --rotate for detail'); + console.log(' DATABASE credential provider-side β€” see docs/SECRET_ROTATION.md'); + console.log( + '\nNote: a Vercel env change does not affect the RUNNING deployment.\n' + + 'Nothing cuts over until you redeploy, so the order above is not urgent β€”\n' + + 'but an un-redeployed rotation has also not taken effect. Verify after deploy.\n' + ); +} + +function rotate(name, targets, alsoLocal) { + if (name === 'ORACLE_SECRET_KEY') die(ORACLE_REFUSAL); + if (/DATABASE|POSTGRES|PRISMA|DIRECT_URL/.test(name)) die(DB_REFUSAL); + const spec = ROTATABLE[name]; + if (!spec) { + die(`${name} is not a known rotatable secret. Known: ${Object.keys(ROTATABLE).join(', ')}`); + } + + const account = requireVercelAuth(); + console.log(`\nRotating ${name}`); + console.log(` vercel account : ${account}`); + console.log(` targets : ${targets.join(', ')}`); + console.log(` effect : ${spec.effect}\n`); + + // Generated once, reused across targets so every environment agrees. + const value = randomBytes(spec.bytes).toString(spec.encoding); + const fp = fingerprint(value); + + for (const target of targets) { + // `update` replaces in place β€” no window where the variable is absent. + // The value arrives on stdin: not in argv, not in a file, not on screen. + const res = spawnSync('vercel', ['env', 'update', name, target, '--yes'], { + input: value, + encoding: 'utf8', + }); + if (res.status !== 0) { + const out = `${res.stdout || ''}${res.stderr || ''}`; + // An absent variable cannot be updated; add it instead. + if (/not found|does not exist/i.test(out)) { + const add = spawnSync('vercel', ['env', 'add', name, target, '--force', '--yes'], { + input: value, + encoding: 'utf8', + }); + if (add.status !== 0) { + die(`vercel env add ${name} ${target} failed:\n ${(add.stderr || add.stdout || '').trim()}`); + } + console.log(` ${target}: added (was absent)`); + } else { + die(`vercel env update ${name} ${target} failed:\n ${out.trim()}`); + } + } else { + console.log(` ${target}: updated`); + } + } + + if (alsoLocal) { + const path = join(ROOT, '.env'); + if (!existsSync(path)) die('.env does not exist; not creating it implicitly'); + const lines = readFileSync(path, 'utf8').split('\n'); + let replaced = false; + const next = lines.map((line) => { + if (new RegExp(`^\\s*${name}\\s*=`).test(line)) { + replaced = true; + return `${name}="${value}"`; + } + return line; + }); + if (!replaced) next.push(`${name}="${value}"`); + writeFileSync(path, next.join('\n')); + console.log(` .env: ${replaced ? 'replaced' : 'appended'} (value not displayed)`); + } + + console.log(`\n fingerprint: ${fp}`); + console.log(' Compare this against the Vercel dashboard only via a later'); + console.log(' --fingerprint-local run; the value itself is now write-only.\n'); + console.log(' NOT YET IN EFFECT. Redeploy, then run --verify-dead with the OLD value.\n'); + + appendEvidence({ + timestamp: new Date().toISOString(), + action: 'rotate', + secret: name, + targets, + local_env_updated: Boolean(alsoLocal), + new_value_fingerprint: fp, + vercel_account: account, + in_effect: false, + note: 'Vercel env change staged; takes effect at next deployment. Old value not yet proven dead.', + }); +} + +function removeSecret(name, targets) { + if (name !== 'BOOTSTRAP_SECRET') { + die( + `--remove is only for BOOTSTRAP_SECRET (removing ${name} would break a live code path).` + ); + } + const account = requireVercelAuth(); + console.log(`\nRemoving ${name} β€” POST /api/admin/bootstrap answers 404 once unset.`); + console.log(' Do this only if the first admin already exists in production.\n'); + const removed = []; + for (const target of targets) { + const res = spawnSync('vercel', ['env', 'remove', name, target, '--yes'], { + encoding: 'utf8', + }); + const out = `${res.stdout || ''}${res.stderr || ''}`; + if (res.status !== 0 && !/not found|does not exist/i.test(out)) { + die(`vercel env remove ${name} ${target} failed:\n ${out.trim()}`); + } + console.log(` ${target}: ${res.status === 0 ? 'removed' : 'already absent'}`); + removed.push(target); + } + appendEvidence({ + timestamp: new Date().toISOString(), + action: 'remove', + secret: name, + targets: removed, + vercel_account: account, + in_effect: false, + note: 'Endpoint disabled once redeployed; verify with --verify-dead BOOTSTRAP_SECRET.', + }); +} + +function fingerprintLocal(name) { + const local = readLocalEnvValue(name); + if (!local) die(`${name} is not set in .env or .env.local`); + console.log(`\n ${name}`); + console.log(` source : ${local.file}`); + console.log(` fingerprint: ${fingerprint(local.value)}`); + console.log(` length : ${local.value.length} chars\n`); +} + +/** Mints an HS256 JWT with the OLD secret. If the app accepts it, it is alive. */ +function forgeJwt(secret) { + const b64 = (o) => + Buffer.from(JSON.stringify(o)).toString('base64url'); + const now = Math.floor(Date.now() / 1000); + const head = b64({ alg: 'HS256', typ: 'JWT' }); + const body = b64({ + sub: 'GROTATIONPROBE000000000000000000000000000000000000000000', + role: 'admin', + iat: now, + exp: now + 300, + }); + const sig = createHmac('sha256', secret) + .update(`${head}.${body}`) + .digest('base64url'); + return `${head}.${body}.${sig}`; +} + +async function verifyDead(name, url) { + if (!url) die('--verify-dead requires --url https://'); + if (process.stdin.isTTY) { + die( + 'the OLD secret must arrive on stdin, never argv (argv is visible in `ps`).\n' + + ` node scripts/rotate-secrets.mjs --verify-dead ${name} --url ${url} < old-secret.txt` + ); + } + const chunks = []; + for await (const c of process.stdin) chunks.push(c); + const old = Buffer.concat(chunks).toString('utf8').trim(); + if (!old) die('stdin was empty β€” expected the OLD secret value'); + // Without this, piping a placeholder would write an evidence record asserting + // the old credential was rejected β€” a claim about a value never actually in + // use. Every secret this script rotates is 64 hex characters. + if (old.length < 32) { + die( + `the value on stdin is ${old.length} characters; every rotated secret is at least 32.\n` + + ' Refusing to record a rejection for a value that was never the live secret.' + ); + } + + const base = url.replace(/\/+$/, ''); + const spec = ROTATABLE[name]; + if (!spec) die(`unknown secret ${name}`); + + const probes = []; + if (spec.verify === 'bearer') { + probes.push( + { label: 'GET /api/indexer/run', method: 'GET', path: '/api/indexer/run', headers: { authorization: `Bearer ${old}` } }, + { label: 'POST /api/reconciliation/run', method: 'POST', path: '/api/reconciliation/run', headers: { authorization: `Bearer ${old}` } } + ); + } else if (spec.verify === 'forged-jwt') { + probes.push({ + label: 'GET /api/auth/me with JWT forged using the old AUTH_SECRET', + method: 'GET', + path: '/api/auth/me', + headers: { cookie: `${SESSION_COOKIE}=${forgeJwt(old)}` }, + }); + } else if (spec.verify === 'config-only') { + // Deliberately NOT probed over the network. + // + // On a secret match, POST /api/admin/bootstrap proceeds directly to + // prisma.user.upsert, defaulting the wallet to ADMIN_WALLETS[0] when the + // body omits one. A probe that *succeeded* would therefore grant admin in + // production β€” the verification would cause the thing it is checking for. + // + // It is also not externally decidable: the endpoint answers 404 both when + // the secret is unset and when it is set but wrong. Absence is proven from + // configuration, not from a response code. + die( + 'BOOTSTRAP_SECRET cannot be verified by probing.\n\n' + + ' A request bearing a still-live secret would perform a REAL admin\n' + + ' bootstrap (route.ts falls back to ADMIN_WALLETS[0]), and the endpoint\n' + + ' returns 404 whether the secret is unset or merely wrong β€” so a 404\n' + + ' proves nothing either way.\n\n' + + ' Verify from configuration instead:\n' + + ' vercel env ls production | grep BOOTSTRAP_SECRET # expect no row\n' + + ' then confirm a deployment was created after the removal.' + ); + } + + // reconciliation/run answers 404 as its REJECTION. A wrong path answers 404 + // too, which would otherwise read as a pass. Require the route in source, so + // a 404 can only mean the credential was refused. + for (const p of probes) { + const routeFile = join(ROOT, 'src', 'app', `${p.path}`, 'route.ts'); + if (!existsSync(routeFile)) { + die( + `${p.path} has no route at src/app${p.path}/route.ts.\n` + + ' Refusing to probe: a 404 from a path that does not exist would be\n' + + ' reported as a rejected credential.' + ); + } + } + + console.log(`\nProving the OLD ${name} is dead against ${base}\n`); + const results = []; + let allDead = true; + for (const p of probes) { + let status = null; + let error = null; + try { + const res = await fetch(`${base}${p.path}`, { + method: p.method, + headers: p.headers, + body: p.body, + redirect: 'manual', + }); + status = res.status; + } catch (e) { + error = e instanceof Error ? e.message : String(e); + } + // 401/403/404 = rejected. 200 = the old credential still works. + const dead = status !== null && [401, 403, 404].includes(status); + if (!dead) allDead = false; + console.log( + ` ${dead ? 'DEAD ' : 'ALIVE ⚠ '} ${p.label} β†’ ${error ? `network error: ${error}` : `HTTP ${status}`}` + ); + results.push({ probe: p.label, status, error, rejected: dead }); + } + + console.log( + allDead + ? `\n OLD ${name} is rejected everywhere probed. Rotation complete.\n` + : `\n OLD ${name} STILL AUTHENTICATES. The rotation is NOT complete β€”\n` + + ' check that you redeployed, and that no fallback variable still holds it.\n' + ); + + appendEvidence({ + timestamp: new Date().toISOString(), + action: 'verify-old-dead', + secret: name, + deployment: base, + old_value_fingerprint: fingerprint(old), + probes: results, + probe_paths_verified_in_source: true, + old_value_rejected_everywhere: allDead, + in_effect: allDead, + }); + + if (!allDead) process.exit(2); +} + +// ---------------------------------------------------------------- arg parsing +const argv = process.argv.slice(2); +const flag = (n) => { + const i = argv.indexOf(n); + return i === -1 ? null : argv[i + 1] ?? null; +}; +const has = (n) => argv.includes(n); + +for (const a of argv) { + if (/^[0-9a-f]{64}$/i.test(a)) { + die('a 64-hex value was passed in argv. Secrets must never be in argv β€” it is visible in `ps`.'); + } +} + +const targets = (flag('--targets') ?? 'production') + .split(',') + .map((t) => t.trim()) + .filter(Boolean); +for (const t of targets) { + if (!['production', 'preview', 'development'].includes(t)) { + die(`unknown target "${t}" (expected production, preview, or development)`); + } +} + +if (has('--plan')) { + plan(); +} else if (has('--rotate')) { + rotate(flag('--rotate'), targets, has('--local')); +} else if (has('--remove')) { + removeSecret(flag('--remove'), targets); +} else if (has('--fingerprint-local')) { + fingerprintLocal(flag('--fingerprint-local')); +} else if (has('--verify-dead')) { + await verifyDead(flag('--verify-dead'), flag('--url')); +} else { + console.log(readFileSync(fileURLToPath(import.meta.url), 'utf8').split('*/')[0]); + process.exit(1); +} diff --git a/scripts/validate-testnet-v2.mjs b/scripts/validate-testnet-v2.mjs new file mode 100755 index 0000000..597ba05 --- /dev/null +++ b/scripts/validate-testnet-v2.mjs @@ -0,0 +1,323 @@ +#!/usr/bin/env node +/** + * CoreFlow v2 β€” end-to-end Testnet validation. + * + * Runs the complete golden path against the DEPLOYED v2 contract and records + * machine-readable evidence: + * + * escrow creation + custody funding + * -> oracle attestation (CFWP-v2, per payment) + * -> submit_hours_proof + * -> manager approval + * -> DISTINCT finance approval + * -> pay_batch + * -> real SAC transfers + * -> balance verification + * + * Every step is verified against on-chain state rather than assumed from a + * non-erroring CLI call. Balances are read before and after, so "the payment + * settled" is a measured fact, not an inference from an exit code. + * + * TESTNET ONLY. CoreFlow v1 on Mainnet is untouched. + */ +import { execFileSync } from 'node:child_process'; +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { Keypair, nativeToScVal } from '@stellar/stellar-sdk'; + +const NETWORK = 'testnet'; +const NETWORK_PASSPHRASE = 'Test SDF Network ; September 2015'; +const DEPLOYMENT = JSON.parse(readFileSync('docs/evidence/testnet-v2-deployment.json', 'utf8')); +const CONTRACT = DEPLOYMENT.contractId; + +const ORACLE_SEED = (process.env.ORACLE_SECRET_KEY || '').trim(); +if (!/^[0-9a-fA-F]{64}$/.test(ORACLE_SEED)) { + console.error('ORACLE_SECRET_KEY must be a 32-byte hex seed.'); + process.exit(1); +} +const oracleKp = Keypair.fromRawEd25519Seed(Buffer.from(ORACLE_SEED, 'hex')); + +// ── CFWP-v2 preimage (mirrors src/lib/oracle/index.ts) ────────────────────── +const u16be = (n) => { const b = Buffer.alloc(2); b.writeUInt16BE(n); return b; }; +const u32be = (n) => { const b = Buffer.alloc(4); b.writeUInt32BE(n >>> 0); return b; }; +const u64be = (n) => { const b = Buffer.alloc(8); b.writeBigUInt64BE(BigInt(n)); return b; }; +const i128be = (n) => { + const b = Buffer.alloc(16); + let v = BigInt(n) & ((1n << 128n) - 1n); + for (let i = 15; i >= 0; i--) { b[i] = Number(v & 0xffn); v >>= 8n; } + return b; +}; +const sha256 = (b) => createHash('sha256').update(b).digest(); +const addrDigest = (a) => sha256(nativeToScVal(a, { type: 'address' }).toXDR()); + +function buildProofMessage(ctx, escrowId, paymentId, hours, nonce) { + const m = Buffer.concat([ + Buffer.from('CFWP', 'ascii'), u16be(2), + sha256(Buffer.from(NETWORK_PASSPHRASE, 'utf8')), + addrDigest(ctx.contractId), addrDigest(ctx.worker), addrDigest(ctx.token), + u32be(escrowId), u32be(paymentId), + i128be(ctx.amount), i128be(hours), + u64be(ctx.startDate), u64be(ctx.endDate), u64be(nonce), + ]); + if (m.length !== 198) throw new Error(`preimage is ${m.length} bytes, expected 198`); + return m; +} + +// ── CLI plumbing ──────────────────────────────────────────────────────────── +const sh = (args) => + execFileSync('stellar', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); + +const addr = (id) => sh(['keys', 'address', id]); + +function invoke(source, fn, args = [], { id = CONTRACT } = {}) { + return sh(['contract', 'invoke', '--id', id, '--source', source, + '--network', NETWORK, '--', fn, ...args]); +} + +/** Invoke and also return the transaction hash, for the evidence record. */ +function invokeWithHash(source, fn, args = [], { id = CONTRACT } = {}) { + const out = execFileSync('stellar', + ['contract', 'invoke', '--id', id, '--source', source, '--network', NETWORK, '--', fn, ...args], + { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); + // The CLI prints the tx hash on stderr as part of its progress output; when + // absent we still record the result rather than failing the run. + return { result: out.trim(), hash: null }; +} + +const balance = (sac, who) => + BigInt(JSON.parse(invoke('coreflow-v2-admin', 'balance', ['--id', who], { id: sac }))); + +const step = (n, msg) => console.log(`\n\x1b[1m[${n}] ${msg}\x1b[0m`); +const ok = (msg) => console.log(` \x1b[32mβœ“\x1b[0m ${msg}`); +const fail = (msg) => { console.error(` \x1b[31mβœ— ${msg}\x1b[0m`); process.exitCode = 1; throw new Error(msg); }; +const check = (cond, msg) => cond ? ok(msg) : fail(msg); + +// ── Payroll definition ────────────────────────────────────────────────────── +// Amounts are USDC base units (7 decimals). Every row satisfies the contract's +// `hours x rate == amount` invariant exactly; nothing is rounded to fit. +const USDC = (n) => BigInt(Math.round(n * 1e7)); +const PAYROLL = [ + { identity: 'coreflow-v2-worker', hours: 40n, rateUsdc: 25 }, + { identity: 'coreflow-v2-worker2', hours: 32n, rateUsdc: 30 }, + { identity: 'coreflow-v2-worker3', hours: 45n, rateUsdc: 20 }, +]; + +const evidence = { version: 'v2', network: NETWORK, contractId: CONTRACT, steps: [] }; +const record = (name, data) => evidence.steps.push({ name, at: new Date().toISOString(), ...data }); + +async function main() { + // The settlement asset comes from the deployment record, not a temp file: the + // record is the committed source of truth for what this deployment settles in. + const SAC = DEPLOYMENT.settlementAsset?.sacContractId; + if (!SAC) { + throw new Error( + 'docs/evidence/testnet-v2-deployment.json has no settlementAsset.sacContractId' + ); + } + const manager = addr('coreflow-v2-manager'); + const finance = addr('coreflow-v2-finance'); + + console.log(`CoreFlow v2 Testnet validation`); + console.log(` contract : ${CONTRACT}`); + console.log(` asset : ${SAC}`); + console.log(` manager : ${manager}`); + console.log(` finance : ${finance}`); + + check(manager !== finance, 'manager and finance are DISTINCT keys (separation of duties)'); + + const now = Math.floor(Date.now() / 1000); + const periodStart = now - 14 * 86400; + const periodEnd = now; + + const rows = PAYROLL.map((p, i) => { + const rate = USDC(p.rateUsdc); + const amount = p.hours * rate; + return { + paymentId: i, + identity: p.identity, + worker: addr(p.identity), + token: SAC, + hours: p.hours, + rate, + amount, + startDate: BigInt(periodStart), + endDate: BigInt(periodEnd), + }; + }); + + const total = rows.reduce((a, r) => a + r.amount, 0n); + console.log(`\n payroll: ${rows.length} contractors, ${Number(total) / 1e7} USDC total`); + for (const r of rows) { + console.log(` #${r.paymentId} ${r.worker.slice(0, 8)}… ${r.hours}h @ ${Number(r.rate) / 1e7} = ${Number(r.amount) / 1e7} USDC`); + } + + // ── 1. Balances before ──────────────────────────────────────────────────── + step(1, 'Recording balances before settlement'); + const managerBefore = balance(SAC, manager); + const workerBefore = rows.map((r) => balance(SAC, r.worker)); + ok(`manager holds ${Number(managerBefore) / 1e7} USDC`); + workerBefore.forEach((b, i) => ok(`worker #${i} holds ${Number(b) / 1e7} USDC`)); + record('balances_before', { + manager: managerBefore.toString(), + workers: workerBefore.map(String), + }); + + // ── 2. Create escrow (pulls custody) ────────────────────────────────────── + step(2, 'Creating escrow β€” custody funded from the manager in one transaction'); + const paymentsJson = JSON.stringify(rows.map((r) => ({ + id: r.paymentId + 1, + worker: r.worker, + token: r.token, + amount: r.amount.toString(), + start_date: Number(r.startDate), + end_date: Number(r.endDate), + hours_logged: '0', + rate_per_hour: r.rate.toString(), + proof_verified: false, + // PaymentStatus is a #[repr(u32)] C-like enum, which the CLI's spec tools + // encode by discriminant rather than by variant name. + status: 0, + }))); + + const escrowIdRaw = invoke('coreflow-v2-manager', 'initialize_multi_sig_escrow', [ + '--manager', manager, + '--finance_approver', finance, + '--oracle_pubkey', DEPLOYMENT.oraclePublicKey, + '--payments', paymentsJson, + ]); + const escrowId = Number(JSON.parse(escrowIdRaw)); + ok(`escrow #${escrowId} created`); + + const custody = balance(SAC, CONTRACT); + check(custody === total, `contract custody holds exactly ${Number(total) / 1e7} USDC`); + const managerAfterFund = balance(SAC, manager); + check(managerAfterFund === managerBefore - total, 'manager debited by exactly the batch total'); + record('escrow_created', { escrowId, custodyBaseUnits: custody.toString() }); + + // ── 3. Oracle attestation ───────────────────────────────────────────────── + step(3, 'Oracle attestation (CFWP-v2) and on-chain proof submission'); + const attestations = []; + for (const r of rows) { + const nonce = BigInt(JSON.parse(invoke('coreflow-v2-admin', 'get_nonce', ['--escrow_id', String(escrowId)]))); + + const ctx = { contractId: CONTRACT, worker: r.worker, token: r.token, amount: r.amount, startDate: r.startDate, endDate: r.endDate }; + const msg = buildProofMessage(ctx, escrowId, r.paymentId, r.hours, nonce); + + // The contract is the source of truth for the preimage β€” confirm our + // independently built bytes are byte-identical before signing them. + const onChain = Buffer.from(JSON.parse(invoke('coreflow-v2-admin', 'proof_preimage', [ + '--escrow_id', String(escrowId), '--payment_id', String(r.paymentId), + '--hours', r.hours.toString(), '--nonce', nonce.toString(), + ])), 'hex'); + check(onChain.equals(msg), `payment #${r.paymentId}: local preimage matches contract's proof_preimage`); + + const sig = oracleKp.sign(msg); + invoke('coreflow-v2-manager', 'submit_hours_proof', [ + '--escrow_id', String(escrowId), '--payment_id', String(r.paymentId), + '--hours_logged', r.hours.toString(), '--nonce', nonce.toString(), + '--signature', sig.toString('hex'), + ]); + ok(`payment #${r.paymentId}: ${r.hours}h attested and verified on-chain (nonce ${nonce})`); + attestations.push({ + paymentId: r.paymentId, hours: r.hours.toString(), nonce: nonce.toString(), + preimageSha256: sha256(msg).toString('hex'), signature: sig.toString('base64'), + }); + } + record('oracle_attestations', { schema: 'CFWP-v2', attestations }); + + // ── 4. Replay must fail ─────────────────────────────────────────────────── + step(4, 'Replay protection β€” resubmitting a consumed attestation'); + const replay = attestations[0]; + let replayRejected = false; + try { + invoke('coreflow-v2-manager', 'submit_hours_proof', [ + '--escrow_id', String(escrowId), '--payment_id', '0', + '--hours_logged', replay.hours, '--nonce', replay.nonce, + '--signature', Buffer.from(replay.signature, 'base64').toString('hex'), + ]); + } catch { + replayRejected = true; + } + check(replayRejected, 'a consumed nonce is rejected on-chain (InvalidNonce)'); + record('replay_rejected', { paymentId: 0, nonce: replay.nonce, rejected: replayRejected }); + + // ── 5. Settlement must require BOTH approvals ───────────────────────────── + step(5, 'Dual approval β€” settlement blocked until both signers approve'); + let blockedNoApprovals = false; + try { invoke('coreflow-v2-manager', 'pay_batch', ['--escrow_id', String(escrowId)]); } + catch { blockedNoApprovals = true; } + check(blockedNoApprovals, 'pay_batch refused with zero approvals'); + + invoke('coreflow-v2-manager', 'manager_approve', ['--escrow_id', String(escrowId)]); + ok('manager approved'); + + let blockedOneApproval = false; + try { invoke('coreflow-v2-manager', 'pay_batch', ['--escrow_id', String(escrowId)]); } + catch { blockedOneApproval = true; } + check(blockedOneApproval, 'pay_batch still refused with only the manager approval'); + + // A DIFFERENT key signs. The manager cannot produce this approval. + invoke('coreflow-v2-finance', 'finance_approve', ['--escrow_id', String(escrowId)]); + ok('finance approved (distinct key)'); + record('dual_approval', { blockedNoApprovals, blockedOneApproval, manager, finance }); + + // ── 6. Settle ───────────────────────────────────────────────────────────── + step(6, 'pay_batch β€” real SAC transfers to every contractor'); + invoke('coreflow-v2-manager', 'pay_batch', ['--escrow_id', String(escrowId)]); + ok('pay_batch executed'); + + // ── 7. Verify settlement by measured balances ───────────────────────────── + step(7, 'Verifying settlement against on-chain balances'); + const custodyAfter = balance(SAC, CONTRACT); + check(custodyAfter === 0n, 'contract custody fully drained (no residue)'); + + const paid = []; + rows.forEach((r, i) => { + const after = balance(SAC, r.worker); + const delta = after - workerBefore[i]; + check(delta === r.amount, `worker #${i} received exactly ${Number(r.amount) / 1e7} USDC`); + paid.push({ paymentId: r.paymentId, worker: r.worker, receivedBaseUnits: delta.toString() }); + }); + record('settlement', { custodyAfter: custodyAfter.toString(), paid }); + + // ── 8. Double-settlement must fail ──────────────────────────────────────── + step(8, 'Double-settlement protection'); + let doubleRejected = false; + try { invoke('coreflow-v2-manager', 'pay_batch', ['--escrow_id', String(escrowId)]); } + catch { doubleRejected = true; } + check(doubleRejected, 'a second pay_batch is rejected (PaymentAlreadyFinalized)'); + record('double_settlement_rejected', { rejected: doubleRejected }); + + // ── 9. Final escrow state ───────────────────────────────────────────────── + step(9, 'Final on-chain escrow state'); + const finalEscrow = JSON.parse(invoke('coreflow-v2-admin', 'get_escrow', ['--escrow_id', String(escrowId)])); + check(finalEscrow.manager_approved === true, 'manager_approved = true'); + check(finalEscrow.finance_approved === true, 'finance_approved = true'); + check(finalEscrow.payments.every((p) => p.proof_verified === true), 'every payment carries a verified oracle proof'); + // PaymentStatus is #[repr(u32)]; the CLI renders it as its discriminant. + // Accept either form so this assertion survives a CLI that learns the names. + const FINALIZED = 3; + check( + finalEscrow.payments.every((p) => p.status === FINALIZED || p.status === 'Finalized'), + 'every payment is Finalized' + ); + record('final_state', { escrowId, escrow: finalEscrow }); + + evidence.escrowId = escrowId; + evidence.assetContract = SAC; + evidence.totalSettledBaseUnits = total.toString(); + evidence.completedAt = new Date().toISOString(); + evidence.explorer = { + contract: `https://stellar.expert/explorer/testnet/contract/${CONTRACT}`, + asset: `https://stellar.expert/explorer/testnet/contract/${SAC}`, + }; + + mkdirSync('docs/evidence', { recursive: true }); + writeFileSync('docs/evidence/testnet-v2-golden-path.json', JSON.stringify(evidence, null, 2)); + + console.log(`\n\x1b[32m\x1b[1mGOLDEN PATH COMPLETE\x1b[0m`); + console.log(` escrow #${escrowId}: ${Number(total) / 1e7} USDC settled to ${rows.length} contractors`); + console.log(` evidence: docs/evidence/testnet-v2-golden-path.json`); +} + +main().catch((e) => { console.error(`\n\x1b[31mFAILED: ${e.message}\x1b[0m`); process.exit(1); }); diff --git a/src/app/api/__tests__/escrows.route.test.ts b/src/app/api/__tests__/escrows.route.test.ts index 72b41b9..3d74448 100644 --- a/src/app/api/__tests__/escrows.route.test.ts +++ b/src/app/api/__tests__/escrows.route.test.ts @@ -10,7 +10,12 @@ vi.mock('@/lib/auth', async (orig) => { vi.mock('@/lib/db/prisma', () => { const prisma = { escrow: { findMany: vi.fn(), create: vi.fn() }, + orgMember: { findMany: vi.fn(), findUnique: vi.fn(), findFirst: vi.fn() }, + payrollBatch: { create: vi.fn() }, + payment: { create: vi.fn() }, auditLog: { create: vi.fn() }, + // The route writes escrow + batch + payment atomically. + $transaction: vi.fn(async (fn: any) => fn(prisma)), }; return { default: prisma }; }); @@ -39,21 +44,107 @@ describe('GET /api/escrows', () => { expect(res.status).toBe(401); }); + it('returns nothing for a user who belongs to no organization', async () => { + // Previously a platform ADMIN with no membership saw EVERY tenant's escrows. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: 'GU', role: 'ADMIN' }); + mockPrisma.orgMember.findMany.mockResolvedValue([]); + + const body = await (await GET(new Request('http://localhost/api/escrows') as any)).json(); + + expect(body.escrows).toEqual([]); + expect(mockPrisma.escrow.findMany).not.toHaveBeenCalled(); + }); + + it('scopes the query to the caller’s organizations', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: 'GU', role: 'ADMIN' }); + mockPrisma.orgMember.findMany.mockResolvedValue([ + { orgId: 'orgA', role: 'OWNER' }, + { orgId: 'orgC', role: 'VIEWER' }, + ]); + mockPrisma.escrow.findMany.mockResolvedValue([]); + + await GET(new Request('http://localhost/api/escrows') as any); + + const where = mockPrisma.escrow.findMany.mock.calls[0][0].where; + // Every branch of the query names an organization the caller belongs to. + const orgFilters = JSON.stringify(where); + expect(orgFilters).toContain('orgA'); + expect(orgFilters).toContain('orgC'); + expect(orgFilters).not.toContain('orgB'); + }); + + it('restricts a WORKER to escrows that pay them', async () => { + // A WORKER holds no organization-wide read: otherwise any contractor could + // enumerate the whole payroll. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: 'GME', role: 'EMPLOYEE' }); + mockPrisma.orgMember.findMany.mockResolvedValue([{ orgId: 'orgA', role: 'WORKER' }]); + mockPrisma.escrow.findMany.mockResolvedValue([]); + + await GET(new Request('http://localhost/api/escrows') as any); + + const where = JSON.stringify(mockPrisma.escrow.findMany.mock.calls[0][0].where); + expect(where).toContain('recipientAddress'); + expect(where).toContain('GME'); + }); + it('returns mapped escrows for an authenticated user', async () => { - mockGetUser.mockResolvedValue({ walletAddress: 'GU', role: 'EMPLOYEE' }); + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: 'GU', role: 'EMPLOYEE' }); + mockPrisma.orgMember.findMany.mockResolvedValue([{ orgId: 'orgA', role: 'OWNER' }]); + // A THREE-payee escrow: the response must carry three payments, not one. + // Collapsing them into a single row was the defect this phase removed. mockPrisma.escrow.findMany.mockResolvedValue([ { - id: 1, onChainId: 7, workerPubKey: 'GWORKERADDRESSLONG', amountCents: 420000, - rateCents: 25000, currency: 'USDC', status: 'ready', managerApproved: true, - financeApproved: true, createdAt: new Date(), timeLogs: [{ hoursLogged: 40 }], + id: 'esc_1', onChainId: 7, contractId: 'CCONTRACT', network: 'testnet', + assetDecimals: 7, managerApproved: true, financeApproved: true, + cancelled: false, createdAt: new Date(), + payments: [ + { + id: 'p0', recipientAddress: 'GWORKER1', onChainPaymentIndex: 0, + amountBaseUnits: 10_000_000_000n, rateBaseUnits: 250_000_000n, hours: 40n, + assetDecimals: 7, assetCode: 'USDC', state: 'PAID', + settlementTxHash: 'HASH0', settledAt: new Date(), + }, + { + id: 'p1', recipientAddress: 'GWORKER2', onChainPaymentIndex: 1, + amountBaseUnits: 9_600_000_000n, rateBaseUnits: 300_000_000n, hours: 32n, + assetDecimals: 7, assetCode: 'USDC', state: 'AWAITING_FINANCE', + settlementTxHash: null, settledAt: null, + }, + { + id: 'p2', recipientAddress: 'GWORKER3', onChainPaymentIndex: 2, + amountBaseUnits: 9_000_000_000n, rateBaseUnits: 200_000_000n, hours: 45n, + assetDecimals: 7, assetCode: 'USDC', state: 'SETTLEMENT_FAILED', + settlementTxHash: 'HASH2', settledAt: null, + }, + ], }, ]); const res = await GET(new Request('http://localhost/api/escrows') as any); const data = await res.json(); expect(res.status).toBe(200); expect(data.escrows[0].id).toBe(7); - expect(data.escrows[0].hoursLogged).toBe('40'); expect(data.escrows[0].manager_approved).toBe(true); + + // THE REGRESSION GUARD: three payees must surface as three payments, each + // with its own recipient and amount. + expect(data.escrows[0].paymentCount).toBe(3); + expect(data.escrows[0].payments).toHaveLength(3); + expect(data.escrows[0].payments.map((p: any) => p.recipient)) + .toEqual(['GWORKER1', 'GWORKER2', 'GWORKER3']); + expect(data.escrows[0].payments.map((p: any) => p.amount)) + .toEqual(['1,000.00', '960.00', '900.00']); + + // Aggregates are derived from the payments, not stored. + expect(data.escrows[0].amount).toBe('2,860.00'); + expect(data.escrows[0].hoursLogged).toBe('117'); // 40 + 32 + 45 + + // A batch containing a failure reports as failed, rather than letting one + // broken payment hide inside a mostly-paid batch. + expect(data.escrows[0].status).toBe('Settlement failed'); + + // Per-payment states are distinct and human-readable, never "Processing". + expect(data.escrows[0].payments.map((p: any) => p.stateLabel)) + .toEqual(['Paid', 'Awaiting finance approval', 'Settlement failed']); }); }); @@ -62,28 +153,60 @@ describe('POST /api/escrows', () => { it('401 when unauthenticated', async () => { mockGetUser.mockResolvedValue(null); - const res = await POST(jsonReq({ workerPubKey: 'G', amountCents: 1, rateCents: 1 })); + const res = await POST(jsonReq({ workerPubKey: 'G', amountBaseUnits: '1', rateBaseUnits: '1' })); expect(res.status).toBe(401); }); it('403 for an employee (insufficient role)', async () => { mockGetUser.mockResolvedValue({ walletAddress: 'GU', role: 'EMPLOYEE' }); - const res = await POST(jsonReq({ workerPubKey: 'G', amountCents: 100, rateCents: 10 })); + const res = await POST(jsonReq({ workerPubKey: 'G', amountBaseUnits: '100', rateBaseUnits: '10' })); expect(res.status).toBe(403); }); it('400 on invalid body (negative amount)', async () => { mockGetUser.mockResolvedValue({ walletAddress: 'GA', role: 'ADMIN' }); - const res = await POST(jsonReq({ workerPubKey: 'G', amountCents: -5, rateCents: 10 })); + const res = await POST(jsonReq({ workerPubKey: 'G', amountBaseUnits: '-5', rateBaseUnits: '10' })); expect(res.status).toBe(400); }); it('201 for an admin with a valid body (and writes an audit log)', async () => { mockGetUser.mockResolvedValue({ walletAddress: 'GA', role: 'ADMIN' }); - mockPrisma.escrow.create.mockResolvedValue({ id: 1, onChainId: null }); - const res = await POST(jsonReq({ workerPubKey: 'GWORKER', amountCents: 4200, rateCents: 250 })); + mockPrisma.orgMember.findFirst.mockResolvedValue({ orgId: 'org_1' }); + mockPrisma.escrow.create.mockResolvedValue({ + id: 'esc_1', + onChainId: null, + totalAmountBaseUnits: 42_000_000_000n, + }); + mockPrisma.payrollBatch.create.mockResolvedValue({ id: 'bat_1', reference: 'ESC-00000-esc_1' }); + mockPrisma.payment.create.mockResolvedValue({ + id: 'pay_1', + state: 'VALIDATING', + amountBaseUnits: 42_000_000_000n, + }); + const res = await POST(jsonReq({ + workerPubKey: 'GWORKER', amountBaseUnits: '42000000000', rateBaseUnits: '2500000000', + })); expect(res.status).toBe(201); expect(mockPrisma.escrow.create).toHaveBeenCalled(); expect(mockPrisma.auditLog.create).toHaveBeenCalled(); + + // BigInt columns must leave the API as strings: JSON.stringify throws on + // bigint outright, so an un-serialized amount is a 500, not a rounding bug. + const body = await res.json(); + expect(body.escrow.totalAmountBaseUnits).toBe('42000000000'); + expect(body.payment.amountBaseUnits).toBe('42000000000'); + + // The value written to the DB is a bigint, not a lossy Number. + expect(mockPrisma.escrow.create.mock.calls[0][0].data.totalAmountBaseUnits) + .toBe(42_000_000_000n); + + // A recorded escrow creates its Payment row β€” the escrow is not itself the + // payment. + expect(mockPrisma.payment.create).toHaveBeenCalled(); + const paymentData = mockPrisma.payment.create.mock.calls[0][0].data; + expect(paymentData.amountBaseUnits).toBe(42_000_000_000n); + expect(paymentData.onChainPaymentIndex).toBe(0); + // Nothing here asserts settlement: the indexer owns that. + expect(paymentData.state).toBe('VALIDATING'); }); }); diff --git a/src/app/api/__tests__/submit-batch.route.test.ts b/src/app/api/__tests__/submit-batch.route.test.ts new file mode 100644 index 0000000..173dede --- /dev/null +++ b/src/app/api/__tests__/submit-batch.route.test.ts @@ -0,0 +1,264 @@ +// @vitest-environment node +/** + * Authorization regression tests for POST /api/submit-batch. + * + * This endpoint issues the Ed25519 attestations that flip `proof_verified` on + * chain, and `pay_batch` refuses to move funds without them. It was previously + * reachable with no session at all, which meant anyone could manufacture the + * proof-of-work half of the security model. These tests pin the two gates that + * now stand in the way: a verified session, and caller == on-chain manager. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('@/lib/auth', async (orig) => { + const actual = await orig(); + return { ...actual, getUserFromRequest: vi.fn() }; +}); + +vi.mock('@/lib/oracle', () => ({ + getOraclePublicKeyHex: vi.fn(() => 'ab'.repeat(32)), + signHoursProof: vi.fn(() => 'SIGNATURE_BASE64'), +})); + +vi.mock('@/lib/config', () => ({ + STELLAR_CONFIG: { + contract: { id: 'CCQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2CNSG' }, + getNetworkPassphrase: () => 'Test SDF Network ; September 2015', + }, +})); + +const getEscrow = vi.fn(); +const getNonce = vi.fn(); +vi.mock('@/lib/contracts', () => ({ + CoreFlowClient: class { + getEscrow = getEscrow; + getNonce = getNonce; + }, +})); + +vi.mock('@/lib/audit', () => ({ audit: vi.fn() })); + +// Tenant layer: the escrow must belong to an organization the caller is in, on +// top of the on-chain manager check. +vi.mock('@/lib/db/prisma', () => { + const prisma: any = { + orgMember: { findUnique: vi.fn(), findMany: vi.fn() }, + escrow: { findFirst: vi.fn() }, + }; + return { default: prisma }; +}); + +import { POST } from '../submit-batch/route'; +import { getUserFromRequest } from '@/lib/auth'; +import { signHoursProof } from '@/lib/oracle'; +import prismaDefault from '@/lib/db/prisma'; + +const prismaMock = prismaDefault as any; +const ORG = 'orgA'; + +/** Give the signed-in caller an ACTIVE membership that owns the escrow. */ +function grantTenant(role = 'MANAGER', walletAddress = MANAGER) { + prismaMock.orgMember.findMany.mockResolvedValue([{ orgId: ORG, role }]); + prismaMock.orgMember.findUnique.mockResolvedValue({ + orgId: ORG, userId: 'u1', role, status: 'ACTIVE', + org: { id: ORG, name: 'Org A', slug: 'org-a' }, + user: { walletAddress }, + }); + prismaMock.escrow.findFirst.mockResolvedValue({ id: 'esc1', orgId: ORG, onChainId: 1 }); +} + +const mockGetUser = getUserFromRequest as unknown as ReturnType; + +const MANAGER = 'G' + 'A'.repeat(55); +const WORKER = 'G' + 'B'.repeat(55); +const PAYEE = 'G' + 'C'.repeat(55); + +function req(body: unknown) { + return new Request('http://localhost/api/submit-batch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) as any; +} + +const validBody = { + escrow_id: 1, + payees: [{ address: PAYEE, amount: '40', token: 'USDC' }], +}; + +/** One on-chain payment row: 10000 units at 250/hour == 40 hours. */ +const onChainPayment = (worker: string) => ({ + worker, + token: 'CCZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLEB3K', + amount: 10000n, + rate_per_hour: 250n, + start_date: 1000, + end_date: 2000, +}); + +describe('POST /api/submit-batch β€” authorization', () => { + beforeEach(() => { + vi.clearAllMocks(); + getEscrow.mockResolvedValue({ + manager: MANAGER, + payments: [onChainPayment(PAYEE)], + }); + getNonce.mockResolvedValue(0); + grantTenant(); + }); + + it('401s an unauthenticated caller and signs nothing', async () => { + mockGetUser.mockResolvedValue(null); + + const res = await POST(req(validBody)); + + expect(res.status).toBe(401); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('403s a signed-in worker who is not the on-chain manager', async () => { + // The worker holds a perfectly valid session β€” the only thing stopping them + // from attesting to their own hours is the on-chain manager check. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: WORKER, role: 'EMPLOYEE' }); + grantTenant('MANAGER', WORKER); + + const res = await POST(req(validBody)); + + expect(res.status).toBe(403); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('403s even an ADMIN who is not the escrow manager', async () => { + // Platform admin is not the same authority as this escrow's manager; + // role must not substitute for on-chain custody of the escrow. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: WORKER, role: 'ADMIN' }); + grantTenant('ADMIN', WORKER); + + const res = await POST(req(validBody)); + + expect(res.status).toBe(403); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('issues attestations to the on-chain manager', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const res = await POST(req(validBody)); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.signatures).toHaveLength(1); + expect(data.signatures[0].signature).toBe('SIGNATURE_BASE64'); + }); + + it('starts nonces at the live on-chain watermark, not zero', async () => { + // Signing from 0 against an escrow that already consumed nonces would + // produce signatures the contract rejects with InvalidNonce (#9). + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + getNonce.mockResolvedValue(7); + getEscrow.mockResolvedValue({ + manager: MANAGER, + payments: [onChainPayment(PAYEE), onChainPayment(WORKER)], + }); + + const data = await (await POST(req({ + escrow_id: 1, + payees: [ + { address: PAYEE, amount: '40', token: 'USDC' }, + { address: WORKER, amount: '32', token: 'USDC' }, + ], + }))).json(); + + expect(data.startNonce).toBe(7); + expect(data.signatures.map((s: { nonce: number }) => s.nonce)).toEqual([7, 8]); + }); + + it('rejects a batch over the 100-payee cap before touching the oracle', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const res = await POST(req({ + escrow_id: 1, + payees: Array.from({ length: 101 }, () => ({ address: PAYEE, amount: '1', token: 'USDC' })), + })); + + expect(res.status).toBe(400); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('refuses when the upload has a different payee count than the escrow', async () => { + // The signed preimage comes from on-chain rows, so a mismatched upload + // would attest to something the uploader never reviewed. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const res = await POST(req({ + escrow_id: 1, + payees: [ + { address: PAYEE, amount: '40', token: 'USDC' }, + { address: WORKER, amount: '32', token: 'USDC' }, + ], + })); + + expect(res.status).toBe(409); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('refuses when an uploaded payee does not match the on-chain row', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const res = await POST(req({ + escrow_id: 1, + payees: [{ address: WORKER, amount: '40', token: 'USDC' }], // chain holds PAYEE + })); + + expect(res.status).toBe(409); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('derives hours from the escrowed amount, not from the upload', async () => { + // The contract enforces `hours x rate == amount`; 10000/250 = 40 regardless + // of what the CSV claims, so a wrong CSV figure cannot reach the signature. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const data = await (await POST(req({ + escrow_id: 1, + payees: [{ address: PAYEE, amount: '999999', token: 'USDC' }], + }))).json(); + + expect(data.signatures[0].hours).toBe(40); + }); + + it('403s a caller whose organization does not own the escrow', async () => { + // The caller may well be the on-chain manager β€” controlling a key is not the + // same as the escrow being part of their workspace. + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + grantTenant('MANAGER', MANAGER); + prismaMock.escrow.findFirst.mockResolvedValue(null); // not in this tenant + + const res = await POST(req(validBody)); + + expect(res.status).toBe(404); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('403s a role that cannot request attestations', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + grantTenant('VIEWER', MANAGER); + + const res = await POST(req(validBody)); + + expect(res.status).toBe(403); + expect(signHoursProof).not.toHaveBeenCalled(); + }); + + it('rejects a malformed payee address', async () => { + mockGetUser.mockResolvedValue({ userId: 'u1', walletAddress: MANAGER, role: 'EMPLOYEE' }); + + const res = await POST(req({ + escrow_id: 1, + payees: [{ address: 'not-a-stellar-address', amount: '40', token: 'USDC' }], + })); + + expect(res.status).toBe(400); + expect(signHoursProof).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/api/admin/__tests__/bootstrap.route.test.ts b/src/app/api/admin/__tests__/bootstrap.route.test.ts new file mode 100644 index 0000000..5bb7b2b --- /dev/null +++ b/src/app/api/admin/__tests__/bootstrap.route.test.ts @@ -0,0 +1,106 @@ +// @vitest-environment node +/** + * Abuse-resistance tests for POST /api/admin/bootstrap. + * + * This endpoint is exempt from session auth by necessity β€” there is no admin to + * authenticate as yet β€” so BOOTSTRAP_SECRET is the only control between an + * anonymous caller and full platform control. It previously had no rate limit + * and used a short-circuiting `!==` comparison. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +vi.mock('@/lib/db/prisma', () => ({ + default: { user: { upsert: vi.fn() }, auditLog: { create: vi.fn() } }, +})); +vi.mock('@/lib/audit', () => ({ audit: vi.fn() })); + +import { POST } from '../bootstrap/route'; +import prisma from '@/lib/db/prisma'; +import { __resetRateLimiter } from '@/lib/ratelimit'; + +const mockPrisma = prisma as any; + +const GOOD_SECRET = 'x'.repeat(48); +const WALLET = 'G' + 'A'.repeat(55); + +function req(secret: string | undefined, ip = '203.0.113.10') { + return new Request('http://localhost/api/admin/bootstrap', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-forwarded-for': ip, + ...(secret === undefined ? {} : { 'x-bootstrap-secret': secret }), + }, + body: JSON.stringify({ walletAddress: WALLET }), + }) as any; +} + +describe('POST /api/admin/bootstrap', () => { + beforeEach(() => { + vi.clearAllMocks(); + __resetRateLimiter(); + process.env.BOOTSTRAP_SECRET = GOOD_SECRET; + mockPrisma.user.upsert.mockResolvedValue({ + id: 'u1', walletAddress: WALLET, role: 'ADMIN', + }); + }); + afterEach(() => { + delete process.env.BOOTSTRAP_SECRET; + }); + + it('promotes the target wallet when the secret is correct', async () => { + const res = await POST(req(GOOD_SECRET)); + expect(res.status).toBe(200); + expect(mockPrisma.user.upsert).toHaveBeenCalled(); + }); + + it('404s a wrong secret without revealing that the endpoint exists', async () => { + const res = await POST(req('y'.repeat(48))); + expect(res.status).toBe(404); + expect(mockPrisma.user.upsert).not.toHaveBeenCalled(); + }); + + it('404s when the secret is not configured', async () => { + delete process.env.BOOTSTRAP_SECRET; + expect((await POST(req(GOOD_SECRET))).status).toBe(404); + }); + + it('refuses to operate with a secret too short to resist guessing', async () => { + // A short secret reads as protection while providing none, so the endpoint + // disables itself rather than accepting it. + process.env.BOOTSTRAP_SECRET = 'short'; + const res = await POST(req('short')); + expect(res.status).toBe(404); + expect(mockPrisma.user.upsert).not.toHaveBeenCalled(); + }); + + it('throttles brute-force guessing from one IP', async () => { + // Without a brake the secret is guessable at network speed. + for (let i = 0; i < 5; i++) { + expect((await POST(req(`wrong-${i}`.padEnd(48, 'z')))).status).toBe(404); + } + + // Budget exhausted: even the CORRECT secret is now refused from this IP. + // That is the point β€” the limiter must not be bypassable by guessing right. + const res = await POST(req(GOOD_SECRET)); + expect(res.status).toBe(404); + expect(mockPrisma.user.upsert).not.toHaveBeenCalled(); + }); + + it('counts the budget per IP, not globally', async () => { + for (let i = 0; i < 5; i++) await POST(req('wrong'.padEnd(48, 'z'), '198.51.100.1')); + + // A different client is unaffected by the first one's exhausted budget. + const res = await POST(req(GOOD_SECRET, '198.51.100.2')); + expect(res.status).toBe(200); + }); + + it('rejects a non-string secret without throwing', async () => { + const r = new Request('http://localhost/api/admin/bootstrap', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-forwarded-for': '203.0.113.99' }, + body: JSON.stringify({ walletAddress: WALLET, bootstrapSecret: { evil: true } }), + }) as any; + expect((await POST(r)).status).toBe(404); + }); +}); diff --git a/src/app/api/admin/__tests__/invitations-revoke.route.test.ts b/src/app/api/admin/__tests__/invitations-revoke.route.test.ts deleted file mode 100644 index d24b469..0000000 --- a/src/app/api/admin/__tests__/invitations-revoke.route.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -// @vitest-environment node -import { describe, it, expect, vi, beforeEach } from 'vitest'; - -vi.mock('@/lib/auth', async (orig) => { - const actual = await orig(); - return { ...actual, getUserFromRequest: vi.fn() }; -}); - -vi.mock('@/lib/db/prisma', () => { - const prisma = { - invitation: { findUnique: vi.fn(), delete: vi.fn() }, - auditLog: { create: vi.fn() }, - }; - return { default: prisma }; -}); - -import { DELETE } from '../invitations/[token]/route'; -import { getUserFromRequest } from '@/lib/auth'; -import prisma from '@/lib/db/prisma'; - -const mockGetUser = getUserFromRequest as unknown as ReturnType; -const mockPrisma = prisma as any; - -function deleteReq(token: string) { - return new Request(`http://localhost/api/admin/invitations/${token}`, { - method: 'DELETE', - }) as any; -} - -describe('DELETE /api/admin/invitations/[token]', () => { - beforeEach(() => vi.clearAllMocks()); - - it('401 when unauthenticated', async () => { - mockGetUser.mockResolvedValue(null); - const res = await DELETE(deleteReq('sometoken'), { params: { token: 'sometoken' } }); - expect(res.status).toBe(401); - }); - - it('403 for a non-admin', async () => { - mockGetUser.mockResolvedValue({ walletAddress: 'GM', role: 'EMPLOYEE' }); - const res = await DELETE(deleteReq('sometoken'), { params: { token: 'sometoken' } }); - expect(res.status).toBe(403); - }); - - it('404 when invitation not found', async () => { - mockGetUser.mockResolvedValue({ walletAddress: 'GA', role: 'ADMIN' }); - mockPrisma.invitation.findUnique.mockResolvedValue(null); - const res = await DELETE(deleteReq('sometoken'), { params: { token: 'sometoken' } }); - expect(res.status).toBe(404); - }); - - it('200 and deletes invitation for an admin', async () => { - mockGetUser.mockResolvedValue({ walletAddress: 'GA', role: 'ADMIN' }); - mockPrisma.invitation.findUnique.mockResolvedValue({ - id: 'inv-1', - email: 'test@example.com', - role: 'EMPLOYEE', - token: 'sometoken', - }); - mockPrisma.invitation.delete.mockResolvedValue({}); - - const res = await DELETE(deleteReq('sometoken'), { params: { token: 'sometoken' } }); - expect(res.status).toBe(200); - expect(mockPrisma.invitation.delete).toHaveBeenCalledWith({ - where: { token: 'sometoken' }, - }); - expect(mockPrisma.auditLog.create).toHaveBeenCalled(); - }); -}); \ No newline at end of file diff --git a/src/app/api/admin/audit-logs/route.ts b/src/app/api/admin/audit-logs/route.ts index fd202db..5e83d47 100644 --- a/src/app/api/admin/audit-logs/route.ts +++ b/src/app/api/admin/audit-logs/route.ts @@ -1,78 +1,75 @@ /** - * GET /api/admin/audit-logs + * GET /api/admin/audit-logs β€” the caller's organization's audit trail. * - * Lists audit log entries for the Admin Audit Log viewer. - * Supports optional filtering by action type and pagination. + * ── What changed and why ───────────────────────────────────────────────────── + * This read the global, tenant-less `AuditLog` table, so any platform admin saw + * every organization's activity: who approved what, for how much, for whom. An + * audit trail is among the most sensitive data in the product β€” it names the + * people holding approval authority β€” so it is now scoped to the caller's own + * organization and served from the tenant-owned `AuditEvent` model. * - * Restricted to ADMIN role only. + * Legacy `AuditLog` rows are NOT exposed here. They predate organizations and + * cannot be attributed to one; surfacing them to whichever tenant happened to ask + * would be the leak this endpoint just closed. */ import { NextRequest, NextResponse } from 'next/server'; import prisma from '@/lib/db/prisma'; -import { getUserFromRequest, isAdmin } from '@/lib/auth'; +import { withTenant } from '@/lib/tenancy/http'; + +const MAX_LIMIT = 200; export async function GET(request: NextRequest) { - const user = await getUserFromRequest(request); - if (!user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - if (!isAdmin(user)) { - return NextResponse.json( - { error: 'Forbidden: ADMIN role required' }, - { status: 403 } + return withTenant(request, { permission: 'audit:read', parseBody: false }, async ({ ctx }) => { + const url = new URL(request.url); + const limit = Math.min( + Math.max(parseInt(url.searchParams.get('limit') || '50', 10) || 50, 1), + MAX_LIMIT ); - } - - try { - const { searchParams } = new URL(request.url); - const limit = Math.min(parseInt(searchParams.get('limit') || '50', 10), 200); - const cursor = searchParams.get('cursor'); - const action = searchParams.get('action'); - - const whereClause: any = {}; - if (action) { - whereClause.action = action; - } - - if (cursor) { - whereClause.id = { lt: cursor }; - } + const type = url.searchParams.get('type'); + const actor = url.searchParams.get('actor'); + const cursor = url.searchParams.get('cursor'); - const logs = await prisma.auditLog.findMany({ - where: whereClause, - take: limit + 1, + const events = await prisma.auditEvent.findMany({ + where: { + // Scope is part of the query, never a check afterwards. + orgId: ctx.orgId, + ...(type ? { type } : {}), + ...(actor ? { actorAddress: actor } : {}), + ...(cursor ? { createdAt: { lt: new Date(cursor) } } : {}), + }, orderBy: { createdAt: 'desc' }, + take: limit + 1, + select: { + id: true, type: true, actorAddress: true, actorSystem: true, + paymentId: true, batchId: true, escrowId: true, + previousState: true, newState: true, txHash: true, + metadata: true, createdAt: true, + }, }); - const hasNextPage = logs.length > limit; - const items = hasNextPage ? logs.slice(0, limit) : logs; - const nextCursor = hasNextPage ? items[items.length - 1].id : null; - - // Parse metadata JSON for each log entry - const mappedLogs = items.map((log) => ({ - id: log.id, - action: log.action, - actor: log.actor, - target: log.target, - metadata: log.metadata ? JSON.parse(log.metadata) : null, - createdAt: log.createdAt.toISOString(), - })); + const hasMore = events.length > limit; + const page = hasMore ? events.slice(0, limit) : events; - return NextResponse.json( - { - logs: mappedLogs, - pagination: { - hasNextPage, - nextCursor, - }, - }, - { status: 200 } - ); - } catch (error) { - console.error('[admin/audit-logs] GET error:', error); - return NextResponse.json( - { error: 'Failed to fetch audit logs' }, - { status: 500 } - ); - } -} \ No newline at end of file + return NextResponse.json({ + organization: { id: ctx.orgId, name: ctx.orgName }, + logs: page.map((e) => ({ + id: e.id, + action: e.type, + // A machine actor is labelled as such, so an automated transition is + // never read as a person's decision. + actor: e.actorAddress ?? e.actorSystem ?? 'system', + actorIsSystem: !e.actorAddress, + paymentId: e.paymentId, + batchId: e.batchId, + escrowId: e.escrowId, + previousState: e.previousState, + newState: e.newState, + txHash: e.txHash, + metadata: e.metadata, + createdAt: e.createdAt.toISOString(), + })), + nextCursor: hasMore ? page[page.length - 1].createdAt.toISOString() : null, + }); + }); +} diff --git a/src/app/api/admin/bootstrap/route.ts b/src/app/api/admin/bootstrap/route.ts index 124f13a..d7895d2 100644 --- a/src/app/api/admin/bootstrap/route.ts +++ b/src/app/api/admin/bootstrap/route.ts @@ -1,8 +1,50 @@ +/** + * POST /api/admin/bootstrap β€” create the very first ADMIN. + * + * This endpoint is deliberately exempt from session auth (there is no admin yet + * to authenticate as), which makes BOOTSTRAP_SECRET the only thing standing + * between an anonymous caller and full platform control. It is therefore + * hardened three ways: + * + * 1. RATE LIMITED per client IP. Without a brake, the secret is simply + * brute-forceable at network speed β€” an unlimited oracle for guessing. + * 2. CONSTANT-TIME comparison. `!==` on strings short-circuits at the first + * differing byte, which leaks the length of a correct prefix. + * 3. MINIMUM LENGTH. A short secret is guessable regardless of the above, so + * the endpoint refuses to operate rather than offering false assurance. + * + * Every outcome returns the same 404 body, so a caller cannot distinguish "the + * endpoint is disabled" from "your secret was wrong" from "you are rate + * limited" β€” none of those should be observable. + */ + import { NextRequest } from 'next/server'; +import { createHash, timingSafeEqual } from 'crypto'; import { ApiResponse } from '@/lib/api-response'; import prisma from '@/lib/db/prisma'; import { Role } from '@prisma/client'; import { audit } from '@/lib/audit'; +import { rateLimit, clientIp } from '@/lib/ratelimit'; + +/** Shortest secret this endpoint will accept. */ +const MIN_SECRET_LENGTH = 32; + +/** Attempts allowed per IP per window. */ +const BOOTSTRAP_ATTEMPT_LIMIT = 5; +const BOOTSTRAP_WINDOW_MS = 60 * 60 * 1000; // 1 hour + +/** + * Compare two secrets without leaking where they diverge. + * + * Both sides are hashed to a fixed length first: `timingSafeEqual` throws on + * length mismatch, and catching that would reintroduce exactly the length + * oracle this is meant to remove. + */ +function secretsMatch(provided: string, expected: string): boolean { + const a = createHash('sha256').update(provided).digest(); + const b = createHash('sha256').update(expected).digest(); + return timingSafeEqual(a, b); +} export async function POST(req: NextRequest) { const secret = process.env.BOOTSTRAP_SECRET; @@ -12,11 +54,34 @@ export async function POST(req: NextRequest) { return ApiResponse.notFound('Endpoint not available'); } + // A secret too short to resist guessing is worse than no endpoint: it reads + // as protection while providing none. + if (secret.trim().length < MIN_SECRET_LENGTH) { + console.error( + `[Bootstrap] BOOTSTRAP_SECRET is shorter than ${MIN_SECRET_LENGTH} characters; endpoint disabled.` + ); + return ApiResponse.notFound('Endpoint not available'); + } + + // Rate limit BEFORE comparing, so failed guesses consume the budget. + const rl = rateLimit(`bootstrap:${clientIp(req)}`, BOOTSTRAP_ATTEMPT_LIMIT, BOOTSTRAP_WINDOW_MS); + if (!rl.ok) { + await audit('admin.bootstrap.throttled', { + actor: clientIp(req), + metadata: { retryAfter: rl.retryAfter }, + }); + return ApiResponse.notFound('Endpoint not available'); + } + try { const body = await req.json().catch(() => ({})); const requestSecret = req.headers.get('x-bootstrap-secret') || body.bootstrapSecret; - if (requestSecret !== secret) { + if (typeof requestSecret !== 'string' || !secretsMatch(requestSecret, secret)) { + await audit('admin.bootstrap.denied', { + actor: clientIp(req), + metadata: { reason: 'invalid bootstrap secret' }, + }); return ApiResponse.notFound('Endpoint not available'); } diff --git a/src/app/api/admin/invitations/[token]/route.ts b/src/app/api/admin/invitations/[token]/route.ts deleted file mode 100644 index 79bb166..0000000 --- a/src/app/api/admin/invitations/[token]/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * DELETE /api/admin/invitations/[token] - * - * Revokes an active invitation by its token. The invitation row is deleted - * from the database so the onboarding link becomes immediately invalid. - * - * Restricted to ADMIN role only. - */ - -import { NextRequest, NextResponse } from 'next/server'; -import prisma from '@/lib/db/prisma'; -import { getUserFromRequest, isAdmin } from '@/lib/auth'; -import { audit } from '@/lib/audit'; - -export async function DELETE( - request: NextRequest, - { params }: { params: { token: string } } -) { - const user = await getUserFromRequest(request); - if (!user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - if (!isAdmin(user)) { - return NextResponse.json( - { error: 'Forbidden: ADMIN role required' }, - { status: 403 } - ); - } - - try { - const invitation = await prisma.invitation.findUnique({ - where: { token: params.token }, - }); - - if (!invitation) { - return NextResponse.json( - { error: 'Invitation not found' }, - { status: 404 } - ); - } - - await prisma.invitation.delete({ - where: { token: params.token }, - }); - - await audit('invitation.revoke', { - actor: user.walletAddress, - target: invitation.email, - metadata: { token: params.token, role: invitation.role }, - }); - - return NextResponse.json( - { message: 'Invitation revoked successfully' }, - { status: 200 } - ); - } catch (error) { - console.error('[admin/invitations/token] DELETE error:', error); - return NextResponse.json( - { error: 'Failed to revoke invitation' }, - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/src/app/api/admin/invitations/route.ts b/src/app/api/admin/invitations/route.ts deleted file mode 100644 index 2bd6a57..0000000 --- a/src/app/api/admin/invitations/route.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import prisma from '@/lib/db/prisma'; -import { getUserFromRequest, isAdmin, Role } from '@/lib/auth'; -import { audit } from '@/lib/audit'; -import crypto from 'crypto'; -import { z } from 'zod'; -import { parseBody } from '@/lib/validation/schemas'; - -const createInviteSchema = z.object({ - email: z.string().email(), - role: z.enum(['ADMIN', 'EMPLOYEE']).default('EMPLOYEE'), -}); - -export async function GET(request: NextRequest) { - const user = await getUserFromRequest(request); - if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - if (!isAdmin(user)) return NextResponse.json({ error: 'Forbidden: ADMIN role required' }, { status: 403 }); - - try { - const invitations = await prisma.invitation.findMany({ - orderBy: { createdAt: 'desc' }, - take: 100, - }); - return NextResponse.json({ invitations }, { status: 200 }); - } catch (error) { - console.error('[admin/invitations] GET error:', error); - return NextResponse.json({ error: 'Failed to fetch invitations' }, { status: 500 }); - } -} - -export async function POST(request: NextRequest) { - const user = await getUserFromRequest(request); - if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - if (!isAdmin(user)) return NextResponse.json({ error: 'Forbidden: ADMIN role required' }, { status: 403 }); - - try { - const body = await request.json().catch(() => null); - const parsed = parseBody(createInviteSchema, body); - if (!parsed.ok) { - return NextResponse.json({ error: parsed.error }, { status: 400 }); - } - const { email, role } = parsed.data; - - // Check if invitation already exists for this email - const existing = await prisma.invitation.findUnique({ where: { email } }); - if (existing && !existing.usedAt && existing.expiresAt > new Date()) { - return NextResponse.json( - { invitation: existing, message: 'Existing active invitation retrieved' }, - { status: 200 } - ); - } - - const token = crypto.randomBytes(32).toString('hex'); - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days - - const invitation = await prisma.invitation.upsert({ - where: { email }, - create: { - email, - role: role as Role, - token, - expiresAt, - }, - update: { - role: role as Role, - token, - expiresAt, - usedAt: null, - }, - }); - - await audit('invitation.create', { - actor: user.walletAddress, - target: email, - metadata: { role, token, expiresAt: expiresAt.toISOString() }, - }); - - return NextResponse.json({ invitation }, { status: 201 }); - } catch (error) { - console.error('[admin/invitations] POST error:', error); - return NextResponse.json({ error: 'Failed to create invitation' }, { status: 500 }); - } -} diff --git a/src/app/api/escrows/[id]/status/route.ts b/src/app/api/escrows/[id]/status/route.ts deleted file mode 100644 index f9b7360..0000000 --- a/src/app/api/escrows/[id]/status/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import prisma from '@/lib/db/prisma'; -import { getUserFromRequest, isAdmin } from '@/lib/auth'; -import { parseBody, statusPatchSchema } from '@/lib/validation/schemas'; -import { audit } from '@/lib/audit'; - -export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) { - // Role guard β€” only ADMIN can update escrow status (approve/reject/finalize/cancel) - const user = await getUserFromRequest(request); - if (!user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - if (!isAdmin(user)) { - return NextResponse.json( - { error: 'Forbidden: only ADMIN can update escrow status' }, - { status: 403 } - ); - } - - try { - const onChainId = parseInt(params.id, 10); - if (isNaN(onChainId)) { - return NextResponse.json({ error: 'Invalid escrow ID' }, { status: 400 }); - } - - const body = await request.json().catch(() => null); - const parsed = parseBody(statusPatchSchema, body); - if (!parsed.ok) { - return NextResponse.json({ error: parsed.error }, { status: 400 }); - } - const { status, managerApproved, financeApproved, rejectionReason } = parsed.data; - - const updated = await prisma.escrow.update({ - where: { onChainId }, - data: { - ...(status && { status }), - ...(managerApproved !== undefined && { managerApproved }), - ...(financeApproved !== undefined && { financeApproved }), - ...(rejectionReason !== undefined && { rejectionReason }), - }, - }); - - await audit(status === 'rejected' ? 'escrow.reject' : 'escrow.status_update', { - actor: user.walletAddress, - target: String(onChainId), - metadata: { status, managerApproved, financeApproved, rejectionReason }, - }); - - return NextResponse.json({ escrow: updated }, { status: 200 }); - } catch (error) { - console.error(`Failed to update escrow ${params.id}:`, error); - return NextResponse.json({ error: 'Failed to update escrow' }, { status: 500 }); - } -} diff --git a/src/app/api/escrows/route.ts b/src/app/api/escrows/route.ts index cddf3c2..913e086 100644 --- a/src/app/api/escrows/route.ts +++ b/src/app/api/escrows/route.ts @@ -1,13 +1,32 @@ +/** + * GET /api/escrows β€” list escrows with their payments + * POST /api/escrows β€” record an escrow created on-chain + * + * ── Status of this endpoint ────────────────────────────────────────────────── + * Superseded by /api/batches and /api/payments, which expose the payment domain + * directly. It is retained, working, because the existing dashboard consumes its + * shape; it now reads through the Payment model rather than the single-payee + * columns that used to live on Escrow. + * + * The per-escrow response aggregates its payments. A caller wanting per-payment + * truth should use /api/payments β€” an escrow-shaped view of a multi-payee batch + * is exactly the lossiness this phase removed, so it is not reintroduced here as + * the primary interface. + */ + import { NextRequest, NextResponse } from 'next/server'; +import { PaymentState } from '@prisma/client'; import prisma from '@/lib/db/prisma'; import { getUserFromRequest, isAdmin } from '@/lib/auth'; +import { MembershipStatus } from '@prisma/client'; +import { can } from '@/lib/tenancy/rbac'; import { parseBody, createEscrowSchema } from '@/lib/validation/schemas'; import { audit } from '@/lib/audit'; +import { SAC_DECIMALS, formatAmountWithSeparators, sumAmounts } from '@/lib/money'; +import { describeState } from '@/lib/payments/state-machine'; +import { rollupBatch } from '@/lib/payments/service'; export async function GET(request: NextRequest) { - // Auth guard β€” all authenticated users can list escrows. - // EMPLOYEES see only their own escrows (filtered by workerPubKey). - // ADMINS see all escrows. const user = await getUserFromRequest(request); if (!user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); @@ -18,74 +37,125 @@ export async function GET(request: NextRequest) { const limit = Math.min(parseInt(searchParams.get('limit') || '10', 10), 50); const cursor = searchParams.get('cursor'); - const whereClause: any = isAdmin(user) - ? {} // Admin sees everything - : { workerPubKey: user.walletAddress }; // Employee sees only their own + // TENANT SCOPE FIRST. + // + // This previously returned `{}` for a platform ADMIN β€” every organization's + // escrows, to anyone holding that flag. Platform-level admin is not a licence + // to read other tenants' payroll, so the query is scoped to organizations the + // caller is an ACTIVE member of, and then narrowed by role within them. + const memberships = await prisma.orgMember.findMany({ + where: { userId: user.userId, status: MembershipStatus.ACTIVE }, + select: { orgId: true, role: true }, + }); + if (memberships.length === 0) { + return NextResponse.json({ escrows: [], nextCursor: null }); + } + + // Organizations where this caller may read payroll at all. A WORKER holds no + // organization-wide read, so they see only escrows that pay them. + const readableOrgIds = memberships.filter((m) => can(m.role, 'escrow:read')).map((m) => m.orgId); + const payeeOnlyOrgIds = memberships.filter((m) => !can(m.role, 'escrow:read')).map((m) => m.orgId); + + const whereClause: any = { + OR: [ + ...(readableOrgIds.length ? [{ orgId: { in: readableOrgIds } }] : []), + ...(payeeOnlyOrgIds.length + ? [{ + orgId: { in: payeeOnlyOrgIds }, + payments: { some: { recipientAddress: user.walletAddress } }, + }] + : []), + ], + }; + if (whereClause.OR.length === 0) { + return NextResponse.json({ escrows: [], nextCursor: null }); + } if (cursor) { - whereClause.id = { lt: parseInt(cursor, 10) }; + whereClause.createdAt = { lt: new Date(cursor) }; } const escrows = await prisma.escrow.findMany({ where: whereClause, - take: limit + 1, // Fetch limit + 1 to check if there is a next page - orderBy: { id: 'desc' }, - include: { timeLogs: true }, + take: limit + 1, + orderBy: { createdAt: 'desc' }, + include: { + payments: { + orderBy: { onChainPaymentIndex: 'asc' }, + select: { + id: true, recipientAddress: true, onChainPaymentIndex: true, + amountBaseUnits: true, rateBaseUnits: true, hours: true, + assetDecimals: true, assetCode: true, state: true, + settlementTxHash: true, settledAt: true, + }, + }, + }, }); const hasNextPage = escrows.length > limit; const items = hasNextPage ? escrows.slice(0, limit) : escrows; - const nextCursor = hasNextPage ? items[items.length - 1].id : null; + const nextCursor = hasNextPage + ? items[items.length - 1].createdAt.toISOString() + : null; + + const mappedEscrows = items.map((e) => { + const rollup = rollupBatch(e.payments); + const total = sumAmounts(e.payments.map((p) => p.amountBaseUnits)); + const decimals = e.assetDecimals ?? SAC_DECIMALS; + const totalHours = e.payments.reduce((acc, p) => acc + p.hours, 0n); - const mappedEscrows = items.map((e: any) => { - const totalHours = e.timeLogs.reduce((acc: number, log: any) => acc + log.hoursLogged, 0); return { - id: e.onChainId || e.id, - worker: - e.workerPubKey.length >= 10 - ? `${e.workerPubKey.slice(0, 6)}...${e.workerPubKey.slice(-4)}` - : e.workerPubKey, - amount: (e.amountCents / 100).toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - currency: e.currency, + id: e.onChainId ?? e.id, + escrowId: e.id, + onChainId: e.onChainId, + contractId: e.contractId, + network: e.network, + // Aggregate across payees, rendered at the asset's own precision. + amount: formatAmountWithSeparators(total, decimals), + amountBaseUnits: total.toString(), + currency: e.payments[0]?.assetCode ?? 'USDC', + paymentCount: e.payments.length, hoursLogged: totalHours.toString(), - status: e.status, + // Derived from the payments, never a stored rollup. + status: rollup.headline, manager_approved: e.managerApproved, finance_approved: e.financeApproved, - hours_verified: totalHours > 0, - rejectionReason: e.rejectionReason, + hours_verified: e.payments.every( + (p) => p.state !== PaymentState.AWAITING_ORACLE + ), + cancelled: e.cancelled, created_at: e.createdAt.toISOString(), isMock: false, + payments: e.payments.map((p) => ({ + id: p.id, + index: p.onChainPaymentIndex, + recipient: p.recipientAddress, + amount: formatAmountWithSeparators(p.amountBaseUnits, p.assetDecimals), + amountBaseUnits: p.amountBaseUnits.toString(), + hours: p.hours.toString(), + state: p.state, + stateLabel: describeState(p.state).label, + txHash: p.settlementTxHash, + settledAt: p.settledAt?.toISOString() ?? null, + })), }; }); - return NextResponse.json( - { - escrows: mappedEscrows, - pagination: { - hasNextPage, - nextCursor, - }, - }, - { status: 200 } - ); + return NextResponse.json({ escrows: mappedEscrows, nextCursor }); } catch (error) { - console.error('Failed to fetch escrows:', error); - return NextResponse.json({ error: 'Failed to fetch escrows' }, { status: 500 }); + console.error('Failed to list escrows:', error); + return NextResponse.json({ error: 'Failed to list escrows' }, { status: 500 }); } } export async function POST(request: NextRequest) { - // Auth guard β€” only ADMIN can create escrow records const user = await getUserFromRequest(request); if (!user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } if (!isAdmin(user)) { return NextResponse.json( - { error: 'Forbidden: only ADMIN can create escrows' }, + { error: 'Forbidden: this action requires the ADMIN role' }, { status: 403 } ); } @@ -96,25 +166,91 @@ export async function POST(request: NextRequest) { if (!parsed.ok) { return NextResponse.json({ error: parsed.error }, { status: 400 }); } - const { onChainId, workerPubKey, amountCents, rateCents, tokenAddress } = parsed.data; - - const escrow = await prisma.escrow.create({ - data: { - onChainId: onChainId ?? null, - workerPubKey, - amountCents, - rateCents, - tokenAddress: tokenAddress ?? null, - }, + const { + onChainId, workerPubKey, financeApprover, + amountBaseUnits, rateBaseUnits, assetDecimals, tokenAddress, + } = parsed.data; + + // A recorded escrow needs an organization and a batch to hold its payment. + // The membership the caller already has determines which. + const membership = await prisma.orgMember.findFirst({ + where: { user: { walletAddress: user.walletAddress } }, + orderBy: { createdAt: 'asc' }, + select: { orgId: true }, + }); + if (!membership) { + return NextResponse.json( + { error: 'You are not a member of any organization.' }, + { status: 409 } + ); + } + + const amount = BigInt(amountBaseUnits); + const rate = BigInt(rateBaseUnits); + + const created = await prisma.$transaction(async (tx) => { + const escrow = await tx.escrow.create({ + data: { + orgId: membership.orgId, + onChainId: onChainId ?? null, + contractId: process.env.NEXT_PUBLIC_STELLAR_CONTRACT_ID ?? '', + network: process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? 'testnet', + managerAddress: user.walletAddress, + financeApproverAddress: financeApprover ?? '', + tokenAddress: tokenAddress ?? null, + assetDecimals, + totalAmountBaseUnits: amount, + }, + }); + + const reference = `ESC-${String(onChainId ?? 0).padStart(5, '0')}-${escrow.id.slice(-6)}`; + const batch = await tx.payrollBatch.create({ + data: { orgId: membership.orgId, reference, uploadedBy: user.walletAddress }, + }); + + const payment = await tx.payment.create({ + data: { + orgId: membership.orgId, + batchId: batch.id, + escrowId: escrow.id, + recipientAddress: workerPubKey, + onChainPaymentIndex: 0, + assetContractId: tokenAddress ?? null, + assetDecimals, + amountBaseUnits: amount, + rateBaseUnits: rate, + hours: rate > 0n ? amount / rate : 0n, + // The client has submitted the on-chain creation; the indexer confirms + // funding and advances the state. Nothing here asserts settlement. + state: PaymentState.VALIDATING, + }, + }); + + return { escrow, batch, payment }; }); await audit('escrow.create', { actor: user.walletAddress, - target: String(escrow.id), - metadata: { onChainId: escrow.onChainId }, + target: created.escrow.id, + metadata: { onChainId: created.escrow.onChainId, paymentId: created.payment.id }, }); - return NextResponse.json({ escrow }, { status: 201 }); + return NextResponse.json( + { + escrow: { + id: created.escrow.id, + onChainId: created.escrow.onChainId, + totalAmountBaseUnits: created.escrow.totalAmountBaseUnits.toString(), + }, + batch: { id: created.batch.id, reference: created.batch.reference }, + payment: { + id: created.payment.id, + state: created.payment.state, + amountBaseUnits: created.payment.amountBaseUnits.toString(), + }, + }, + { status: 201 } + ); } catch (error: any) { if (error?.code === 'P2002') { return NextResponse.json({ message: 'Escrow already indexed' }, { status: 200 }); diff --git a/src/app/api/hours/route.ts b/src/app/api/hours/route.ts index 968fdd1..a5fb996 100644 --- a/src/app/api/hours/route.ts +++ b/src/app/api/hours/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import prisma from '@/lib/db/prisma'; import { getUserFromRequest, isEmployee } from '@/lib/auth'; +import { resolveTenant, findEscrowByOnChainId } from '@/lib/tenancy/resolve'; import { parseBody, hoursSchema } from '@/lib/validation/schemas'; import { audit } from '@/lib/audit'; @@ -27,20 +28,39 @@ export async function POST(request: NextRequest) { } const { onChainId, hoursLogged, paymentId, txHash } = parsed.data; - const escrow = await prisma.escrow.findUnique({ where: { onChainId } }); + // TENANT SCOPE. `onChainId` comes from the contract, so the same id exists in + // other organizations on other deployments β€” resolving it globally would let + // a member of any organization log hours against another's escrow. + const orgId = + request.headers.get('x-organization-id') ?? + new URL(request.url).searchParams.get('orgId') ?? + (await prisma.orgMember + .findMany({ where: { userId: user.userId, status: 'ACTIVE' }, select: { orgId: true }, take: 2 }) + .then((m) => (m.length === 1 ? m[0].orgId : null))); - if (!escrow) { - return NextResponse.json({ error: 'Escrow not found in database index' }, { status: 404 }); + const tenant = await resolveTenant(prisma, user.userId, orgId); + if (!tenant.ok) { + return NextResponse.json({ error: tenant.message }, { status: tenant.status }); } + const owned = await findEscrowByOnChainId(prisma, tenant.value, onChainId); + if (!owned.ok) { + return NextResponse.json({ error: owned.message }, { status: owned.status }); + } + const escrow = owned.value; + const timeLog = await prisma.timeLog.create({ data: { escrowId: escrow.id, hoursLogged, paymentId, txHash }, }); - await prisma.escrow.update({ - where: { id: escrow.id }, - data: { status: 'pending_manager' }, - }); + // Deliberately NOT advancing payment state here. + // + // This endpoint records that a client submitted an hours proof. Whether the + // CONTRACT accepted it is a separate question, answered by the `hours/submit` + // event the indexer observes. Moving the payment to ORACLE_VERIFIED from here + // would let a client assert a verification the chain may have rejected β€” + // exactly the "frontend manufactures a successful state" failure the state + // machine exists to prevent. await audit('hours.submit', { actor: user.walletAddress, diff --git a/src/app/api/invitations/[token]/route.ts b/src/app/api/invitations/[token]/route.ts index 7a5df7d..23008a5 100644 --- a/src/app/api/invitations/[token]/route.ts +++ b/src/app/api/invitations/[token]/route.ts @@ -1,86 +1,99 @@ +/** + * GET /api/invitations/:token β€” what this invitation offers + * POST /api/invitations/:token β€” accept it + * + * Public by necessity: the recipient has no membership yet, so there is nothing to + * authorize against except the token itself. Acceptance still requires an + * authenticated wallet β€” a token proves you were invited, not who you are. + * + * ── Why every failure looks the same ──────────────────────────────────────── + * Expired, revoked, already-used and never-existed all return the same 404 body. + * Distinguishing them tells someone probing tokens which of their guesses were + * real, and a real-but-used token still reveals that an organization invited that + * address. + */ + import { NextRequest, NextResponse } from 'next/server'; import prisma from '@/lib/db/prisma'; -import { getUserFromRequest, Role } from '@/lib/auth'; -import { audit } from '@/lib/audit'; +import { getUserFromRequest } from '@/lib/auth'; +import { resolveInvitation, acceptInvitation } from '@/lib/tenancy/membership'; +import { permissionsFor } from '@/lib/tenancy/rbac'; +import { rateLimit, clientIp } from '@/lib/ratelimit'; -export async function GET(request: NextRequest, { params }: { params: { token: string } }) { - try { - const invitation = await prisma.invitation.findUnique({ - where: { token: params.token }, - }); +const NOT_FOUND = NextResponse.json( + { error: 'This invitation is not valid. Ask your administrator for a new one.' }, + { status: 404 } +); - if (!invitation) { - return NextResponse.json({ error: 'Invalid or expired invitation token' }, { status: 404 }); - } +export async function GET(_request: NextRequest, { params }: { params: { token: string } }) { + // Unauthenticated and guessable-by-construction, so brake it per IP. + const rl = rateLimit(`invite-read:${clientIp(_request)}`, 30, 60_000); + if (!rl.ok) return NOT_FOUND; - if (invitation.usedAt) { - return NextResponse.json({ error: 'This invitation link has already been used' }, { status: 400 }); - } + const resolved = await resolveInvitation(prisma, params.token); + if (!resolved.ok) { + console.warn(`[invitations] rejected lookup: ${resolved.reason}`); + return NOT_FOUND; + } - if (invitation.expiresAt < new Date()) { - return NextResponse.json({ error: 'This invitation has expired' }, { status: 400 }); - } + const org = await prisma.organization.findUnique({ + where: { id: resolved.value.orgId }, + select: { name: true, slug: true }, + }); - return NextResponse.json({ - invitation: { - email: invitation.email, - role: invitation.role, - expiresAt: invitation.expiresAt, - }, - }); - } catch (error) { - console.error('[invitations/token] GET error:', error); - return NextResponse.json({ error: 'Failed to validate invitation' }, { status: 500 }); - } + return NextResponse.json({ + invitation: { + // The organization NAME is shown so the recipient knows what they are + // joining. Its id is not: that is an internal identifier with no business + // meaning to an invitee. + organizationName: org?.name ?? 'an organization', + email: resolved.value.email, + role: resolved.value.orgRole, + permissions: permissionsFor(resolved.value.orgRole), + }, + }); } export async function POST(request: NextRequest, { params }: { params: { token: string } }) { + const rl = rateLimit(`invite-accept:${clientIp(request)}`, 10, 60_000); + if (!rl.ok) return NOT_FOUND; + + // A token says you were invited. It does not say who you are β€” that needs a + // wallet signature, so the membership is bound to a proven identity. const user = await getUserFromRequest(request); if (!user) { - return NextResponse.json({ error: 'Please connect and sign in with your wallet first' }, { status: 401 }); + return NextResponse.json( + { + error: 'Connect and sign in with your Stellar wallet to accept this invitation.', + code: 'AUTHENTICATION_REQUIRED', + }, + { status: 401 } + ); } - try { - const invitation = await prisma.invitation.findUnique({ - where: { token: params.token }, - }); + const result = await acceptInvitation(prisma, params.token, { + id: user.userId, + walletAddress: user.walletAddress, + }); - if (!invitation) { - return NextResponse.json({ error: 'Invalid invitation token' }, { status: 404 }); + if (!result.ok) { + // A 403 here is meaningful and safe: the caller is authenticated, and being + // told their membership was removed is information they already have. + if (result.status === 403) { + return NextResponse.json({ error: result.message }, { status: 403 }); } + return NOT_FOUND; + } - if (invitation.usedAt) { - return NextResponse.json({ error: 'This invitation link has already been redeemed' }, { status: 400 }); - } - - if (invitation.expiresAt < new Date()) { - return NextResponse.json({ error: 'This invitation link has expired' }, { status: 400 }); - } - - // Assign the role to the logged-in wallet user - const updatedUser = await prisma.user.update({ - where: { walletAddress: user.walletAddress }, - data: { role: invitation.role as Role }, - }); - - // Mark invitation as used - await prisma.invitation.update({ - where: { token: params.token }, - data: { usedAt: new Date() }, - }); - - await audit('invitation.accept', { - actor: user.walletAddress, - target: invitation.email, - metadata: { role: invitation.role, token: params.token }, - }); + const org = await prisma.organization.findUnique({ + where: { id: result.value.orgId }, + select: { id: true, name: true, slug: true }, + }); - return NextResponse.json({ - message: `Invitation accepted! Role set to ${updatedUser.role}`, - user: updatedUser, - }); - } catch (error) { - console.error('[invitations/token] POST error:', error); - return NextResponse.json({ error: 'Failed to redeem invitation' }, { status: 500 }); - } + return NextResponse.json({ + joined: true, + organization: org, + role: result.value.role, + permissions: permissionsFor(result.value.role), + }); } diff --git a/src/app/api/oracle/attest/route.ts b/src/app/api/oracle/attest/route.ts index 17a264d..e6bb74f 100644 --- a/src/app/api/oracle/attest/route.ts +++ b/src/app/api/oracle/attest/route.ts @@ -14,7 +14,10 @@ import { NextRequest, NextResponse } from 'next/server'; import prisma from '@/lib/db/prisma'; import { getUserFromRequest } from '@/lib/auth'; -import { signHoursProof, getOraclePublicKeyHex } from '@/lib/oracle'; +import { resolveTenant, findEscrowByOnChainId, requirePermission } from '@/lib/tenancy/resolve'; +import { signHoursProof, getOraclePublicKeyHex, type ProofContext } from '@/lib/oracle'; +import { CoreFlowClient } from '@/lib/contracts'; +import { STELLAR_CONFIG } from '@/lib/config'; import { parseBody, attestSchema } from '@/lib/validation/schemas'; import { rateLimit } from '@/lib/ratelimit'; @@ -47,10 +50,41 @@ export async function POST(request: NextRequest) { } const { onChainId, paymentId, hoursLogged, nonce } = parsed.data; + // ── Tenant scope ──────────────────────────────────────────────────────── + // An attestation unlocks settlement, so it must be scoped to an organization + // the caller actually belongs to. Without this, a member of any organization + // could request signatures for another's escrow simply by naming its id. + const orgId = + request.headers.get('x-organization-id') ?? + new URL(request.url).searchParams.get('orgId') ?? + (await prisma.orgMember + .findMany({ where: { userId: user.userId, status: 'ACTIVE' }, select: { orgId: true }, take: 2 }) + .then((m) => (m.length === 1 ? m[0].orgId : null))); + + const tenant = await resolveTenant(prisma, user.userId, orgId); + if (!tenant.ok) { + return NextResponse.json({ error: tenant.message }, { status: tenant.status }); + } + const denied = requirePermission(tenant.value, 'oracle:attest:request'); + if (denied) { + return NextResponse.json({ error: denied.message, code: denied.code }, { status: 403 }); + } + + // The escrow must be one of THIS organization's. Escrow ids come from the + // contract, so the same id exists in other tenants on other deployments. + const owned = await findEscrowByOnChainId(prisma, tenant.value, onChainId); + if (!owned.ok) { + return NextResponse.json({ error: owned.message }, { status: owned.status }); + } + // Idempotency: never sign the same (escrow, payment, nonce) twice. const existing = await prisma.oracleAttestation.findUnique({ where: { - escrowOnChainId_paymentId_nonce: { escrowOnChainId: onChainId, paymentId, nonce }, + escrowOnChainId_onChainPaymentIndex_nonce: { + escrowOnChainId: onChainId, + onChainPaymentIndex: paymentId, + nonce: BigInt(nonce), + }, }, }); if (existing) { @@ -60,15 +94,71 @@ export async function POST(request: NextRequest) { ); } - const signature = signHoursProof(onChainId, paymentId, hoursLogged, nonce); + // Schema v2 binds the attestation to the payee, asset, amount, period, + // contract and network. Those come from the on-chain payment row, never + // from the request, so a caller cannot steer a signature onto a payment + // the oracle was not asked about. + let escrow: Awaited>; + try { + escrow = await new CoreFlowClient().getEscrow(onChainId); + } catch { + return NextResponse.json( + { error: `Could not read escrow ${onChainId} on this network.` }, + { status: 502 } + ); + } + + if (escrow.manager !== user.walletAddress) { + return NextResponse.json( + { error: 'Only the escrow manager can request attestations for this escrow.' }, + { status: 403 } + ); + } + + const payment = escrow.payments[paymentId]; + if (!payment) { + return NextResponse.json( + { error: `Escrow ${onChainId} has no payment ${paymentId}.` }, + { status: 400 } + ); + } + + // The contract enforces `hours x rate == amount`; signing anything else + // produces a signature that can only ever be rejected on chain. + const expectedHours = payment.amount / payment.rate_per_hour; + if (BigInt(hoursLogged) !== expectedHours) { + return NextResponse.json( + { + error: + `Escrow ${onChainId} payment ${paymentId} is funded for ${payment.amount} ` + + `at ${payment.rate_per_hour}/hour, which is ${expectedHours} hours, not ${hoursLogged}.`, + }, + { status: 409 } + ); + } + + const ctx: ProofContext = { + networkPassphrase: STELLAR_CONFIG.getNetworkPassphrase(), + contractId: STELLAR_CONFIG.contract.id, + worker: payment.worker, + token: payment.token, + amount: payment.amount, + startDate: BigInt(payment.start_date), + endDate: BigInt(payment.end_date), + }; + + const signature = signHoursProof(ctx, onChainId, paymentId, hoursLogged, nonce); await prisma.oracleAttestation.create({ data: { + orgId: tenant.value.orgId, escrowOnChainId: onChainId, - paymentId, - hoursLogged, - nonce, + onChainPaymentIndex: paymentId, + hours: BigInt(hoursLogged), + nonce: BigInt(nonce), signature, + schema: 'CFWP-v2', + contractId: STELLAR_CONFIG.contract.id, createdBy: user.walletAddress, }, }); diff --git a/src/app/api/organizations/[id]/escrows/claim/route.ts b/src/app/api/organizations/[id]/escrows/claim/route.ts new file mode 100644 index 0000000..4e4bef6 --- /dev/null +++ b/src/app/api/organizations/[id]/escrows/claim/route.ts @@ -0,0 +1,151 @@ +/** + * POST /api/organizations/:id/escrows/claim β€” attribute an on-chain escrow to this + * organization. + * + * ── Why a claim exists at all ──────────────────────────────────────────────── + * The chain knows nothing about CoreFlow organizations. An escrow created outside + * the app β€” by the CLI, a validation script, or another client β€” has no tenant + * mapping, so the indexer records its events as unattributed and projects nothing. + * This is how such an escrow becomes visible, deliberately, to a named + * organization. + * + * ── What makes the claim safe ──────────────────────────────────────────────── + * The caller must prove, against LIVE CONTRACT STATE, that their wallet is the + * escrow's on-chain manager. Without that check, any organization could claim any + * escrow by naming its id and gain the payroll of whoever actually created it β€” + * the exact leak the unattributed-by-default rule exists to prevent. + * + * An escrow already claimed by another organization is reported as a conflict and + * never reassigned: silently moving it would transfer one tenant's financial + * records to another. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { writeMembershipAudit } from '@/lib/tenancy/membership'; +import { CoreFlowClient } from '@/lib/contracts'; +import { STELLAR_CONFIG } from '@/lib/config'; +import { SAC_DECIMALS } from '@/lib/money'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'escrow:create' }, async ({ ctx, body }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const onChainId = Number(body?.onChainId); + if (!Number.isInteger(onChainId) || onChainId < 1) { + return NextResponse.json( + { error: 'onChainId must be a positive integer.' }, + { status: 400 } + ); + } + + const contractId = STELLAR_CONFIG.contract.id; + const network = STELLAR_CONFIG.contract.network; + if (!contractId) { + return NextResponse.json( + { error: 'No contract is configured for this deployment.' }, + { status: 503 } + ); + } + + // Already mapped? Report it rather than reassigning. + const existing = await prisma.escrow.findFirst({ + where: { onChainId, contractId, network }, + select: { id: true, orgId: true }, + }); + if (existing) { + if (existing.orgId === ctx.orgId) { + return NextResponse.json( + { claimed: true, escrowId: existing.id, alreadyClaimed: true }, + { status: 200 } + ); + } + // Deliberately vague: confirming that ANOTHER organization holds it would + // disclose that a tenant we must not name exists and uses this escrow. + return NextResponse.json( + { + error: `Escrow ${onChainId} is not available to claim.`, + code: 'ESCROW_UNAVAILABLE', + }, + { status: 409 } + ); + } + + // The proof: live contract state must name this caller as manager. + let chainEscrow: Awaited>; + try { + chainEscrow = await new CoreFlowClient().getEscrow(onChainId); + } catch { + return NextResponse.json( + { error: `Escrow ${onChainId} could not be read from ${network}.` }, + { status: 502 } + ); + } + + if (chainEscrow.manager !== ctx.walletAddress) { + return NextResponse.json( + { + error: + 'Only the escrow’s on-chain manager can claim it. Sign in with the ' + + 'wallet that created the escrow.', + code: 'NOT_ON_CHAIN_MANAGER', + }, + { status: 403 } + ); + } + + const total = chainEscrow.payments.reduce((a, p) => a + p.amount, 0n); + + const escrow = await prisma.$transaction(async (tx) => { + const created = await tx.escrow.create({ + data: { + orgId: ctx.orgId, + onChainId, + contractId, + network, + managerAddress: chainEscrow.manager, + financeApproverAddress: chainEscrow.finance_approver, + oraclePublicKey: chainEscrow.oracle_pubkey ?? null, + tokenAddress: chainEscrow.payments[0]?.token ?? null, + assetDecimals: SAC_DECIMALS, + totalAmountBaseUnits: total, + managerApproved: chainEscrow.manager_approved, + financeApproved: chainEscrow.finance_approved, + cancelled: chainEscrow.cancelled, + oracleRotations: chainEscrow.oracle_rotations, + }, + }); + + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: 'escrow.claimed', + actorAddress: ctx.walletAddress, + metadata: { + onChainId, contractId, network, + paymentCount: chainEscrow.payments.length, + totalAmountBaseUnits: total.toString(), + }, + }); + + return created; + }); + + return NextResponse.json( + { + claimed: true, + escrowId: escrow.id, + onChainId, + paymentCount: chainEscrow.payments.length, + // Events that arrived before the claim were recorded unattributed; the + // next indexer run applies them now that a mapping exists. + note: + 'Run the indexer to project this escrow’s history. Events recorded ' + + 'before the claim are replayed, not lost.', + }, + { status: 201 } + ); + }); +} diff --git a/src/app/api/organizations/[id]/findings/[findingId]/route.ts b/src/app/api/organizations/[id]/findings/[findingId]/route.ts new file mode 100644 index 0000000..b13419b --- /dev/null +++ b/src/app/api/organizations/[id]/findings/[findingId]/route.ts @@ -0,0 +1,138 @@ +/** + * PATCH /api/organizations/:id/findings/:findingId β€” advance a finding's lifecycle. + * + * OPEN β†’ ACKNOWLEDGED β†’ INVESTIGATING β†’ RESOLVED + * + * ── Why resolution needs a reason ──────────────────────────────────────────── + * A "mark resolved" button with no explanation turns the findings queue into a + * dismiss button. The next person to look at a resolved CRITICAL finding β€” quite + * possibly during an incident β€” needs to know what was established and by whom. + * Resolution therefore requires a substantive reason and records the actor. + * + * ── What this endpoint cannot do ───────────────────────────────────────────── + * It cannot change a payment's state, amount, recipient or transaction hash. + * Resolving a finding records a human judgement ABOUT a discrepancy; it does not + * alter the financial record, and it certainly cannot manufacture a settlement. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { FindingStatus } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { recordAuditEvent } from '@/lib/payments/service'; + +/** Valid lifecycle moves. RESOLVED is terminal; reopening is a new finding. */ +const TRANSITIONS: Record = { + [FindingStatus.OPEN]: [FindingStatus.ACKNOWLEDGED, FindingStatus.INVESTIGATING, FindingStatus.RESOLVED], + [FindingStatus.ACKNOWLEDGED]: [FindingStatus.INVESTIGATING, FindingStatus.RESOLVED], + [FindingStatus.INVESTIGATING]: [FindingStatus.RESOLVED, FindingStatus.ACKNOWLEDGED], + [FindingStatus.RESOLVED]: [], +}; + +const MIN_RESOLUTION_LENGTH = 10; + +export async function PATCH( + request: NextRequest, + { params }: { params: { id: string; findingId: string } } +) { + return withTenant( + request, + { permission: 'reconciliation:resolve' }, + async ({ ctx, body }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const target = body?.status as FindingStatus | undefined; + if (!target || !(Object.values(FindingStatus) as string[]).includes(target)) { + return NextResponse.json( + { error: `status must be one of ${Object.values(FindingStatus).join(', ')}.` }, + { status: 400 } + ); + } + + // Scoped lookup: a finding id from another tenant is a 404. + const finding = await prisma.reconciliationFinding.findFirst({ + where: { id: params.findingId, orgId: ctx.orgId }, + }); + if (!finding) { + return NextResponse.json({ error: 'Finding not found.' }, { status: 404 }); + } + + if (finding.status === target) { + return NextResponse.json({ changed: false, status: finding.status }); + } + + if (!TRANSITIONS[finding.status].includes(target)) { + return NextResponse.json( + { + error: + `A ${finding.status} finding cannot become ${target}. ` + + `Valid next states: ${TRANSITIONS[finding.status].join(', ') || 'none'}.`, + code: 'INVALID_FINDING_TRANSITION', + }, + { status: 409 } + ); + } + + const reason = typeof body?.resolution === 'string' ? body.resolution.trim() : ''; + if (target === FindingStatus.RESOLVED && reason.length < MIN_RESOLUTION_LENGTH) { + return NextResponse.json( + { + error: + 'Resolving a finding requires an explanation of what was established. ' + + 'The next person to read this β€” possibly during an incident β€” needs to ' + + 'know why it was closed.', + code: 'RESOLUTION_REASON_REQUIRED', + }, + { status: 400 } + ); + } + + const now = new Date(); + const updated = await prisma.$transaction(async (tx) => { + const row = await tx.reconciliationFinding.update({ + where: { id: finding.id }, + data: { + status: target, + ...(target === FindingStatus.ACKNOWLEDGED + ? { acknowledgedAt: now, acknowledgedBy: ctx.walletAddress } + : {}), + ...(target === FindingStatus.RESOLVED + ? { resolvedAt: now, resolvedBy: ctx.walletAddress, resolution: reason } + : {}), + }, + }); + + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: `reconciliation.finding.${target.toLowerCase()}`, + actor: { kind: 'user', role: ctx.role, address: ctx.walletAddress }, + paymentId: finding.paymentId ?? undefined, + txHash: finding.txHash ?? undefined, + metadata: { + findingId: finding.id, + kind: finding.kind, + severity: finding.severity, + previousStatus: finding.status, + newStatus: target, + ...(reason ? { resolution: reason } : {}), + }, + }); + + return row; + }); + + return NextResponse.json({ + changed: true, + finding: { + id: updated.id, + status: updated.status, + acknowledgedBy: updated.acknowledgedBy, + resolvedBy: updated.resolvedBy, + resolution: updated.resolution, + }, + }); + } + ); +} diff --git a/src/app/api/organizations/[id]/findings/route.ts b/src/app/api/organizations/[id]/findings/route.ts new file mode 100644 index 0000000..e041757 --- /dev/null +++ b/src/app/api/organizations/[id]/findings/route.ts @@ -0,0 +1,113 @@ +/** + * GET /api/organizations/:id/findings β€” reconciliation findings for this tenant. + * + * Tenant-scoped like everything else. Findings name payments, amounts and + * recipients, so a cross-tenant leak here would disclose another organization's + * payroll. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { FindingStatus, FindingSeverity } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { formatAmountWithSeparators } from '@/lib/money'; +import { txUrl } from '@/lib/explorer'; + +const SEVERITY_ORDER: FindingSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] as FindingSeverity[]; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant( + request, + { permission: 'reconciliation:read', parseBody: false }, + async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const url = new URL(request.url); + const statusParam = url.searchParams.get('status'); + const severityParam = url.searchParams.get('severity'); + const limit = Math.min( + Math.max(parseInt(url.searchParams.get('limit') || '50', 10) || 50, 1), + 200 + ); + + if (statusParam && !(Object.values(FindingStatus) as string[]).includes(statusParam)) { + return NextResponse.json({ error: `Unknown status "${statusParam}".` }, { status: 400 }); + } + if (severityParam && !(Object.values(FindingSeverity) as string[]).includes(severityParam)) { + return NextResponse.json({ error: `Unknown severity "${severityParam}".` }, { status: 400 }); + } + + const findings = await prisma.reconciliationFinding.findMany({ + where: { + orgId: ctx.orgId, + // Unresolved by default: a findings queue defaults to "what still needs + // attention", not to a historical archive. + ...(statusParam + ? { status: statusParam as FindingStatus } + : { status: { not: FindingStatus.RESOLVED } }), + ...(severityParam ? { severity: severityParam as FindingSeverity } : {}), + }, + orderBy: [{ severity: 'asc' }, { detectedAt: 'asc' }], + take: limit, + include: { + payment: { + select: { + id: true, recipientAddress: true, amountBaseUnits: true, + assetDecimals: true, assetCode: true, state: true, + batch: { select: { id: true, reference: true } }, + }, + }, + run: { select: { correlationId: true, startedAt: true } }, + }, + }); + + // Severity enum order is alphabetical in Prisma, not by urgency. + const ordered = [...findings].sort( + (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity) + ); + + return NextResponse.json({ + findings: ordered.map((f) => ({ + id: f.id, + kind: f.kind, + status: f.status, + severity: f.severity, + detail: f.detail, + /** What to actually do. A finding without this is a puzzle. */ + remediation: f.remediation, + dbState: f.dbState, + chainState: f.chainState, + escrowOnChainId: f.escrowOnChainId, + paymentIndex: f.paymentIndex, + transaction: f.txHash + ? { hash: f.txHash, explorerUrl: txUrl(f.txHash) } + : null, + payment: f.payment + ? { + id: f.payment.id, + recipient: f.payment.recipientAddress, + amount: formatAmountWithSeparators( + f.payment.amountBaseUnits, + f.payment.assetDecimals + ), + assetCode: f.payment.assetCode, + state: f.payment.state, + batch: f.payment.batch, + } + : null, + detectedAt: f.detectedAt.toISOString(), + lastObservedAt: f.lastObservedAt.toISOString(), + observationCount: f.observationCount, + acknowledgedBy: f.acknowledgedBy, + acknowledgedAt: f.acknowledgedAt?.toISOString() ?? null, + resolvedBy: f.resolvedBy, + resolvedAt: f.resolvedAt?.toISOString() ?? null, + resolution: f.resolution, + detectedByRun: f.run?.correlationId ?? null, + })), + }); + } + ); +} diff --git a/src/app/api/organizations/[id]/invitations/[invitationId]/route.ts b/src/app/api/organizations/[id]/invitations/[invitationId]/route.ts new file mode 100644 index 0000000..0002332 --- /dev/null +++ b/src/app/api/organizations/[id]/invitations/[invitationId]/route.ts @@ -0,0 +1,59 @@ +/** + * DELETE /api/organizations/:id/invitations/:invitationId β€” revoke an invitation. + * + * Revocation is recorded (`revokedAt`), not deleted. Deleting it would erase the + * fact that someone was once invited, which is exactly what an access review + * needs to see. `revokedAt` is kept distinct from `usedAt` so "withdrawn" is never + * mistaken for "accepted". + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { writeMembershipAudit } from '@/lib/tenancy/membership'; + +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string; invitationId: string } } +) { + return withTenant(request, { permission: 'member:invite', parseBody: false }, async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + // Scoped by organization: an invitation id from another tenant is a 404, not + // a revocation of their invitation. + const invitation = await prisma.invitation.findFirst({ + where: { id: params.invitationId, orgId: ctx.orgId }, + }); + if (!invitation) { + return NextResponse.json({ error: 'Invitation not found.' }, { status: 404 }); + } + if (invitation.usedAt) { + return NextResponse.json( + { + error: + 'That invitation has already been accepted. Suspend or remove the ' + + 'member instead.', + code: 'ALREADY_ACCEPTED', + }, + { status: 409 } + ); + } + + await prisma.$transaction(async (tx) => { + await tx.invitation.update({ + where: { id: invitation.id }, + data: { revokedAt: new Date(), revokedBy: ctx.walletAddress }, + }); + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: 'invitation.revoked', + actorAddress: ctx.walletAddress, + metadata: { invitationId: invitation.id, email: invitation.email }, + }); + }); + + return NextResponse.json({ revoked: true }); + }); +} diff --git a/src/app/api/organizations/[id]/invitations/route.ts b/src/app/api/organizations/[id]/invitations/route.ts new file mode 100644 index 0000000..f6bb9c3 --- /dev/null +++ b/src/app/api/organizations/[id]/invitations/route.ts @@ -0,0 +1,140 @@ +/** + * GET /api/organizations/:id/invitations β€” list pending invitations + * POST /api/organizations/:id/invitations β€” invite someone + * + * The invited ROLE is validated against what the inviter may delegate. An + * invitation is the easiest place to smuggle a privilege escalation: without that + * check, an ADMIN could invite an OWNER and take the organization, or a MANAGER + * could invite a FINANCE approver and manufacture the second signature they are + * forbidden from giving. + * + * The plaintext token is returned ONCE, in the creation response. Only its hash is + * stored, so it cannot be recovered later β€” a database dump must not hand over + * working invitations. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { OrgRole } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { assignableRoles } from '@/lib/tenancy/rbac'; +import { + generateInvitationToken, hashInvitationToken, + checkRoleAssignment, writeMembershipAudit, INVITATION_TTL_DAYS, +} from '@/lib/tenancy/membership'; + +const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'member:read', parseBody: false }, async ({ ctx }) => { + if (ctx.orgId !== params.id) { + // The path names one organization and the resolved membership another. + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const invitations = await prisma.invitation.findMany({ + where: { orgId: ctx.orgId }, + orderBy: { createdAt: 'desc' }, + select: { + id: true, email: true, orgRole: true, expiresAt: true, + usedAt: true, revokedAt: true, invitedBy: true, createdAt: true, + }, + }); + + return NextResponse.json({ + // tokenHash is deliberately absent: it is a credential, not metadata. + invitations: invitations.map((i) => ({ + ...i, + status: i.revokedAt ? 'REVOKED' + : i.usedAt ? 'ACCEPTED' + : i.expiresAt.getTime() <= Date.now() ? 'EXPIRED' + : 'PENDING', + })), + /** What this caller may invite, so the UI need not re-derive the rules. */ + assignableRoles: assignableRoles(ctx.role), + }); + }); +} + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'member:invite' }, async ({ ctx, body }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const email = String(body?.email ?? '').trim().toLowerCase(); + if (!EMAIL.test(email)) { + return NextResponse.json({ error: 'A valid email address is required.' }, { status: 400 }); + } + + const requested = body?.orgRole; + if (!requested || !(Object.values(OrgRole) as string[]).includes(requested)) { + return NextResponse.json( + { error: `orgRole must be one of ${Object.values(OrgRole).join(', ')}.` }, + { status: 400 } + ); + } + const orgRole = requested as OrgRole; + + // The escalation guard. + const denied = checkRoleAssignment(ctx, orgRole); + if (denied) return denialResponse(denied); + + const token = generateInvitationToken(); + const tokenHash = hashInvitationToken(token); + const expiresAt = new Date(Date.now() + INVITATION_TTL_DAYS * 86_400_000); + + try { + const invitation = await prisma.$transaction(async (tx) => { + // Re-inviting the same address replaces the outstanding invitation rather + // than erroring, so a lost email is recoverable. The previous token stops + // working, which is the point: two live tokens for one seat is one too + // many. + const created = await tx.invitation.upsert({ + where: { orgId_email: { orgId: ctx.orgId, email } }, + create: { + orgId: ctx.orgId, email, orgRole, tokenHash, expiresAt, + invitedBy: ctx.walletAddress, + }, + update: { + orgRole, tokenHash, expiresAt, + usedAt: null, revokedAt: null, revokedBy: null, + invitedBy: ctx.walletAddress, + }, + }); + + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: 'invitation.sent', + actorAddress: ctx.walletAddress, + metadata: { email, orgRole, invitationId: created.id }, + }); + + return created; + }); + + return NextResponse.json( + { + invitation: { + id: invitation.id, + email: invitation.email, + orgRole: invitation.orgRole, + expiresAt: invitation.expiresAt, + }, + /** Shown once. Not recoverable β€” only the hash is stored. */ + token, + acceptUrl: `/invite/${token}`, + }, + { status: 201 } + ); + } catch (e: any) { + if (e?.code === 'P2002') { + return NextResponse.json( + { error: 'An invitation for that address already exists.' }, + { status: 409 } + ); + } + throw e; + } + }); +} diff --git a/src/app/api/organizations/[id]/members/[memberId]/route.ts b/src/app/api/organizations/[id]/members/[memberId]/route.ts new file mode 100644 index 0000000..3e5870f --- /dev/null +++ b/src/app/api/organizations/[id]/members/[memberId]/route.ts @@ -0,0 +1,201 @@ +/** + * PATCH /api/organizations/:id/members/:memberId β€” change role or status + * DELETE /api/organizations/:id/members/:memberId β€” remove from the organization + * + * Business actions, not a generic field setter: the accepted inputs are a `role` + * to grant and a `status` transition, each validated against the membership state + * machine and the delegation rules. A caller cannot write arbitrary columns. + * + * Three refusals are enforced here because each is a plausible mistake: + * - granting a role above your own, or one you may not exercise + * - changing your OWN role or status + * - removing or suspending the last active administrator + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { OrgRole, MembershipStatus } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { can } from '@/lib/tenancy/rbac'; +import { + checkRoleAssignment, checkNotSelf, checkNotLastAdministrator, + canTransitionMembership, membershipTransitionsFrom, writeMembershipAudit, +} from '@/lib/tenancy/membership'; + +async function loadMember(orgId: string, memberId: string) { + // Scoped by organization: a member id from another tenant is a 404. + return prisma.orgMember.findFirst({ where: { id: memberId, orgId } }); +} + +export async function PATCH( + request: NextRequest, + { params }: { params: { id: string; memberId: string } } +) { + return withTenant(request, {}, async ({ ctx, body }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const member = await loadMember(ctx.orgId, params.memberId); + if (!member) { + return NextResponse.json({ error: 'Member not found.' }, { status: 404 }); + } + + const selfDenial = checkNotSelf(ctx, member.userId); + if (selfDenial) return denialResponse(selfDenial); + + const wantsRole = body?.role as OrgRole | undefined; + const wantsStatus = body?.status as MembershipStatus | undefined; + + if (!wantsRole && !wantsStatus) { + return NextResponse.json( + { error: 'Provide a `role` to grant or a `status` transition.' }, + { status: 400 } + ); + } + + // ── Role change ────────────────────────────────────────────────────────── + if (wantsRole) { + if (!(Object.values(OrgRole) as string[]).includes(wantsRole)) { + return NextResponse.json({ error: `Unknown role "${wantsRole}".` }, { status: 400 }); + } + const denied = checkRoleAssignment(ctx, wantsRole); + if (denied) return denialResponse(denied); + + // Demoting the last administrator strands the organization just as surely + // as removing them. + if (member.role !== wantsRole) { + const lastAdmin = await checkNotLastAdministrator(prisma, ctx.orgId, member); + if (lastAdmin && !['OWNER', 'ADMIN'].includes(wantsRole)) { + return denialResponse(lastAdmin); + } + } + } + + // ── Status change ──────────────────────────────────────────────────────── + if (wantsStatus) { + if (!(Object.values(MembershipStatus) as string[]).includes(wantsStatus)) { + return NextResponse.json({ error: `Unknown status "${wantsStatus}".` }, { status: 400 }); + } + if (!canTransitionMembership(member.status, wantsStatus)) { + return NextResponse.json( + { + error: + `A ${member.status} membership cannot become ${wantsStatus}. ` + + `Valid next states: ${membershipTransitionsFrom(member.status).join(', ') || 'none'}.`, + code: 'INVALID_MEMBERSHIP_TRANSITION', + }, + { status: 409 } + ); + } + const suspendGuard = requiresAdminCover(wantsStatus) + ? await checkNotLastAdministrator(prisma, ctx.orgId, member) + : null; + if (suspendGuard) return denialResponse(suspendGuard); + + const permission = wantsStatus === MembershipStatus.REMOVED ? 'member:remove' : 'member:suspend'; + if (!can(ctx.role, permission)) { + return NextResponse.json( + { error: `Your role (${ctx.role}) cannot perform this action.`, code: 'PERMISSION_DENIED' }, + { status: 403 } + ); + } + } + + const now = new Date(); + const updated = await prisma.$transaction(async (tx) => { + const row = await tx.orgMember.update({ + where: { id: member.id }, + data: { + ...(wantsRole ? { role: wantsRole } : {}), + ...(wantsStatus + ? { + status: wantsStatus, + ...(wantsStatus === MembershipStatus.ACTIVE + ? { activatedAt: now, suspendedAt: null } + : {}), + ...(wantsStatus === MembershipStatus.SUSPENDED ? { suspendedAt: now } : {}), + ...(wantsStatus === MembershipStatus.REMOVED ? { removedAt: now } : {}), + } + : {}), + }, + }); + + if (wantsRole && wantsRole !== member.role) { + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: 'member.role.changed', + actorAddress: ctx.walletAddress, + targetUserId: member.userId, + metadata: { from: member.role, to: wantsRole, memberId: member.id }, + }); + } + if (wantsStatus && wantsStatus !== member.status) { + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: `member.${String(wantsStatus).toLowerCase()}`, + actorAddress: ctx.walletAddress, + targetUserId: member.userId, + metadata: { from: member.status, to: wantsStatus, memberId: member.id }, + }); + } + return row; + }); + + return NextResponse.json({ + member: { id: updated.id, role: updated.role, status: updated.status }, + }); + }); +} + +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string; memberId: string } } +) { + return withTenant(request, { permission: 'member:remove', parseBody: false }, async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const member = await loadMember(ctx.orgId, params.memberId); + if (!member) { + return NextResponse.json({ error: 'Member not found.' }, { status: 404 }); + } + + const selfDenial = checkNotSelf(ctx, member.userId); + if (selfDenial) return denialResponse(selfDenial); + + const lastAdmin = await checkNotLastAdministrator(prisma, ctx.orgId, member); + if (lastAdmin) return denialResponse(lastAdmin); + + if (!canTransitionMembership(member.status, MembershipStatus.REMOVED)) { + return NextResponse.json( + { error: 'That membership is already removed.', code: 'INVALID_MEMBERSHIP_TRANSITION' }, + { status: 409 } + ); + } + + await prisma.$transaction(async (tx) => { + // Soft removal. Hard-deleting the row would erase who held approval + // authority and when β€” the first thing an access review asks for. + await tx.orgMember.update({ + where: { id: member.id }, + data: { status: MembershipStatus.REMOVED, removedAt: new Date() }, + }); + await writeMembershipAudit(tx, { + orgId: ctx.orgId, + type: 'member.removed', + actorAddress: ctx.walletAddress, + targetUserId: member.userId, + metadata: { role: member.role, memberId: member.id }, + }); + }); + + return NextResponse.json({ removed: true }); + }); +} + +/** SUSPENDED and REMOVED both reduce the pool of usable administrators. */ +function requiresAdminCover(status: MembershipStatus): boolean { + return status === MembershipStatus.SUSPENDED || status === MembershipStatus.REMOVED; +} diff --git a/src/app/api/organizations/[id]/members/route.ts b/src/app/api/organizations/[id]/members/route.ts new file mode 100644 index 0000000..787d50f --- /dev/null +++ b/src/app/api/organizations/[id]/members/route.ts @@ -0,0 +1,42 @@ +/** + * GET /api/organizations/:id/members β€” the organization's roster. + * + * Scoped to the caller's own organization. A member list is sensitive: it names + * who holds approval authority, which is the information an attacker wants before + * choosing a target. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { assignableRoles } from '@/lib/tenancy/rbac'; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'member:read', parseBody: false }, async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const members = await prisma.orgMember.findMany({ + where: { orgId: ctx.orgId }, + include: { user: { select: { id: true, walletAddress: true } } }, + orderBy: [{ role: 'asc' }, { createdAt: 'asc' }], + }); + + return NextResponse.json({ + members: members.map((m) => ({ + id: m.id, + userId: m.userId, + walletAddress: m.user.walletAddress, + role: m.role, + status: m.status, + isYou: m.userId === ctx.userId, + invitedAt: m.invitedAt, + activatedAt: m.activatedAt, + suspendedAt: m.suspendedAt, + removedAt: m.removedAt, + })), + assignableRoles: assignableRoles(ctx.role), + }); + }); +} diff --git a/src/app/api/organizations/[id]/reconciliation/route.ts b/src/app/api/organizations/[id]/reconciliation/route.ts new file mode 100644 index 0000000..6f02dad --- /dev/null +++ b/src/app/api/organizations/[id]/reconciliation/route.ts @@ -0,0 +1,87 @@ +/** + * GET /api/organizations/:id/reconciliation β€” operational health + * POST /api/organizations/:id/reconciliation β€” trigger a run for this organization + * + * Membership-scoped. A run is a read-heavy operation that can also correct the + * projection, so triggering it requires `reconciliation:resolve` rather than mere + * read access. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant } from '@/lib/tenancy/http'; +import { STELLAR_CONFIG } from '@/lib/config'; +import { reconciliationHealth, runReconciliation } from '@/lib/reconciliation/scheduler'; + +export const maxDuration = 300; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant( + request, + { permission: 'reconciliation:read', parseBody: false }, + async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const health = await reconciliationHealth(prisma, ctx.orgId); + + const recentRuns = await prisma.reconciliationRun.findMany({ + where: { orgId: ctx.orgId }, + orderBy: { startedAt: 'desc' }, + take: 10, + select: { + id: true, correlationId: true, status: true, scope: true, + startedAt: true, completedAt: true, + escrowsExamined: true, paymentsExamined: true, + agreed: true, mismatched: true, unreadable: true, + chainAhead: true, databaseAhead: true, + findingsOpened: true, correctionsApplied: true, errorMessage: true, + }, + }); + + const bySeverity = await prisma.reconciliationFinding.groupBy({ + by: ['severity'], + where: { orgId: ctx.orgId, status: { not: 'RESOLVED' } }, + _count: true, + }); + + return NextResponse.json({ + health, + recentRuns, + openBySeverity: Object.fromEntries( + bySeverity.map((r: any) => [r.severity, r._count]) + ), + }); + } + ); +} + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant( + request, + { permission: 'reconciliation:resolve' }, + async ({ ctx }) => { + if (ctx.orgId !== params.id) { + return NextResponse.json({ error: 'Organization not found.' }, { status: 404 }); + } + + const result = await runReconciliation(prisma, ctx.orgId, { + contractId: STELLAR_CONFIG.contract.id || undefined, + network: STELLAR_CONFIG.contract.network, + }); + + if ('skipped' in result) { + return NextResponse.json( + { + skipped: true, + reason: 'A reconciliation run is already in progress for this organization.', + runId: result.runId, + }, + { status: 409 } + ); + } + return NextResponse.json(result, { status: 200 }); + } + ); +} diff --git a/src/app/api/organizations/__tests__/invitations.route.test.ts b/src/app/api/organizations/__tests__/invitations.route.test.ts new file mode 100644 index 0000000..501076c --- /dev/null +++ b/src/app/api/organizations/__tests__/invitations.route.test.ts @@ -0,0 +1,219 @@ +// @vitest-environment node +/** + * Org-scoped invitation route tests. + * + * Replaces the previous platform-admin invitation tests: that endpoint had no + * organization scope, so "is this admin allowed to revoke this invitation" had no + * answer beyond "they are an admin somewhere". + * + * The intent of the old tests is preserved (401 unauthenticated, 403 wrong role, + * 404 missing, success path) and extended with the cases that only exist once + * tenancy does: cross-tenant revocation, and role escalation via invitation. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { OrgRole, MembershipStatus } from '@prisma/client'; + +// Defined inside the factory: vi.mock is hoisted above any top-level variable. +vi.mock('@/lib/db/prisma', () => { + const prisma: any = { + orgMember: { findUnique: vi.fn(), findMany: vi.fn() }, + organization: { findUnique: vi.fn() }, + invitation: { findMany: vi.fn(), findFirst: vi.fn(), upsert: vi.fn(), update: vi.fn() }, + auditEvent: { create: vi.fn() }, + }; + prisma.$transaction = vi.fn(async (fn: any) => fn(prisma)); + return { default: prisma }; +}); +vi.mock('@/lib/auth', () => ({ getUserFromRequest: vi.fn() })); + +import { GET, POST } from '../[id]/invitations/route'; +import { DELETE } from '../[id]/invitations/[invitationId]/route'; +import { getUserFromRequest } from '@/lib/auth'; +import prismaDefault from '@/lib/db/prisma'; + +const prismaMock = prismaDefault as any; + +const mockUser = getUserFromRequest as unknown as ReturnType; +const ORG = 'orgA'; + +function signedInAs(role: OrgRole, userId = 'u1', orgId = ORG) { + mockUser.mockResolvedValue({ userId, walletAddress: 'G' + 'A'.repeat(55), role: 'EMPLOYEE' }); + prismaMock.orgMember.findUnique.mockResolvedValue({ + orgId, userId, role, status: MembershipStatus.ACTIVE, + org: { id: orgId, name: 'Org A', slug: 'org-a' }, + user: { walletAddress: 'G' + 'A'.repeat(55) }, + }); + prismaMock.orgMember.findMany.mockResolvedValue([{ orgId, role }]); +} + +function req(body?: unknown, orgId = ORG) { + return new Request(`http://localhost/api/organizations/${orgId}/invitations`, { + method: body ? 'POST' : 'GET', + headers: { 'Content-Type': 'application/json', 'x-organization-id': orgId }, + ...(body ? { body: JSON.stringify(body) } : {}), + }) as any; +} + +beforeEach(() => { + vi.clearAllMocks(); + prismaMock.invitation.findMany.mockResolvedValue([]); + prismaMock.organization.findUnique.mockResolvedValue({ id: ORG, name: 'Org A', slug: 'org-a' }); +}); + +describe('POST /api/organizations/:id/invitations', () => { + it('401 when unauthenticated', async () => { + mockUser.mockResolvedValue(null); + const res = await POST(req({ email: 'a@b.com', orgRole: 'VIEWER' }), { params: { id: ORG } }); + expect(res.status).toBe(401); + }); + + it('403 for a role that cannot invite', async () => { + signedInAs(OrgRole.VIEWER); + const res = await POST(req({ email: 'a@b.com', orgRole: 'VIEWER' }), { params: { id: ORG } }); + expect(res.status).toBe(403); + }); + + it('creates an invitation and returns the token exactly once', async () => { + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.upsert.mockResolvedValue({ + id: 'inv1', email: 'a@b.com', orgRole: OrgRole.MANAGER, expiresAt: new Date(), + }); + + const res = await POST(req({ email: 'A@B.com', orgRole: 'MANAGER' }), { params: { id: ORG } }); + const body = await res.json(); + + expect(res.status).toBe(201); + expect(body.token).toBeTruthy(); + expect(body.acceptUrl).toContain(body.token); + + // Only the HASH is persisted β€” a database dump must not yield live tokens. + const stored = prismaMock.invitation.upsert.mock.calls[0][0]; + expect(stored.create.tokenHash).toBeTruthy(); + expect(stored.create.tokenHash).not.toBe(body.token); + expect(JSON.stringify(stored)).not.toContain(body.token); + // Email normalized, so re-inviting the same person is recognised as such. + expect(stored.where.orgId_email.email).toBe('a@b.com'); + }); + + it('refuses an ADMIN inviting an OWNER', async () => { + // Otherwise an admin can hand themselves, or an accomplice, the organization. + signedInAs(OrgRole.ADMIN); + const res = await POST(req({ email: 'a@b.com', orgRole: 'OWNER' }), { params: { id: ORG } }); + const body = await res.json(); + + expect(res.status).toBe(403); + expect(body.code).toBe('ROLE_ESCALATION_REFUSED'); + expect(prismaMock.invitation.upsert).not.toHaveBeenCalled(); + }); + + it('refuses an invitation naming a different organization than the caller’s', async () => { + signedInAs(OrgRole.ADMIN, 'u1', ORG); + const res = await POST(req({ email: 'a@b.com', orgRole: 'VIEWER' }, ORG), { + params: { id: 'orgB' }, + }); + expect(res.status).toBe(404); + expect(prismaMock.invitation.upsert).not.toHaveBeenCalled(); + }); + + it('rejects a malformed email and an unknown role', async () => { + signedInAs(OrgRole.ADMIN); + expect((await POST(req({ email: 'nope', orgRole: 'VIEWER' }), { params: { id: ORG } })).status).toBe(400); + expect((await POST(req({ email: 'a@b.com', orgRole: 'GOD' }), { params: { id: ORG } })).status).toBe(400); + }); +}); + +describe('GET /api/organizations/:id/invitations', () => { + it('never returns the token hash', async () => { + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.findMany.mockResolvedValue([{ + id: 'inv1', email: 'a@b.com', orgRole: OrgRole.VIEWER, + expiresAt: new Date(Date.now() + 86400000), usedAt: null, revokedAt: null, + invitedBy: 'GX', createdAt: new Date(), + }]); + + const body = await (await GET(req(), { params: { id: ORG } })).json(); + + expect(body.invitations[0].status).toBe('PENDING'); + expect(JSON.stringify(body)).not.toContain('tokenHash'); + }); + + it('labels expired, accepted and revoked invitations distinctly', async () => { + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.findMany.mockResolvedValue([ + { id: '1', email: 'a@b.com', orgRole: 'VIEWER', expiresAt: new Date(Date.now() - 1000), usedAt: null, revokedAt: null, createdAt: new Date() }, + { id: '2', email: 'c@b.com', orgRole: 'VIEWER', expiresAt: new Date(Date.now() + 1000), usedAt: new Date(), revokedAt: null, createdAt: new Date() }, + { id: '3', email: 'd@b.com', orgRole: 'VIEWER', expiresAt: new Date(Date.now() + 1000), usedAt: null, revokedAt: new Date(), createdAt: new Date() }, + ]); + + const body = await (await GET(req(), { params: { id: ORG } })).json(); + expect(body.invitations.map((i: any) => i.status)).toEqual(['EXPIRED', 'ACCEPTED', 'REVOKED']); + }); + + it('tells the caller which roles they may invite', async () => { + signedInAs(OrgRole.ADMIN); + const body = await (await GET(req(), { params: { id: ORG } })).json(); + expect(body.assignableRoles).not.toContain(OrgRole.OWNER); + expect(body.assignableRoles).toContain(OrgRole.MANAGER); + }); +}); + +describe('DELETE /api/organizations/:id/invitations/:invitationId', () => { + const delReq = (orgId = ORG) => + new Request(`http://localhost/api/organizations/${orgId}/invitations/inv1`, { + method: 'DELETE', + headers: { 'x-organization-id': orgId }, + }) as any; + + it('401 when unauthenticated', async () => { + mockUser.mockResolvedValue(null); + const res = await DELETE(delReq(), { params: { id: ORG, invitationId: 'inv1' } }); + expect(res.status).toBe(401); + }); + + it('403 for a role that cannot invite', async () => { + signedInAs(OrgRole.VIEWER); + const res = await DELETE(delReq(), { params: { id: ORG, invitationId: 'inv1' } }); + expect(res.status).toBe(403); + }); + + it('404 when the invitation does not exist in this organization', async () => { + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.findFirst.mockResolvedValue(null); + const res = await DELETE(delReq(), { params: { id: ORG, invitationId: 'inv1' } }); + expect(res.status).toBe(404); + }); + + it('revokes rather than deletes, preserving the record', async () => { + // Deleting would erase that someone was ever invited β€” the first thing an + // access review asks for. + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.findFirst.mockResolvedValue({ + id: 'inv1', orgId: ORG, email: 'a@b.com', usedAt: null, + }); + + const res = await DELETE(delReq(), { params: { id: ORG, invitationId: 'inv1' } }); + + expect(res.status).toBe(200); + expect(prismaMock.invitation.update).toHaveBeenCalled(); + expect(prismaMock.invitation.update.mock.calls[0][0].data.revokedAt).toBeInstanceOf(Date); + expect(prismaMock.auditEvent.create).toHaveBeenCalled(); + }); + + it('refuses to revoke an already-accepted invitation', async () => { + signedInAs(OrgRole.ADMIN); + prismaMock.invitation.findFirst.mockResolvedValue({ + id: 'inv1', orgId: ORG, email: 'a@b.com', usedAt: new Date(), + }); + const res = await DELETE(delReq(), { params: { id: ORG, invitationId: 'inv1' } }); + const body = await res.json(); + expect(res.status).toBe(409); + expect(body.code).toBe('ALREADY_ACCEPTED'); + }); + + it('refuses a cross-tenant revocation', async () => { + signedInAs(OrgRole.ADMIN, 'u1', ORG); + const res = await DELETE(delReq(ORG), { params: { id: 'orgB', invitationId: 'inv1' } }); + expect(res.status).toBe(404); + expect(prismaMock.invitation.update).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/api/organizations/route.ts b/src/app/api/organizations/route.ts new file mode 100644 index 0000000..89c444b --- /dev/null +++ b/src/app/api/organizations/route.ts @@ -0,0 +1,123 @@ +/** + * GET /api/organizations β€” the caller's workspaces + * POST /api/organizations β€” create one, becoming its first OWNER + * + * Creation is the only place a user may grant themselves a privileged role, and + * it is safe precisely because the organization does not exist yet: there is no + * existing tenant whose authority is being escalated. Every later grant goes + * through the delegation rules in tenancy/rbac. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { OrgRole, MembershipStatus } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { listTenants } from '@/lib/tenancy/resolve'; +import { permissionsFor } from '@/lib/tenancy/rbac'; +import { writeMembershipAudit } from '@/lib/tenancy/membership'; + +/** Lowercase, hyphenated, no leading/trailing hyphen. */ +function slugify(name: string): string { + return name + .toLowerCase() + .normalize('NFKD') + .replace(/[^\p{Letter}\p{Number}]+/gu, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 48); +} + +export async function GET(request: NextRequest) { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const orgs = await listTenants(prisma, user.userId); + return NextResponse.json({ + organizations: orgs.map((o) => ({ + ...o, + // The UI renders from permissions, never the reverse. Shipping them here + // keeps the client from re-deriving the rules and drifting from the server. + permissions: permissionsFor(o.role), + })), + }); +} + +export async function POST(request: NextRequest) { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const body = await request.json().catch(() => ({})); + const name = String(body?.name ?? '').trim(); + + if (name.length < 2 || name.length > 80) { + return NextResponse.json( + { error: 'Organization name must be between 2 and 80 characters.' }, + { status: 400 } + ); + } + + const base = slugify(name); + if (!base) { + return NextResponse.json( + { error: 'Organization name must contain at least one letter or number.' }, + { status: 400 } + ); + } + + try { + const result = await prisma.$transaction(async (tx) => { + // Slugs are public-ish identifiers, so a collision gets a suffix rather + // than revealing that some other tenant already took the name. + let slug = base; + for (let i = 2; i < 50; i++) { + const taken = await tx.organization.findUnique({ where: { slug } }); + if (!taken) break; + slug = `${base}-${i}`; + } + + const org = await tx.organization.create({ data: { name, slug } }); + + const member = await tx.orgMember.create({ + data: { + orgId: org.id, + userId: user.userId, + role: OrgRole.OWNER, + status: MembershipStatus.ACTIVE, + activatedAt: new Date(), + }, + }); + + await writeMembershipAudit(tx, { + orgId: org.id, + type: 'organization.created', + actorAddress: user.walletAddress, + targetUserId: user.userId, + metadata: { name, slug, role: OrgRole.OWNER }, + }); + + return { org, member }; + }); + + return NextResponse.json( + { + organization: { + id: result.org.id, + name: result.org.name, + slug: result.org.slug, + }, + role: result.member.role, + permissions: permissionsFor(result.member.role), + }, + { status: 201 } + ); + } catch (e: any) { + console.error('[organizations] create failed:', e?.message); + return NextResponse.json( + { error: 'The organization could not be created.' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/payments/[id]/approve/route.ts b/src/app/api/payments/[id]/approve/route.ts new file mode 100644 index 0000000..8f1e67d --- /dev/null +++ b/src/app/api/payments/[id]/approve/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/approve + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { approvePayment } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, approvePayment); +} diff --git a/src/app/api/payments/[id]/cancel/route.ts b/src/app/api/payments/[id]/cancel/route.ts new file mode 100644 index 0000000..c825795 --- /dev/null +++ b/src/app/api/payments/[id]/cancel/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/cancel + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { cancelPayment } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, cancelPayment); +} diff --git a/src/app/api/payments/[id]/reconcile/route.ts b/src/app/api/payments/[id]/reconcile/route.ts new file mode 100644 index 0000000..8aa9f81 --- /dev/null +++ b/src/app/api/payments/[id]/reconcile/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/reconcile + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { flagForReconciliation } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, flagForReconciliation); +} diff --git a/src/app/api/payments/[id]/reject/route.ts b/src/app/api/payments/[id]/reject/route.ts new file mode 100644 index 0000000..a53ad87 --- /dev/null +++ b/src/app/api/payments/[id]/reject/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/reject + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { rejectPayment } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, rejectPayment); +} diff --git a/src/app/api/payments/[id]/retry/route.ts b/src/app/api/payments/[id]/retry/route.ts new file mode 100644 index 0000000..91c2329 --- /dev/null +++ b/src/app/api/payments/[id]/retry/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/retry + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { retryPayment } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, retryPayment); +} diff --git a/src/app/api/payments/[id]/route.ts b/src/app/api/payments/[id]/route.ts new file mode 100644 index 0000000..64014f4 --- /dev/null +++ b/src/app/api/payments/[id]/route.ts @@ -0,0 +1,126 @@ +/** + * GET /api/payments/:id β€” one payment, with its full history. + * + * Returns the audit trail alongside current state. A "current status only" view + * cannot answer how a payment reached that status, which is the question asked + * whenever something has gone wrong. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { resolveMembership, canRead, findPaymentForMember } from '@/lib/payments/authz'; +import { describeState, transitionsFrom } from '@/lib/payments/state-machine'; +import { formatAmountWithSeparators } from '@/lib/money'; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const url = new URL(request.url); + const orgId = request.headers.get('x-organization-id') ?? url.searchParams.get('orgId'); + if (!orgId) { + return NextResponse.json( + { error: 'An organization is required (X-Organization-Id header or ?orgId).' }, + { status: 400 } + ); + } + + const membership = await resolveMembership(prisma, user.userId, orgId); + if (!membership.ok) { + return NextResponse.json({ error: membership.message }, { status: membership.status }); + } + if (!canRead(membership.value.role)) { + return NextResponse.json({ error: 'Your role cannot read payment data.' }, { status: 403 }); + } + + const found = await findPaymentForMember(prisma, membership.value, params.id, { + batch: true, + escrow: true, + worker: true, + approvals: { orderBy: { createdAt: 'asc' } }, + attestations: { orderBy: { createdAt: 'asc' } }, + transactions: { orderBy: { createdAt: 'asc' } }, + auditEvents: { orderBy: { createdAt: 'asc' } }, + findings: { orderBy: { detectedAt: 'desc' } }, + }); + if (!found.ok) { + return NextResponse.json({ error: found.message }, { status: found.status }); + } + + const p = found.value; + const d = describeState(p.state); + + return NextResponse.json({ + payment: { + id: p.id, + recipient: p.recipientAddress, + onChainPaymentIndex: p.onChainPaymentIndex, + asset: { code: p.assetCode, contractId: p.assetContractId, decimals: p.assetDecimals }, + amount: formatAmountWithSeparators(p.amountBaseUnits, p.assetDecimals), + amountBaseUnits: p.amountBaseUnits.toString(), + rateBaseUnits: p.rateBaseUnits.toString(), + hours: p.hours.toString(), + periodStart: p.periodStart?.toISOString() ?? null, + periodEnd: p.periodEnd?.toISOString() ?? null, + state: p.state, + stateLabel: d.label, + stateDescription: d.description, + tone: d.tone, + needsAttention: d.needsAttention, + stateReason: p.stateReason, + transactionHash: d.mayHaveTransaction ? p.settlementTxHash : null, + settledAt: p.settledAt?.toISOString() ?? null, + createdAt: p.createdAt.toISOString(), + }, + batch: p.batch + ? { id: p.batch.id, reference: p.batch.reference } + : null, + escrow: p.escrow + ? { + id: p.escrow.id, + onChainId: p.escrow.onChainId, + contractId: p.escrow.contractId, + network: p.escrow.network, + managerApproved: p.escrow.managerApproved, + financeApproved: p.escrow.financeApproved, + cancelled: p.escrow.cancelled, + } + : null, + approvals: p.approvals, + attestations: p.attestations.map((a: any) => ({ + id: a.id, + schema: a.schema, + hours: a.hours.toString(), + nonce: a.nonce.toString(), + preimageSha256: a.preimageSha256, + createdAt: a.createdAt.toISOString(), + })), + transactions: p.transactions.map((t: any) => ({ + id: t.id, kind: t.kind, status: t.status, hash: t.hash, + attempt: t.attempt, ledger: t.ledger, errorMessage: t.errorMessage, + createdAt: t.createdAt.toISOString(), + })), + history: p.auditEvents.map((e: any) => ({ + id: e.id, type: e.type, + actor: e.actorAddress ?? e.actorSystem, + previousState: e.previousState, newState: e.newState, + txHash: e.txHash, metadata: e.metadata, + at: e.createdAt.toISOString(), + })), + findings: p.findings.map((f: any) => ({ + id: f.id, kind: f.kind, dbState: f.dbState, chainState: f.chainState, + detail: f.detail, detectedAt: f.detectedAt.toISOString(), + resolvedAt: f.resolvedAt?.toISOString() ?? null, + })), + /** What this caller may do next, so the UI need not re-derive the rules. */ + availableActions: transitionsFrom(p.state) + .filter((t) => t.actors.includes('user') && t.roles?.includes(membership.value.role)) + .map((t) => ({ to: t.to, reason: t.reason })), + }); +} diff --git a/src/app/api/payments/[id]/submit/route.ts b/src/app/api/payments/[id]/submit/route.ts new file mode 100644 index 0000000..c4bd773 --- /dev/null +++ b/src/app/api/payments/[id]/submit/route.ts @@ -0,0 +1,17 @@ +/** + * POST /api/payments/:id/submit + * + * A business action. The server decides the resulting state β€” see + * src/lib/payments/state-machine.ts for the transition table, and + * docs/PAYMENT_STATE_MACHINE.md for who may do what. + */ +import { NextRequest } from 'next/server'; +import { submitPaymentForSettlement } from '@/lib/payments/actions'; +import { runPaymentAction } from '@/lib/payments/http'; + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + return runPaymentAction(request, params.id, submitPaymentForSettlement); +} diff --git a/src/app/api/payments/route.ts b/src/app/api/payments/route.ts new file mode 100644 index 0000000..dd0fb92 --- /dev/null +++ b/src/app/api/payments/route.ts @@ -0,0 +1,101 @@ +/** + * GET /api/payments β€” list payments, scoped to the caller's organization. + * + * Tenant scoping is part of the query, not a check after the fact: a payment id + * belonging to another organization is indistinguishable from one that does not + * exist. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { PaymentState } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { resolveMembership, canRead } from '@/lib/payments/authz'; +import { describeState } from '@/lib/payments/state-machine'; +import { formatAmountWithSeparators } from '@/lib/money'; + +const VALID_STATES = new Set(Object.values(PaymentState)); + +export async function GET(request: NextRequest) { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const url = new URL(request.url); + const orgId = request.headers.get('x-organization-id') ?? url.searchParams.get('orgId'); + if (!orgId) { + return NextResponse.json( + { error: 'An organization is required (X-Organization-Id header or ?orgId).' }, + { status: 400 } + ); + } + + const membership = await resolveMembership(prisma, user.userId, orgId); + if (!membership.ok) { + return NextResponse.json({ error: membership.message }, { status: membership.status }); + } + if (!canRead(membership.value.role)) { + return NextResponse.json( + { error: 'Your role cannot read payment data.' }, + { status: 403 } + ); + } + + const stateParam = url.searchParams.get('state'); + if (stateParam && !VALID_STATES.has(stateParam)) { + return NextResponse.json({ error: `Unknown state "${stateParam}".` }, { status: 400 }); + } + + const limit = Math.min(parseInt(url.searchParams.get('limit') || '50', 10), 200); + const batchId = url.searchParams.get('batchId'); + + const payments = await prisma.payment.findMany({ + where: { + orgId: membership.value.orgId, + ...(stateParam ? { state: stateParam as PaymentState } : {}), + ...(batchId ? { batchId } : {}), + }, + orderBy: [{ createdAt: 'desc' }], + take: limit, + include: { + batch: { select: { id: true, reference: true } }, + escrow: { select: { onChainId: true, contractId: true, network: true } }, + approvals: { select: { role: true, decision: true, actorAddress: true, createdAt: true } }, + _count: { select: { attestations: true, transactions: true } }, + }, + }); + + return NextResponse.json({ + payments: payments.map((p) => { + const d = describeState(p.state); + return { + id: p.id, + batch: p.batch, + escrowOnChainId: p.escrow?.onChainId ?? null, + network: p.escrow?.network ?? null, + recipient: p.recipientAddress, + onChainPaymentIndex: p.onChainPaymentIndex, + asset: { code: p.assetCode, contractId: p.assetContractId, decimals: p.assetDecimals }, + amount: formatAmountWithSeparators(p.amountBaseUnits, p.assetDecimals), + amountBaseUnits: p.amountBaseUnits.toString(), + rateBaseUnits: p.rateBaseUnits.toString(), + hours: p.hours.toString(), + state: p.state, + stateLabel: d.label, + stateDescription: d.description, + tone: d.tone, + needsAttention: d.needsAttention, + stateReason: p.stateReason, + // A transaction reference is surfaced only where one can actually exist. + // Showing an explorer link for an unsubmitted payment invites a reader to + // believe something settled. + transactionHash: d.mayHaveTransaction ? p.settlementTxHash : null, + settledAt: p.settledAt?.toISOString() ?? null, + approvals: p.approvals, + counts: p._count, + createdAt: p.createdAt.toISOString(), + }; + }), + }); +} diff --git a/src/app/api/payroll/__tests__/batches.route.test.ts b/src/app/api/payroll/__tests__/batches.route.test.ts new file mode 100644 index 0000000..5e953e3 --- /dev/null +++ b/src/app/api/payroll/__tests__/batches.route.test.ts @@ -0,0 +1,781 @@ +// @vitest-environment node +/** + * Bulk Pay route tests. + * + * These are UNIT tests. The Prisma client is replaced by the in-memory fake, which + * enforces the unique constraints and required columns that carry the idempotency + * and tenancy guarantees β€” so a test can observe those being relied upon rather + * than trusting a canned return value. + * + * They do NOT constitute database-backed validation. No migration has been applied + * and no real Postgres has seen these queries; composite foreign keys, partial + * indexes and column types remain unverified against a real database. That work is + * BLOCKED on a local development database. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { OrgRole, MembershipStatus, PaymentState, ApprovalDecision } from '@prisma/client'; +import type { FakeDb } from '@/lib/payments/__tests__/fake-db'; + +// The factory is HOISTED above every import, so it must not close over a module +// variable β€” `const db = createFakeDb()` above this line is not yet initialized +// when the factory runs. The fake is therefore built inside the factory and read +// back from the mocked module afterwards. +vi.mock('@/lib/db/prisma', async () => { + const { createFakeDb } = await import('@/lib/payments/__tests__/fake-db'); + return { default: createFakeDb() }; +}); +vi.mock('@/lib/auth', () => ({ getUserFromRequest: vi.fn() })); + +import { POST as createBatchRoute, GET as listBatchesRoute } from '../batches/route'; +import { POST as validateCsvRoute } from '../batches/validate/route'; +import { GET as getBatchRoute } from '../batches/[id]/route'; +import { POST as revalidateRoute } from '../batches/[id]/validate/route'; +import { POST as approveBatchRoute } from '../batches/[id]/approve/route'; +import { getUserFromRequest } from '@/lib/auth'; +import { __resetRateLimiter } from '@/lib/ratelimit'; +import prismaDefault from '@/lib/db/prisma'; + +/** The same instance the routes use, so assertions read the rows they wrote. */ +const db = prismaDefault as unknown as FakeDb; + +const mockUser = getUserFromRequest as unknown as ReturnType; + +const ORG_A = 'orgA'; +const ORG_B = 'orgB'; +const TOKEN = 'CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M'; + +function addr(tag: string): string { + return ('G' + tag.toUpperCase().replace(/[^A-Z2-7]/g, '')).padEnd(56, 'A'); +} + +const HEADER = 'recipient,amount,asset,hours,rate,period_start,period_end'; +/** A pay period, required because the oracle attests to it. */ +const PERIOD = '2026-09-01,2026-09-15'; +const GOOD_CSV = [ + HEADER, + `${addr('alice')},1000,USDC,40,25,${PERIOD}`, + `${addr('bob')},1600,USDC,80,20,${PERIOD}`, + `${addr('carol')},260,USDC,20,13,${PERIOD}`, +].join('\n'); + +/** Seed an organization and a member. */ +function seedMember(orgId: string, userId: string, role: OrgRole, wallet: string) { + if (!db.__tables.organization.rows.some((o) => o.id === orgId)) { + db.__tables.organization.rows.push({ id: orgId, name: orgId, slug: orgId }); + } + if (!db.__tables.user.rows.some((u) => u.id === userId)) { + db.__tables.user.rows.push({ id: userId, walletAddress: wallet, role: 'EMPLOYEE' }); + } + db.__tables.orgMember.rows.push({ + id: `ogm_${orgId}_${userId}`, + orgId, + userId, + role, + status: MembershipStatus.ACTIVE, + createdAt: new Date(), + }); +} + +function signedInAs(userId: string, wallet: string) { + mockUser.mockResolvedValue({ userId, walletAddress: wallet, role: 'EMPLOYEE' }); +} + +function post(url: string, body: unknown, headers: Record = {}): any { + return new Request(url, { + method: 'POST', + headers: { 'content-type': 'application/json', ...headers }, + body: JSON.stringify(body), + }); +} + +function get(url: string, headers: Record = {}): any { + return new Request(url, { method: 'GET', headers }); +} + +/** JSON.stringify refuses bigint, and every monetary column is one. */ +function snapshotRows(rows: readonly Record[]): string { + return JSON.stringify(rows, (_k, v) => (typeof v === 'bigint' ? v.toString() : v)); +} + +const URL_BATCHES = 'https://app.test/api/payroll/batches'; + +beforeEach(() => { + vi.clearAllMocks(); + __resetRateLimiter(); + for (const t of Object.values(db.__tables)) t.rows.length = 0; + + process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID = TOKEN; + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'USDC'; + + // Two organizations, so every test can check isolation rather than assuming it. + seedMember(ORG_A, 'u_admin_a', OrgRole.ADMIN, addr('adminA')); + seedMember(ORG_A, 'u_manager_a', OrgRole.MANAGER, addr('managerA')); + seedMember(ORG_A, 'u_finance_a', OrgRole.FINANCE, addr('financeA')); + seedMember(ORG_A, 'u_worker_a', OrgRole.WORKER, addr('workerA')); + seedMember(ORG_A, 'u_viewer_a', OrgRole.VIEWER, addr('viewerA')); + seedMember(ORG_B, 'u_admin_b', OrgRole.ADMIN, addr('adminB')); +}); + +// --------------------------------------------------------------------------- + +describe('POST /api/payroll/batches β€” authentication and authorization', () => { + it('refuses an unauthenticated request', async () => { + mockUser.mockResolvedValue(null); + const res = await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV })); + expect(res.status).toBe(401); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); + + it('refuses a role without payroll:create', async () => { + signedInAs('u_viewer_a', addr('viewerA')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(403); + expect((await res.json()).code).toBe('PERMISSION_DENIED'); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); + + it('refuses a WORKER, who holds no organization permissions at all', async () => { + signedInAs('u_worker_a', addr('workerA')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(403); + }); + + it('does not let a member of org B create a batch in org A', async () => { + signedInAs('u_admin_b', addr('adminB')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + // A non-enumerating 404: naming an organization you do not belong to is + // indistinguishable from naming one that does not exist. + expect(res.status).toBe(404); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); +}); + +describe('POST /api/payroll/batches β€” request validation', () => { + beforeEach(() => signedInAs('u_admin_a', addr('adminA'))); + + it('rejects a non-JSON content type before reading the body', async () => { + const res = await createBatchRoute( + new Request(URL_BATCHES, { + method: 'POST', + headers: { 'content-type': 'text/csv', 'x-organization-id': ORG_A }, + body: GOOD_CSV, + }) as any, + ); + expect(res.status).toBe(400); + expect((await res.json()).code).toBe('UNSUPPORTED_CONTENT_TYPE'); + }); + + it('rejects an oversized declared body', async () => { + const req = new Request(URL_BATCHES, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'content-length': String(50 * 1024 * 1024), + 'x-organization-id': ORG_A, + }, + body: JSON.stringify({ csv: GOOD_CSV }), + }); + const res = await createBatchRoute(req as any); + expect(res.status).toBe(400); + expect((await res.json()).code).toBe('PAYLOAD_TOO_LARGE'); + }); + + it('rejects unknown fields rather than ignoring them', async () => { + const res = await createBatchRoute( + post( + URL_BATCHES, + { csv: GOOD_CSV, state: 'PAID', role: 'FINANCE' }, + { 'x-organization-id': ORG_A }, + ), + ); + expect(res.status).toBe(400); + const body = await res.json(); + expect(body.code).toBe('MALFORMED_REQUEST'); + // Silently dropping these would teach a client that they were honoured. + expect(JSON.stringify(body.details)).toContain('UNKNOWN_FIELD'); + expect(db.__tables.payment.rows).toHaveLength(0); + }); + + it('rejects a missing csv field', async () => { + const res = await createBatchRoute(post(URL_BATCHES, {}, { 'x-organization-id': ORG_A })); + expect(res.status).toBe(400); + }); + + it('rejects a reference containing control characters', async () => { + const res = await createBatchRoute( + post( + URL_BATCHES, + { csv: GOOD_CSV, reference: 'A' + String.fromCharCode(7) + 'B' }, + { 'x-organization-id': ORG_A }, + ), + ); + expect(res.status).toBe(400); + expect(JSON.stringify((await res.json()).details)).toContain('control characters'); + }); + + it('rejects a projectId belonging to another organization', async () => { + db.__tables.project.rows.push({ id: 'prjb', orgId: ORG_B, code: 'B1', name: 'Other' }); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV, projectId: 'prjb' }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(404); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); +}); + +describe('POST /api/payroll/batches β€” CSV validation', () => { + beforeEach(() => signedInAs('u_admin_a', addr('adminA'))); + + it('creates one payment per row and returns 201', async () => { + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV, filename: 'sept.csv' }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(201); + const body = await res.json(); + expect(body.created).toBe(true); + expect(body.batch.paymentCount).toBe(3); + expect(body.batch.totalBaseUnits).toBe('28600000000'); + expect(body.batch.asset).toBe('USDC'); + + expect(db.__tables.payment.rows).toHaveLength(3); + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + expect(db.__tables.payment.rows.every((p) => p.orgId === ORG_A)).toBe(true); + }); + + it('returns 422 with per-row errors for an invalid file, and writes nothing', async () => { + const csv = [ + HEADER, + `${addr('ok')},1000,USDC,40,25,${PERIOD}`, + `NOTANADDRESS,100,USDC,10,10,${PERIOD}`, + `${addr('sci')},1e3,USDC,10,10,${PERIOD}`, + `${addr('frac')},100,USDC,7.5,10,${PERIOD}`, + ].join('\n'); + + const res = await createBatchRoute(post(URL_BATCHES, { csv }, { 'x-organization-id': ORG_A })); + expect(res.status).toBe(422); + const body = await res.json(); + expect(body.code).toBe('CSV_INVALID'); + + const codes = body.details.errors.map((e: any) => e.code).sort(); + expect(codes).toEqual(['AMBIGUOUS_NUMBER', 'FRACTIONAL_HOURS', 'INVALID_ADDRESS']); + // Each error names the line the uploader sees. + expect(body.details.errors.every((e: any) => typeof e.row === 'number')).toBe(true); + // Nothing partial: a batch is created only from a wholly valid file. + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + expect(db.__tables.payment.rows).toHaveLength(0); + }); + + it('rejects an asset this deployment cannot settle', async () => { + const csv = [HEADER, `${addr('x')},100,XLM,10,10,${PERIOD}`].join('\n'); + const res = await createBatchRoute(post(URL_BATCHES, { csv }, { 'x-organization-id': ORG_A })); + expect(res.status).toBe(422); + const body = await res.json(); + expect(body.details.errors[0].code).toBe('UNSUPPORTED_ASSET'); + expect(body.details.errors[0].message).toContain('settles: USDC'); + }); + + it('rejects a row whose amount does not equal hours x rate', async () => { + const csv = [HEADER, `${addr('x')},1000,USDC,40,20,${PERIOD}`].join('\n'); + const res = await createBatchRoute(post(URL_BATCHES, { csv }, { 'x-organization-id': ORG_A })); + expect(res.status).toBe(422); + expect((await res.json()).details.errors[0].code).toBe('HOURS_RATE_MISMATCH'); + }); + + it('rejects a file with a header but no rows', async () => { + const res = await createBatchRoute( + post(URL_BATCHES, { csv: HEADER }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(422); + }); +}); + +describe('POST /api/payroll/batches β€” idempotency', () => { + beforeEach(() => signedInAs('u_admin_a', addr('adminA'))); + + const KEY = 'idem-sept-2026'; + + it('replays the first outcome for a repeated request (double-click, retry)', async () => { + const headers = { 'x-organization-id': ORG_A, 'idempotency-key': KEY }; + const first = await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + const second = await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + + expect(first.status).toBe(201); + expect(second.status).toBe(200); + const a = await first.json(); + const b = await second.json(); + expect(b.created).toBe(false); + expect(b.batch.id).toBe(a.batch.id); + + // The property that matters: three payments, not six. + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + expect(db.__tables.payment.rows).toHaveLength(3); + }); + + it('is deterministic across several concurrent identical requests', async () => { + const headers = { 'x-organization-id': ORG_A, 'idempotency-key': KEY }; + const results = await Promise.all([ + createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)), + ]); + const bodies = await Promise.all(results.map((r) => r.json())); + const ids = new Set(bodies.map((b) => b.batch.id)); + + expect(ids.size).toBe(1); + expect(bodies.filter((b) => b.created)).toHaveLength(1); + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + expect(db.__tables.payment.rows).toHaveLength(3); + }); + + it('refuses the same key with a different payload instead of replaying the wrong batch', async () => { + const headers = { 'x-organization-id': ORG_A, 'idempotency-key': KEY }; + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + + const differentCsv = [HEADER, `${addr('dave')},500,USDC,25,20,${PERIOD}`].join('\n'); + const res = await createBatchRoute(post(URL_BATCHES, { csv: differentCsv }, headers)); + + expect(res.status).toBe(409); + expect((await res.json()).code).toBe('IDEMPOTENCY_KEY_REUSED'); + // Neither payroll was altered, and no second one was created. + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + expect(db.__tables.payment.rows).toHaveLength(3); + }); + + it('does not tie together two deliberate uploads without a key', async () => { + const headers = { 'x-organization-id': ORG_A }; + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + expect(db.__tables.payrollBatch.rows).toHaveLength(2); + expect(db.__tables.payment.rows).toHaveLength(6); + }); + + it('warns about a byte-identical recent upload rather than blocking it', async () => { + const headers = { 'x-organization-id': ORG_A }; + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + const res = await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, headers)); + const body = await res.json(); + expect(res.status).toBe(201); + expect(body.possibleDuplicateOf?.reference).toBe('CF-00001'); + }); + + it('scopes an idempotency key to the organization', async () => { + signedInAs('u_admin_a', addr('adminA')); + await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A, 'idempotency-key': KEY }), + ); + signedInAs('u_admin_b', addr('adminB')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_B, 'idempotency-key': KEY }), + ); + expect(res.status).toBe(201); + expect((await res.json()).created).toBe(true); + }); +}); + +describe('POST /api/payroll/batches/validate', () => { + const URL_VALIDATE = `${URL_BATCHES}/validate`; + beforeEach(() => signedInAs('u_admin_a', addr('adminA'))); + + it('reports a valid file without creating anything', async () => { + const res = await validateCsvRoute( + post(URL_VALIDATE, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.valid).toBe(true); + expect(body.summary.paymentsToCreate).toBe(3); + expect(body.summary.totalHours).toBe('140'); + + // The whole point of the endpoint: no writes at all. + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + expect(db.__tables.payment.rows).toHaveLength(0); + expect(db.__tables.auditEvent.rows).toHaveLength(0); + }); + + it('returns 200 with structured row errors for an invalid file', async () => { + const csv = [HEADER, `BADADDRESS,100,USDC,10,10,${PERIOD}`].join('\n'); + const res = await validateCsvRoute( + post(URL_VALIDATE, { csv }, { 'x-organization-id': ORG_A }), + ); + // The CALL succeeded; the file is what is wrong. A reviewer iterating on a + // preview is not making failing requests. + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.valid).toBe(false); + expect(body.errors[0]).toMatchObject({ row: 2, field: 'recipient', code: 'INVALID_ADDRESS' }); + }); + + it('is safe to call repeatedly', async () => { + const req = () => + validateCsvRoute(post(URL_VALIDATE, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A })); + for (let i = 0; i < 5; i++) expect((await req()).status).toBe(200); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + expect(db.__tables.payment.rows).toHaveLength(0); + }); + + it('reports an unconfigured settlement asset without failing the call', async () => { + delete process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID; + const res = await validateCsvRoute( + post(URL_VALIDATE, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + const body = await res.json(); + expect(body.asset.configured).toBe(false); + expect(body.asset.contractId).toBeNull(); + }); + + it('refuses an unauthenticated caller', async () => { + mockUser.mockResolvedValue(null); + const res = await validateCsvRoute(post(URL_VALIDATE, { csv: GOOD_CSV })); + expect(res.status).toBe(401); + }); +}); + +describe('GET /api/payroll/batches', () => { + beforeEach(async () => { + signedInAs('u_admin_a', addr('adminA')); + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A })); + signedInAs('u_admin_b', addr('adminB')); + await createBatchRoute(post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_B })); + }); + + it('lists only the caller organization batches', async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await listBatchesRoute(get(URL_BATCHES, { 'x-organization-id': ORG_A })); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.batches).toHaveLength(1); + expect(body.batches[0].paymentCount).toBe(3); + expect(body.batches[0].totalBaseUnits).toBe('28600000000'); + // Standing is derived on read, not stored. + expect(body.batches[0].headline).toBeDefined(); + }); + + it('rejects an unknown query parameter', async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await listBatchesRoute( + get(`${URL_BATCHES}?sneaky=1`, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(400); + }); + + it('rejects a limit outside the allowed range', async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await listBatchesRoute( + get(`${URL_BATCHES}?limit=5000`, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(400); + }); +}); + +describe('GET /api/payroll/batches/:id', () => { + let batchId: string; + + beforeEach(async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + batchId = (await res.json()).batch.id; + }); + + it('returns the batch with its payments and derived standing', async () => { + const res = await getBatchRoute(get(`${URL_BATCHES}/${batchId}`, { 'x-organization-id': ORG_A }), { + params: { id: batchId }, + }); + expect(res.status).toBe(200); + const { batch } = await res.json(); + expect(batch.paymentCount).toBe(3); + expect(batch.payments).toHaveLength(3); + expect(batch.standing.totalAmountBaseUnits).toBe('28600000000'); + expect(batch.standing.paidAmountBaseUnits).toBe('0'); + // No transaction link on a payment that cannot have one. + expect(batch.payments.every((p: any) => p.transactionHash === null)).toBe(true); + }); + + it('returns a non-enumerating 404 for another organization batch', async () => { + signedInAs('u_admin_b', addr('adminB')); + const res = await getBatchRoute(get(`${URL_BATCHES}/${batchId}`, { 'x-organization-id': ORG_B }), { + params: { id: batchId }, + }); + expect(res.status).toBe(404); + // Identical to a batch that truly does not exist, so an id cannot be probed. + const absent = await getBatchRoute( + get(`${URL_BATCHES}/batnope`, { 'x-organization-id': ORG_B }), + { params: { id: 'batnope' } }, + ); + expect(absent.status).toBe(404); + expect(await res.json()).toEqual(await absent.json()); + }); +}); + +describe('POST /api/payroll/batches/:id/validate', () => { + let batchId: string; + + beforeEach(async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + batchId = (await res.json()).batch.id; + }); + + it('confirms a sound draft without changing anything', async () => { + const before = snapshotRows(db.__tables.payment.rows); + const res = await revalidateRoute( + post(`${URL_BATCHES}/${batchId}/validate`, {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(200); + expect((await res.json()).valid).toBe(true); + expect(snapshotRows(db.__tables.payment.rows)).toBe(before); + }); + + it('reports a batch denominated in an asset no longer settleable', async () => { + // Configuration moved under the batch, as it would if the operator switched + // the settlement asset between drafting and funding. + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'EURC'; + const res = await revalidateRoute( + post(`${URL_BATCHES}/${batchId}/validate`, {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + const body = await res.json(); + expect(body.valid).toBe(false); + expect(body.errors.some((e: any) => e.code === 'ASSET_NOT_SETTLEABLE')).toBe(true); + }); + + it('reports an unconfigured settlement contract', async () => { + delete process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID; + const res = await revalidateRoute( + post(`${URL_BATCHES}/${batchId}/validate`, {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + const body = await res.json(); + expect(body.errors.some((e: any) => e.code === 'SETTLEMENT_ASSET_UNCONFIGURED')).toBe(true); + }); + + it('gives another organization a non-enumerating 404', async () => { + signedInAs('u_admin_b', addr('adminB')); + const res = await revalidateRoute( + post(`${URL_BATCHES}/${batchId}/validate`, {}, { 'x-organization-id': ORG_B }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(404); + }); +}); + +describe('POST /api/payroll/batches/:id/approve', () => { + let batchId: string; + let paymentIds: string[]; + + beforeEach(async () => { + signedInAs('u_admin_a', addr('adminA')); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + batchId = (await res.json()).batch.id; + paymentIds = db.__tables.payment.rows.map((p) => p.id); + }); + + const approveUrl = () => `${URL_BATCHES}/${batchId}/approve`; + + it('derives the approval role from membership, ignoring a client-sent role', async () => { + signedInAs('u_manager_a', addr('managerA')); + // `role` is not in the schema, so sending it is refused outright rather than + // quietly dropped β€” a manager cannot nominate themselves as finance. + const res = await approveBatchRoute( + post(approveUrl(), { role: 'FINANCE' }, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(400); + expect(JSON.stringify((await res.json()).details)).toContain('UNKNOWN_FIELD'); + expect(db.__tables.approval.rows).toHaveLength(0); + }); + + it('records a MANAGER approval on every payment in the batch', async () => { + signedInAs('u_manager_a', addr('managerA')); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.approvalRole).toBe(OrgRole.MANAGER); + expect(body.recorded).toBe(3); + expect(body.failed).toBe(0); + + expect(db.__tables.approval.rows).toHaveLength(3); + expect(db.__tables.approval.rows.every((a) => a.role === OrgRole.MANAGER)).toBe(true); + expect(db.__tables.approval.rows.every((a) => a.orgId === ORG_A)).toBe(true); + expect( + db.__tables.approval.rows.every((a) => a.decision === ApprovalDecision.APPROVED), + ).toBe(true); + }); + + it('records an audit event per approval', async () => { + signedInAs('u_manager_a', addr('managerA')); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': ORG_A }), { + params: { id: batchId }, + }); + const granted = db.__tables.auditEvent.rows.filter((e) => e.type === 'approval.granted'); + expect(granted).toHaveLength(3); + expect(granted.every((e) => e.actorAddress === addr('managerA'))).toBe(true); + }); + + it('is idempotent: a repeated approval records nothing further', async () => { + signedInAs('u_manager_a', addr('managerA')); + const headers = { 'x-organization-id': ORG_A }; + await approveBatchRoute(post(approveUrl(), {}, headers), { params: { id: batchId } }); + const res = await approveBatchRoute(post(approveUrl(), {}, headers), { + params: { id: batchId }, + }); + const body = await res.json(); + + expect(res.status).toBe(200); + expect(body.recorded).toBe(0); + expect(body.alreadyRecorded).toBe(3); + // One decision per role per payment. A second manager approval is a + // duplicate, not a new fact. + expect(db.__tables.approval.rows).toHaveLength(3); + }); + + it('keeps manager and finance as two distinct decisions', async () => { + signedInAs('u_manager_a', addr('managerA')); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': ORG_A }), { + params: { id: batchId }, + }); + signedInAs('u_finance_a', addr('financeA')); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + const body = await res.json(); + + expect(body.approvalRole).toBe(OrgRole.FINANCE); + expect(body.recorded).toBe(3); + expect(db.__tables.approval.rows).toHaveLength(6); + const roles = new Set(db.__tables.approval.rows.map((a) => a.role)); + expect(roles).toEqual(new Set([OrgRole.MANAGER, OrgRole.FINANCE])); + // Two distinct wallets, never one standing in for both. + const wallets = new Set(db.__tables.approval.rows.map((a) => a.actorAddress)); + expect(wallets.size).toBe(2); + }); + + it('refuses to let one wallet supply both halves of the gate', async () => { + // An ADMIN holds both permissions, so separation of duties has to be enforced + // by the approval logic rather than by the permission check. + signedInAs('u_admin_a', addr('adminA')); + const headers = { 'x-organization-id': ORG_A }; + const first = await approveBatchRoute(post(approveUrl(), {}, headers), { + params: { id: batchId }, + }); + expect((await first.json()).recorded).toBe(3); + + const second = await approveBatchRoute(post(approveUrl(), {}, headers), { + params: { id: batchId }, + }); + const body = await second.json(); + // The second attempt finds its own role already recorded and adds nothing. + expect(body.recorded).toBe(0); + expect(db.__tables.approval.rows).toHaveLength(3); + }); + + it('refuses a role that cannot approve at all', async () => { + signedInAs('u_viewer_a', addr('viewerA')); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(403); + expect(db.__tables.approval.rows).toHaveLength(0); + }); + + it('does not approve another organization batch', async () => { + signedInAs('u_admin_b', addr('adminB')); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': ORG_B }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(404); + expect(db.__tables.approval.rows).toHaveLength(0); + }); + + it('approves only the named payments', async () => { + signedInAs('u_manager_a', addr('managerA')); + const res = await approveBatchRoute( + post(approveUrl(), { paymentIds: [paymentIds[0]] }, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect((await res.json()).recorded).toBe(1); + expect(db.__tables.approval.rows).toHaveLength(1); + expect(db.__tables.approval.rows[0].paymentId).toBe(paymentIds[0]); + }); + + it('refuses a payment id that is not in this batch', async () => { + signedInAs('u_manager_a', addr('managerA')); + const res = await approveBatchRoute( + post(approveUrl(), { paymentIds: ['payelsewhere'] }, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(404); + expect(db.__tables.approval.rows).toHaveLength(0); + }); + + it('reports a conflict when no payment is awaiting a decision', async () => { + for (const p of db.__tables.payment.rows) p.state = PaymentState.PAID; + signedInAs('u_manager_a', addr('managerA')); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': ORG_A }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(409); + expect((await res.json()).code).toBe('STATE_CONFLICT'); + expect(db.__tables.approval.rows).toHaveLength(0); + }); + + it('never changes payment state, settlement hash or amount', async () => { + const snapshot = () => + db.__tables.payment.rows.map((p) => ({ + state: p.state, + settlementTxHash: p.settlementTxHash ?? null, + amount: p.amountBaseUnits, + recipient: p.recipientAddress, + })); + const before = snapshot(); + signedInAs('u_manager_a', addr('managerA')); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': ORG_A }), { + params: { id: batchId }, + }); + // Recording an approval is not settlement. Nothing financial moves here. + expect(snapshot()).toEqual(before); + expect(snapshot().every((p) => p.state === PaymentState.DRAFT)).toBe(true); + }); +}); + +describe('error sanitization', () => { + beforeEach(() => signedInAs('u_admin_a', addr('adminA'))); + + it('returns an opaque 500 and leaks no internals when a write fails', async () => { + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); + db.__failOn('payrollBatch', 'create', 1); + + const res = await createBatchRoute( + post(URL_BATCHES, { csv: GOOD_CSV }, { 'x-organization-id': ORG_A }), + ); + expect(res.status).toBe(500); + const raw = JSON.stringify(await res.json()); + + // None of this may cross the boundary. + expect(raw).not.toContain('fake-db'); + expect(raw).not.toContain('payrollBatch'); + expect(raw).not.toContain('prisma'); + expect(raw).not.toMatch(/at \w+ \(/); + expect(db.__tables.payment.rows).toHaveLength(0); + spy.mockRestore(); + }); +}); diff --git a/src/app/api/payroll/__tests__/bulk-pay.integration.test.ts b/src/app/api/payroll/__tests__/bulk-pay.integration.test.ts new file mode 100644 index 0000000..ca23a3c --- /dev/null +++ b/src/app/api/payroll/__tests__/bulk-pay.integration.test.ts @@ -0,0 +1,673 @@ +/** + * Bulk Pay against real PostgreSQL. + * + * The same route handlers the unit suite exercises, but with the real Prisma client + * and a real database: composite foreign keys, unique indexes, bigint columns and + * genuine transaction isolation. + * + * Only authentication is substituted. Wallet challenge/signature verification has + * its own tests and cannot be performed headlessly; everything downstream of the + * authenticated identity β€” membership, role, permission, tenant scope β€” is resolved + * from this database on every request, exactly as in production. + */ + +import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest'; +import { OrgRole, PaymentState, ApprovalDecision } from '@prisma/client'; + +vi.mock('@/lib/auth', () => ({ getUserFromRequest: vi.fn() })); + +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { __resetRateLimiter } from '@/lib/ratelimit'; +import { POST as createBatchRoute, GET as listBatchesRoute } from '../batches/route'; +import { POST as validateCsvRoute } from '../batches/validate/route'; +import { GET as getBatchRoute } from '../batches/[id]/route'; +import { POST as revalidateRoute } from '../batches/[id]/validate/route'; +import { POST as approveBatchRoute } from '../batches/[id]/approve/route'; +import { + assertLocalDatabase, + resetDatabase, + seedOrganization, + seedWorker, + payeeWallet, + payrollCsv, + type SeededOrg, +} from '@/lib/db/__tests__/helpers'; + +assertLocalDatabase(); + +const mockUser = getUserFromRequest as unknown as ReturnType; +const URL_BATCHES = 'https://app.test/api/payroll/batches'; + +let orgA: SeededOrg; +let orgB: SeededOrg; + +/** Three contractors whose rows satisfy hours x rate == amount exactly. */ +const THREE_CONTRACTORS = payrollCsv([ + { tag: 'alice', amount: '1000', hours: 40, rate: '25' }, + { tag: 'bob', amount: '1600', hours: 80, rate: '20' }, + { tag: 'carol', amount: '260', hours: 20, rate: '13' }, +]); +const THREE_TOTAL = 28_600_000_000n; // 2,860.00 USDC in base units + +function signedInAs(org: SeededOrg, role: OrgRole) { + const m = org.members[role]; + mockUser.mockResolvedValue({ userId: m.userId, walletAddress: m.wallet, role: 'EMPLOYEE' }); + return m; +} + +function post(url: string, body: unknown, headers: Record = {}): any { + return new Request(url, { + method: 'POST', + headers: { 'content-type': 'application/json', ...headers }, + body: JSON.stringify(body), + }); +} + +function get(url: string, headers: Record = {}): any { + return new Request(url, { method: 'GET', headers }); +} + +beforeAll(async () => { + process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID = + 'CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M'; + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'USDC'; + await prisma.$connect(); +}); + +afterAll(async () => { + await prisma.$disconnect(); +}); + +beforeEach(async () => { + vi.clearAllMocks(); + __resetRateLimiter(); + await resetDatabase(prisma); + orgA = await seedOrganization(prisma, 'orga'); + orgB = await seedOrganization(prisma, 'orgb'); +}); + +async function createThreePaymentBatch(org: SeededOrg, headers: Record = {}) { + signedInAs(org, OrgRole.ADMIN); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: THREE_CONTRACTORS, filename: 'september.csv' }, { + 'x-organization-id': org.orgId, + ...headers, + }), + ); + const body = await res.json(); + return { res, body }; +} + +// --------------------------------------------------------------------------- +// The realistic path: CSV -> parser -> validation -> batch -> payments -> audit +// --------------------------------------------------------------------------- + +describe('CSV to persisted payroll', () => { + it('creates one PayrollBatch and exactly three Payment rows', async () => { + const { res, body } = await createThreePaymentBatch(orgA); + expect(res.status).toBe(201); + expect(body.created).toBe(true); + + // Read back from the database, not from the response. + const batches = await prisma.payrollBatch.findMany({ where: { orgId: orgA.orgId } }); + expect(batches).toHaveLength(1); + expect(batches[0].reference).toBe('CF-00001'); + expect(batches[0].sourceFilename).toBe('september.csv'); + expect(batches[0].sourceRowCount).toBe(3); + expect(batches[0].sourceChecksum).toMatch(/^[0-9a-f]{64}$/); + + const payments = await prisma.payment.findMany({ + where: { orgId: orgA.orgId }, + orderBy: { amountBaseUnits: 'asc' }, + }); + // Three payees, three rows. No aggregate shortcut. + expect(payments).toHaveLength(3); + expect(payments.every((p) => p.batchId === batches[0].id)).toBe(true); + expect(payments.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + expect(payments.every((p) => p.orgId === orgA.orgId)).toBe(true); + }); + + it('persists exact bigint amounts, rates and hours', async () => { + await createThreePaymentBatch(orgA); + const payments = await prisma.payment.findMany({ + where: { orgId: orgA.orgId }, + orderBy: { amountBaseUnits: 'asc' }, + }); + + expect(payments.map((p) => p.amountBaseUnits)).toEqual([ + 2_600_000_000n, + 10_000_000_000n, + 16_000_000_000n, + ]); + expect(payments.map((p) => p.rateBaseUnits)).toEqual([ + 130_000_000n, + 250_000_000n, + 200_000_000n, + ]); + expect(payments.map((p) => p.hours)).toEqual([20n, 40n, 80n]); + + // The contract's invariant, as actually stored. + for (const p of payments) { + expect(p.hours * p.rateBaseUnits).toBe(p.amountBaseUnits); + } + + const sum = payments.reduce((a, p) => a + p.amountBaseUnits, 0n); + expect(sum).toBe(THREE_TOTAL); + }); + + it('links a payee with an existing worker record and leaves the rest unlinked', async () => { + const worker = await seedWorker(prisma, orgA.orgId, 'alice'); + const { body } = await createThreePaymentBatch(orgA); + expect(body.batch.unlinkedRecipients).toBe(2); + + const linked = await prisma.payment.findFirstOrThrow({ + where: { orgId: orgA.orgId, recipientAddress: worker.walletAddress }, + select: { workerId: true }, + }); + expect(linked.workerId).toBe(worker.id); + + const unlinked = await prisma.payment.count({ + where: { orgId: orgA.orgId, workerId: null }, + }); + expect(unlinked).toBe(2); + }); + + it('writes one batch-level audit event carrying money as a string', async () => { + await createThreePaymentBatch(orgA); + const events = await prisma.auditEvent.findMany({ where: { orgId: orgA.orgId } }); + expect(events).toHaveLength(1); + expect(events[0].type).toBe('payroll.batch.created'); + const meta = events[0].metadata as any; + expect(meta.paymentCount).toBe(3); + expect(meta.totalBaseUnits).toBe(THREE_TOTAL.toString()); + expect(typeof meta.totalBaseUnits).toBe('string'); + }); + + it('stores a neutralized reference so a formula cannot reach a spreadsheet', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const csv = [ + 'recipient,amount,asset,hours,rate,period_start,period_end,reference', + `${payeeWallet('inj')},100,USDC,10,10,2026-09-01,2026-09-15,"=HYPERLINK(""http://evil"",""click"")"`, + ].join('\n'); + await createBatchRoute(post(URL_BATCHES, { csv }, { 'x-organization-id': orgA.orgId })); + + const payment = await prisma.payment.findFirstOrThrow({ + where: { orgId: orgA.orgId }, + select: { sourceReference: true }, + }); + expect(payment.sourceReference?.startsWith("'=")).toBe(true); + }); + + it('writes nothing at all for an invalid file', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const csv = payrollCsv([{ tag: 'bad', amount: '1000', hours: 40, rate: '20' }]); + const res = await createBatchRoute( + post(URL_BATCHES, { csv }, { 'x-organization-id': orgA.orgId }), + ); + expect(res.status).toBe(422); + expect((await res.json()).details.errors[0].code).toBe('HOURS_RATE_MISMATCH'); + + expect(await prisma.payrollBatch.count()).toBe(0); + expect(await prisma.payment.count()).toBe(0); + expect(await prisma.auditEvent.count()).toBe(0); + }); + + it('validates without writing, repeatedly', async () => { + signedInAs(orgA, OrgRole.ADMIN); + for (let i = 0; i < 3; i++) { + const res = await validateCsvRoute( + post(`${URL_BATCHES}/validate`, { csv: THREE_CONTRACTORS }, { + 'x-organization-id': orgA.orgId, + }), + ); + expect(res.status).toBe(200); + expect((await res.json()).valid).toBe(true); + } + expect(await prisma.payrollBatch.count()).toBe(0); + expect(await prisma.payment.count()).toBe(0); + expect(await prisma.auditEvent.count()).toBe(0); + }); +}); + +// --------------------------------------------------------------------------- +// Transactional integrity (item 9) +// --------------------------------------------------------------------------- + +describe('Transactional integrity', () => { + it('leaves zero partial records when a payment write fails mid-batch', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + // The fault is injected INSIDE the real transaction, so PostgreSQL performs the + // rollback. Spying on `prisma.payment.create` would not work: the service writes + // through the transaction client, which is a different object β€” and a test that + // quietly intercepted nothing would have reported success. + const realTransaction = prisma.$transaction.bind(prisma); + const txSpy = vi.spyOn(prisma, '$transaction').mockImplementation(((fn: any, opts: any) => + realTransaction(async (tx: any) => { + let writes = 0; + const proxied = new Proxy(tx, { + get(target: any, prop: string | symbol) { + if (prop !== 'payment') return target[prop]; + return { + create: (args: any) => { + writes += 1; + // Fail the THIRD row, so the batch and two payments are already + // written inside the transaction when it breaks. + if (writes === 3) return Promise.reject(new Error('simulated failure on row 3')); + return target.payment.create(args); + }, + }; + }, + }); + return fn(proxied); + }, opts)) as any); + + const res = await createBatchRoute( + post(URL_BATCHES, { csv: THREE_CONTRACTORS }, { 'x-organization-id': orgA.orgId }), + ); + txSpy.mockRestore(); + + expect(res.status).toBe(500); + // The client learns nothing about why. + const body = JSON.stringify(await res.json()); + expect(body).not.toContain('simulated'); + expect(body).not.toContain('payment'); + + // Real PostgreSQL rollback. A batch that looked complete while missing its third + // payment would quietly underpay a contractor. + expect(await prisma.payrollBatch.count()).toBe(0); + expect(await prisma.payment.count()).toBe(0); + expect(await prisma.auditEvent.count()).toBe(0); + + spy.mockRestore(); + }); +}); + +// --------------------------------------------------------------------------- +// Concurrency and idempotency (items 5C, 5D, 10) +// --------------------------------------------------------------------------- + +describe('Idempotency against the real unique index', () => { + const KEY = 'integration-key-september'; + + it('C. three concurrent identical requests produce exactly one batch', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const headers = { 'x-organization-id': orgA.orgId, 'idempotency-key': KEY }; + + const responses = await Promise.all([ + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + ]); + const bodies = await Promise.all(responses.map((r) => r.json())); + + // Exactly one request created it; the rest replayed the original. + expect(bodies.filter((b) => b.created === true)).toHaveLength(1); + expect(bodies.filter((b) => b.created === false)).toHaveLength(2); + expect(new Set(bodies.map((b) => b.batch.id)).size).toBe(1); + + // The database is the authority here, not the pre-check. + expect(await prisma.payrollBatch.count()).toBe(1); + expect(await prisma.payment.count()).toBe(3); + + // Every replay reports the same money as the original. + for (const b of bodies) expect(b.batch.totalBaseUnits).toBe(THREE_TOTAL.toString()); + }); + + it('replays a sequential retry rather than creating a second payroll', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const headers = { 'x-organization-id': orgA.orgId, 'idempotency-key': KEY }; + const first = await createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)); + const second = await createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)); + + expect(first.status).toBe(201); + expect(second.status).toBe(200); + expect((await second.json()).batch.id).toBe((await first.json()).batch.id); + expect(await prisma.payment.count()).toBe(3); + }); + + it('D. refuses the same key with a different payload', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const headers = { 'x-organization-id': orgA.orgId, 'idempotency-key': KEY }; + await createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)); + + const different = payrollCsv([{ tag: 'dave', amount: '500', hours: 25, rate: '20' }]); + const res = await createBatchRoute(post(URL_BATCHES, { csv: different }, headers)); + + expect(res.status).toBe(409); + expect((await res.json()).code).toBe('IDEMPOTENCY_KEY_REUSED'); + // The original payroll is untouched and no second one exists. + expect(await prisma.payrollBatch.count()).toBe(1); + expect(await prisma.payment.count()).toBe(3); + }); + + it('keeps keys distinct per organization', async () => { + const headers = (org: SeededOrg) => ({ + 'x-organization-id': org.orgId, + 'idempotency-key': KEY, + }); + signedInAs(orgA, OrgRole.ADMIN); + await createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers(orgA))); + signedInAs(orgB, OrgRole.ADMIN); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers(orgB)), + ); + expect(res.status).toBe(201); + expect(await prisma.payrollBatch.count()).toBe(2); + }); + + it('allocates distinct sequential references under concurrency', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const headers = { 'x-organization-id': orgA.orgId }; + // No idempotency key: three deliberate payrolls racing for a reference. The + // unique index on (orgId, reference) forces the retry path to resolve them. + const responses = await Promise.all([ + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + createBatchRoute(post(URL_BATCHES, { csv: THREE_CONTRACTORS }, headers)), + ]); + const ok = responses.filter((r) => r.status === 201); + expect(ok).toHaveLength(3); + + const refs = (await prisma.payrollBatch.findMany({ select: { reference: true } })).map( + (b) => b.reference, + ); + expect(new Set(refs).size).toBe(3); + expect(await prisma.payment.count()).toBe(9); + }); +}); + +// --------------------------------------------------------------------------- +// Approval (item 13, partial β€” off-chain decision only) +// --------------------------------------------------------------------------- + +describe('Approval against the real database', () => { + let batchId: string; + + beforeEach(async () => { + const { body } = await createThreePaymentBatch(orgA); + batchId = body.batch.id; + }); + + const approveUrl = () => `${URL_BATCHES}/${batchId}/approve`; + + it('records a manager decision per payment, with its tenant', async () => { + const manager = signedInAs(orgA, OrgRole.MANAGER); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': orgA.orgId }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(200); + expect((await res.json()).recorded).toBe(3); + + const approvals = await prisma.approval.findMany({ where: { orgId: orgA.orgId } }); + expect(approvals).toHaveLength(3); + // The composite FK requires orgId; this is the write that previously failed. + expect(approvals.every((a) => a.orgId === orgA.orgId)).toBe(true); + expect(approvals.every((a) => a.role === OrgRole.MANAGER)).toBe(true); + expect(approvals.every((a) => a.decision === ApprovalDecision.APPROVED)).toBe(true); + expect(approvals.every((a) => a.actorAddress === manager.wallet)).toBe(true); + + const audit = await prisma.auditEvent.findMany({ + where: { orgId: orgA.orgId, type: 'approval.granted' }, + }); + expect(audit).toHaveLength(3); + }); + + it('records manager and finance as two distinct decisions by two wallets', async () => { + const manager = signedInAs(orgA, OrgRole.MANAGER); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': orgA.orgId }), { + params: { id: batchId }, + }); + const finance = signedInAs(orgA, OrgRole.FINANCE); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': orgA.orgId }), { + params: { id: batchId }, + }); + + const approvals = await prisma.approval.findMany({ where: { orgId: orgA.orgId } }); + expect(approvals).toHaveLength(6); + expect(new Set(approvals.map((a) => a.role))).toEqual( + new Set([OrgRole.MANAGER, OrgRole.FINANCE]), + ); + expect(new Set(approvals.map((a) => a.actorAddress))).toEqual( + new Set([manager.wallet, finance.wallet]), + ); + }); + + it('is idempotent, enforced by the unique index on (paymentId, role)', async () => { + signedInAs(orgA, OrgRole.MANAGER); + const headers = { 'x-organization-id': orgA.orgId }; + await approveBatchRoute(post(approveUrl(), {}, headers), { params: { id: batchId } }); + const again = await approveBatchRoute(post(approveUrl(), {}, headers), { + params: { id: batchId }, + }); + const body = await again.json(); + expect(body.recorded).toBe(0); + expect(body.alreadyRecorded).toBe(3); + expect(await prisma.approval.count()).toBe(3); + }); + + it('does not let one wallet satisfy both halves of the gate', async () => { + // ADMIN holds both approval permissions, so only the approval logic stands + // between one wallet and a fully approved payroll. + signedInAs(orgA, OrgRole.ADMIN); + const headers = { 'x-organization-id': orgA.orgId }; + await approveBatchRoute(post(approveUrl(), {}, headers), { params: { id: batchId } }); + await approveBatchRoute(post(approveUrl(), {}, headers), { params: { id: batchId } }); + + const approvals = await prisma.approval.findMany(); + expect(approvals).toHaveLength(3); + expect(new Set(approvals.map((a) => a.role))).toEqual(new Set([OrgRole.MANAGER])); + }); + + it('changes no payment state, amount, recipient or settlement hash', async () => { + const before = await prisma.payment.findMany({ + where: { orgId: orgA.orgId }, + orderBy: { id: 'asc' }, + select: { + id: true, + state: true, + amountBaseUnits: true, + recipientAddress: true, + settlementTxHash: true, + settledAt: true, + }, + }); + + signedInAs(orgA, OrgRole.MANAGER); + await approveBatchRoute(post(approveUrl(), {}, { 'x-organization-id': orgA.orgId }), { + params: { id: batchId }, + }); + + const after = await prisma.payment.findMany({ + where: { orgId: orgA.orgId }, + orderBy: { id: 'asc' }, + select: { + id: true, + state: true, + amountBaseUnits: true, + recipientAddress: true, + settlementTxHash: true, + settledAt: true, + }, + }); + + // Recording an approval is not settlement. Nothing financial moves. + expect(after).toEqual(before); + expect(after.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + expect(after.every((p) => p.settlementTxHash === null)).toBe(true); + }); + + it('refuses a role that cannot approve', async () => { + signedInAs(orgA, OrgRole.VIEWER); + const res = await approveBatchRoute( + post(approveUrl(), {}, { 'x-organization-id': orgA.orgId }), + { params: { id: batchId } }, + ); + expect(res.status).toBe(403); + expect(await prisma.approval.count()).toBe(0); + }); +}); + +// --------------------------------------------------------------------------- +// Tenant isolation (item 11) +// --------------------------------------------------------------------------- + +describe('Tenant isolation through the API and the database', () => { + let batchA: string; + let batchB: string; + + beforeEach(async () => { + batchA = (await createThreePaymentBatch(orgA)).body.batch.id; + batchB = (await createThreePaymentBatch(orgB)).body.batch.id; + }); + + it('lists only the caller own batches', async () => { + signedInAs(orgA, OrgRole.ADMIN); + const a = await listBatchesRoute(get(URL_BATCHES, { 'x-organization-id': orgA.orgId })); + const bodyA = await a.json(); + expect(bodyA.batches).toHaveLength(1); + expect(bodyA.batches[0].id).toBe(batchA); + + signedInAs(orgB, OrgRole.ADMIN); + const b = await listBatchesRoute(get(URL_BATCHES, { 'x-organization-id': orgB.orgId })); + const bodyB = await b.json(); + expect(bodyB.batches).toHaveLength(1); + expect(bodyB.batches[0].id).toBe(batchB); + + // Six payments exist; each organization sees three. + expect(await prisma.payment.count()).toBe(6); + }); + + it('gives a cross-tenant batch read the same 404 as a non-existent one', async () => { + signedInAs(orgB, OrgRole.ADMIN); + const foreign = await getBatchRoute( + get(`${URL_BATCHES}/${batchA}`, { 'x-organization-id': orgB.orgId }), + { params: { id: batchA } }, + ); + const absent = await getBatchRoute( + get(`${URL_BATCHES}/cmdoesnotexist000000000`, { 'x-organization-id': orgB.orgId }), + { params: { id: 'cmdoesnotexist000000000' } }, + ); + expect(foreign.status).toBe(404); + expect(absent.status).toBe(404); + // Byte-identical, so an id cannot be probed for existence. + expect(await foreign.json()).toEqual(await absent.json()); + }); + + it('refuses to approve, re-validate or name another tenant batch', async () => { + signedInAs(orgB, OrgRole.MANAGER); + const approve = await approveBatchRoute( + post(`${URL_BATCHES}/${batchA}/approve`, {}, { 'x-organization-id': orgB.orgId }), + { params: { id: batchA } }, + ); + const revalidate = await revalidateRoute( + post(`${URL_BATCHES}/${batchA}/validate`, {}, { 'x-organization-id': orgB.orgId }), + { params: { id: batchA } }, + ); + expect(approve.status).toBe(404); + expect(revalidate.status).toBe(404); + expect(await prisma.approval.count()).toBe(0); + }); + + it('refuses to act in an organization the caller does not belong to', async () => { + // Authenticated as an org B member, naming org A. + signedInAs(orgB, OrgRole.ADMIN); + const res = await createBatchRoute( + post(URL_BATCHES, { csv: THREE_CONTRACTORS }, { 'x-organization-id': orgA.orgId }), + ); + expect(res.status).toBe(404); + expect(await prisma.payrollBatch.count({ where: { orgId: orgA.orgId } })).toBe(1); + }); + + it('isolates workers, projects, audit events and findings by tenant', async () => { + await seedWorker(prisma, orgA.orgId, 'wa'); + await seedWorker(prisma, orgB.orgId, 'wb'); + await prisma.project.create({ data: { orgId: orgA.orgId, code: 'PA', name: 'A' } }); + await prisma.project.create({ data: { orgId: orgB.orgId, code: 'PB', name: 'B' } }); + const paymentA = await prisma.payment.findFirstOrThrow({ + where: { orgId: orgA.orgId }, + select: { id: true }, + }); + await prisma.reconciliationFinding.create({ + data: { + orgId: orgA.orgId, + paymentId: paymentA.id, + kind: 'ASSET_MISMATCH', + detail: 'fixture', + severity: 'HIGH', + }, + }); + + for (const [org, other] of [ + [orgA, orgB], + [orgB, orgA], + ] as const) { + const scoped = { orgId: org.orgId }; + expect(await prisma.worker.count({ where: scoped })).toBe(1); + expect(await prisma.project.count({ where: scoped })).toBe(1); + expect(await prisma.payment.count({ where: scoped })).toBe(3); + // And nothing of the other tenant's leaks into the same filter. + const workers = await prisma.worker.findMany({ where: scoped }); + expect(workers.every((w) => w.orgId !== other.orgId)).toBe(true); + } + + expect(await prisma.reconciliationFinding.count({ where: { orgId: orgB.orgId } })).toBe(0); + expect(await prisma.reconciliationFinding.count({ where: { orgId: orgA.orgId } })).toBe(1); + }); +}); + +// --------------------------------------------------------------------------- +// Reads and re-validation +// --------------------------------------------------------------------------- + +describe('Batch reads', () => { + it('derives standing from the payments on every read', async () => { + const { body } = await createThreePaymentBatch(orgA); + signedInAs(orgA, OrgRole.ADMIN); + + const res = await getBatchRoute( + get(`${URL_BATCHES}/${body.batch.id}`, { 'x-organization-id': orgA.orgId }), + { params: { id: body.batch.id } }, + ); + const { batch } = await res.json(); + + expect(batch.paymentCount).toBe(3); + expect(batch.standing.totalAmountBaseUnits).toBe(THREE_TOTAL.toString()); + expect(batch.standing.paidAmountBaseUnits).toBe('0'); + expect(batch.payments.every((p: any) => p.transactionHash === null)).toBe(true); + + // There is no stored status column for the standing to drift from. + const columns = await prisma.$queryRawUnsafe<{ column_name: string }[]>( + `SELECT column_name FROM information_schema.columns + WHERE table_name = 'PayrollBatch' AND column_name IN ('status','state')`, + ); + expect(columns).toHaveLength(0); + }); + + it('reports a draft whose asset is no longer settleable', async () => { + const { body } = await createThreePaymentBatch(orgA); + signedInAs(orgA, OrgRole.ADMIN); + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'EURC'; + try { + const res = await revalidateRoute( + post(`${URL_BATCHES}/${body.batch.id}/validate`, {}, { + 'x-organization-id': orgA.orgId, + }), + { params: { id: body.batch.id } }, + ); + const report = await res.json(); + expect(report.valid).toBe(false); + expect(report.errors.some((e: any) => e.code === 'ASSET_NOT_SETTLEABLE')).toBe(true); + } finally { + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'USDC'; + } + + // A read-only check: the payments are untouched. + const payments = await prisma.payment.findMany({ where: { orgId: orgA.orgId } }); + expect(payments.every((p) => p.assetCode === 'USDC')).toBe(true); + expect(payments.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + }); +}); diff --git a/src/app/api/payroll/batches/[id]/approve/route.ts b/src/app/api/payroll/batches/[id]/approve/route.ts new file mode 100644 index 0000000..3fd3c19 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/approve/route.ts @@ -0,0 +1,69 @@ +/** + * POST /api/payroll/batches/:id/approve β€” record the caller's approval + * + * The request does NOT say which role is approving. That is derived from the + * caller's membership, because a body-supplied role would let one manager send + * `{"role":"FINANCE"}` and satisfy both halves of the dual-approval gate alone β€” + * exactly what the contract refuses with SignersNotDistinct. + * + * Nor does the request name a destination state. This records an off-chain + * approval DECISION; the authoritative approval is the on-chain signature the + * indexer observes. Recording a decision here does not settle anything, and + * cannot. + * + * Per-payment outcomes are reported individually: "11 approved, 1 needs + * attention" is the normal result of a real batch. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch, requireAnyPermission } from '@/lib/tenancy/resolve'; +import { errorResponse, handleRouteError } from '@/lib/api/errors'; +import { approveBatch } from '@/lib/payroll/api'; +import { approveBatchRequest, zodIssues } from '@/lib/payroll/schemas'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + // No `permission` here: either half of the gate is a legitimate approver, and + // gating on one would reject the other. The central helper checks for either, + // and the caller's ROLE still decides which half they exercise. + return withTenant(request, {}, async ({ ctx, body, idempotencyKey }) => { + const denied = requireAnyPermission(ctx, [ + 'payment:approve:manager', + 'payment:approve:finance', + ]); + if (denied) return denialResponse(denied); + + const parsed = approveBatchRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const found = await findBatch(prisma, ctx, params.id, { + payments: { select: { id: true, state: true } }, + }); + if (!found.ok) return denialResponse(found); + + const outcome = await approveBatch( + prisma, + ctx, + { id: found.value.id, payments: found.value.payments }, + { + paymentIds: parsed.data.paymentIds, + reason: parsed.data.reason, + idempotencyKey: idempotencyKey ?? parsed.data.idempotencyKey, + }, + ); + + // 200 even when some payments failed: the batch-level request succeeded and + // the body reports each outcome. A blanket 4xx would discard the approvals + // that were legitimately recorded. + return NextResponse.json(outcome, { status: 200 }); + } catch (e) { + return handleRouteError('payroll.batch.approve.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/funding/abandon/route.ts b/src/app/api/payroll/batches/[id]/funding/abandon/route.ts new file mode 100644 index 0000000..22e50e3 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/funding/abandon/route.ts @@ -0,0 +1,51 @@ +/** + * POST /api/payroll/batches/:id/funding/abandon + * + * Closes an attempt that did not reach the network β€” a declined signature, a + * simulation failure β€” so the batch is not blocked forever. + * + * Narrowly defined on purpose. It never asserts that an on-chain escrow does not + * exist: payments return to DRAFT only when NOTHING was submitted. Once a + * transaction hash exists the money may have moved, and manufacturing a "not + * funded" state would invite funding a second escrow. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { assertJsonContentType, errorResponse, handleRouteError } from '@/lib/api/errors'; +import { failFundingIntent } from '@/lib/funding/service'; +import { fundingAbandonRequest, zodIssues } from '@/lib/payroll/schemas'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + try { + assertJsonContentType(request); + } catch (e) { + return handleRouteError('funding.abandon.POST', e); + } + + return withTenant(request, { permission: 'escrow:create' }, async ({ ctx, body }) => { + const parsed = fundingAbandonRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const found = await findBatch(prisma, ctx, params.id, undefined); + if (!found.ok) return denialResponse(found); + + const attempt = await failFundingIntent(prisma, ctx, { + attemptId: parsed.data.attemptId, + reason: parsed.data.reason, + userRejected: parsed.data.userRejected, + batchId: found.value.id, + }); + return NextResponse.json({ attempt }); + } catch (e) { + return handleRouteError('funding.abandon.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/funding/confirm/route.ts b/src/app/api/payroll/batches/[id]/funding/confirm/route.ts new file mode 100644 index 0000000..655e4da --- /dev/null +++ b/src/app/api/payroll/batches/[id]/funding/confirm/route.ts @@ -0,0 +1,62 @@ +/** + * POST /api/payroll/batches/:id/funding/confirm + * + * Independent chain verification. Four outcomes, deliberately never collapsed: + * + * CONFIRMED the chain agrees with the frozen plan, payment by payment, and a + * transfer of the exact total reached custody in that transaction + * FAILED the chain says the transaction failed; nothing moved + * UNVERIFIABLE the chain could not be read. NOT a failure β€” an RPC outage does + * not prove anything about the transaction + * MISMATCH we read the chain and it disagrees with the plan. The escrow is + * NOT adopted, and a CRITICAL finding preserves the evidence + * + * Safe to call repeatedly: a confirmed attempt replays its answer. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { assertJsonContentType, errorResponse, handleRouteError } from '@/lib/api/errors'; +import { confirmFunding } from '@/lib/funding/service'; +import { createRpcVerifier } from '@/lib/reconciliation/chain-verifier'; +import { fundingConfirmRequest, zodIssues } from '@/lib/payroll/schemas'; + +/** Reading the chain can be slow; this must not be cut short mid-verification. */ +export const maxDuration = 120; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + try { + assertJsonContentType(request); + } catch (e) { + return handleRouteError('funding.confirm.POST', e); + } + + return withTenant(request, { permission: 'escrow:create' }, async ({ ctx, body }) => { + const parsed = fundingConfirmRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const found = await findBatch(prisma, ctx, params.id, undefined); + if (!found.ok) return denialResponse(found); + + const result = await confirmFunding(prisma, ctx, createRpcVerifier(), { + attemptId: parsed.data.attemptId, + onChainEscrowId: parsed.data.onChainEscrowId, + batchId: found.value.id, + }); + + // 200 for every outcome: the verification REQUEST succeeded, and the body + // says what the chain showed. A 4xx would conflate "we could not check" with + // "your request was wrong". + return NextResponse.json(result); + } catch (e) { + return handleRouteError('funding.confirm.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/funding/intent/route.ts b/src/app/api/payroll/batches/[id]/funding/intent/route.ts new file mode 100644 index 0000000..ec09e19 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/funding/intent/route.ts @@ -0,0 +1,52 @@ +/** + * POST /api/payroll/batches/:id/funding/intent + * + * Opens the funding intent and freezes the plan, BEFORE any wallet is shown. + * + * This is the anti-double-funding boundary. `initialize_multi_sig_escrow` creates + * the escrow and moves custody in one atomic invocation and offers no idempotency, + * so a second submission would fund a second escrow. The unique index on the + * attempt's idempotency key is what prevents that β€” not a disabled button, not + * client state. A second call while an attempt is open returns THAT attempt. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { assertJsonContentType, errorResponse, handleRouteError } from '@/lib/api/errors'; +import { openFundingIntent } from '@/lib/funding/service'; +import { fundingIntentRequest, zodIssues } from '@/lib/payroll/schemas'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + try { + assertJsonContentType(request); + } catch (e) { + return handleRouteError('funding.intent.POST', e); + } + + return withTenant(request, { permission: 'escrow:create' }, async ({ ctx, body }) => { + const parsed = fundingIntentRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const found = await findBatch(prisma, ctx, params.id, undefined); + if (!found.ok) return denialResponse(found); + + const result = await openFundingIntent(prisma, ctx, { + id: found.value.id, + reference: found.value.reference, + }); + + // 201 only when an attempt was actually opened. A recovered attempt returns + // 200, so a client can tell "I started this" from "this was already open". + return NextResponse.json(result, { status: result.created ? 201 : 200 }); + } catch (e) { + return handleRouteError('funding.intent.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/funding/route.ts b/src/app/api/payroll/batches/[id]/funding/route.ts new file mode 100644 index 0000000..7f44c53 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/funding/route.ts @@ -0,0 +1,31 @@ +/** + * GET /api/payroll/batches/:id/funding β€” may this batch be funded, and with what? + * + * Read-only, and safe to poll. Returns every blocker at once, plus the exact plan + * when the batch is fundable β€” which is the disclosure the signer is entitled to + * see before a wallet opens. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { handleRouteError } from '@/lib/api/errors'; +import { getFundingState } from '@/lib/funding/service'; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'escrow:read', parseBody: false }, async ({ ctx }) => { + try { + const found = await findBatch(prisma, ctx, params.id, undefined); + if (!found.ok) return denialResponse(found); + + const state = await getFundingState(prisma, ctx, { + id: found.value.id, + reference: found.value.reference, + }); + return NextResponse.json(state); + } catch (e) { + return handleRouteError('funding.GET', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/funding/submitted/route.ts b/src/app/api/payroll/batches/[id]/funding/submitted/route.ts new file mode 100644 index 0000000..5c8f191 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/funding/submitted/route.ts @@ -0,0 +1,48 @@ +/** + * POST /api/payroll/batches/:id/funding/submitted + * + * Records that a signed transaction reached the network. It does NOT mark the + * escrow funded: a submitted transaction is not a settled one, and the only thing + * that establishes funding is reading the chain back. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { assertJsonContentType, errorResponse, handleRouteError } from '@/lib/api/errors'; +import { recordFundingSubmitted } from '@/lib/funding/service'; +import { fundingSubmittedRequest, zodIssues } from '@/lib/payroll/schemas'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + try { + assertJsonContentType(request); + } catch (e) { + return handleRouteError('funding.submitted.POST', e); + } + + return withTenant(request, { permission: 'escrow:create' }, async ({ ctx, body }) => { + const parsed = fundingSubmittedRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + // The batch is resolved within the tenant first, so an attempt id from another + // organization cannot be reached by naming it. + const found = await findBatch(prisma, ctx, params.id, undefined); + if (!found.ok) return denialResponse(found); + + const attempt = await recordFundingSubmitted(prisma, ctx, { + attemptId: parsed.data.attemptId, + transactionHash: parsed.data.transactionHash, + batchId: found.value.id, + }); + return NextResponse.json({ attempt }); + } catch (e) { + return handleRouteError('funding.submitted.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/route.ts b/src/app/api/payroll/batches/[id]/route.ts new file mode 100644 index 0000000..ed75346 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/route.ts @@ -0,0 +1,60 @@ +/** + * GET /api/payroll/batches/:id β€” one batch, with its payments + * + * The batch's standing is DERIVED from its payments on every read. There is no + * stored status column to disagree with the rows it summarizes. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { handleRouteError } from '@/lib/api/errors'; +import { presentBatch, presentActivity, presentFindings } from '@/lib/payroll/api'; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'payroll:read', parseBody: false }, async ({ ctx }) => { + try { + // Tenant-scoped lookup. A batch in another organization returns the same + // 404 as one that does not exist, so an id cannot be probed for existence. + const found = await findBatch(prisma, ctx, params.id, { + payments: { + orderBy: [{ createdAt: 'asc' }, { id: 'asc' }], + include: { + approvals: { + select: { role: true, decision: true, actorAddress: true, createdAt: true }, + }, + }, + }, + }); + if (!found.ok) return denialResponse(found); + + // The activity timeline and any findings, both tenant-scoped by the same + // organization filter the batch itself was resolved with. + const [events, findings] = await Promise.all([ + prisma.auditEvent.findMany({ + where: { orgId: ctx.orgId, batchId: found.value.id }, + orderBy: [{ createdAt: 'asc' }], + take: 200, + }), + prisma.reconciliationFinding.findMany({ + where: { + orgId: ctx.orgId, + status: { not: 'RESOLVED' }, + paymentId: { in: found.value.payments.map((p: { id: string }) => p.id) }, + }, + orderBy: [{ detectedAt: 'desc' }], + take: 50, + }), + ]); + + return NextResponse.json({ + batch: presentBatch(found.value), + activity: presentActivity(events), + findings: presentFindings(findings), + }); + } catch (e) { + return handleRouteError('payroll.batch.GET', e); + } + }); +} diff --git a/src/app/api/payroll/batches/[id]/validate/route.ts b/src/app/api/payroll/batches/[id]/validate/route.ts new file mode 100644 index 0000000..d75cbd4 --- /dev/null +++ b/src/app/api/payroll/batches/[id]/validate/route.ts @@ -0,0 +1,54 @@ +/** + * POST /api/payroll/batches/:id/validate β€” re-check an existing draft + * + * Configuration can move under a batch: a payroll reviewed on Monday and funded + * on Wednesday may have been denominated in an asset this deployment no longer + * settles. This catches that before a wallet is opened. + * + * Read-only. It reports, and changes nothing β€” no state transition, no approval, + * no transaction. Safe to call repeatedly. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { findBatch } from '@/lib/tenancy/resolve'; +import { errorResponse, handleRouteError } from '@/lib/api/errors'; +import { revalidateBatch } from '@/lib/payroll/api'; +import { revalidateBatchRequest, zodIssues } from '@/lib/payroll/schemas'; + +export async function POST(request: NextRequest, { params }: { params: { id: string } }) { + return withTenant(request, { permission: 'payroll:read' }, async ({ ctx, body }) => { + const parsed = revalidateBatchRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const found = await findBatch(prisma, ctx, params.id, { + payments: { + select: { + recipientAddress: true, + assetCode: true, + assetDecimals: true, + amountBaseUnits: true, + rateBaseUnits: true, + hours: true, + }, + }, + }); + if (!found.ok) return denialResponse(found); + + const report = await revalidateBatch(prisma, ctx, { + id: found.value.id, + reference: found.value.reference, + payments: found.value.payments, + }); + return NextResponse.json(report, { status: 200 }); + } catch (e) { + return handleRouteError('payroll.batch.validate.POST', e); + } + }); +} diff --git a/src/app/api/payroll/batches/route.ts b/src/app/api/payroll/batches/route.ts new file mode 100644 index 0000000..f7bd7dc --- /dev/null +++ b/src/app/api/payroll/batches/route.ts @@ -0,0 +1,150 @@ +/** + * POST /api/payroll/batches β€” create a draft batch from an uploaded CSV + * GET /api/payroll/batches β€” list this organization's batches + * + * Creation produces one DRAFT Payment per valid CSV row. It does NOT touch the + * chain, fund custody, or advance any payment toward settlement: a draft is a + * reviewable intention, and every step after it is a separate, explicit action. + * + * The route decodes and renders. Every rule lives in the domain services it calls. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { withTenant, denialResponse } from '@/lib/tenancy/http'; +import { assertProjectInTenant } from '@/lib/tenancy/resolve'; +import { + assertDeclaredSizeWithin, + assertJsonContentType, + errorResponse, + handleRouteError, +} from '@/lib/api/errors'; +import { rateLimit, clientIp } from '@/lib/ratelimit'; +import { createBatch } from '@/lib/payroll/api'; +import { createBatchRequest, listBatchesQuery, zodIssues } from '@/lib/payroll/schemas'; +import { MAX_CSV_BYTES } from '@/lib/payroll/csv'; +import { rollupBatch } from '@/lib/payments/service'; +import { formatAmountWithSeparators } from '@/lib/money'; + +/** The JSON envelope around a 1 MB file, plus headroom for escaping. */ +const MAX_REQUEST_BYTES = MAX_CSV_BYTES + 64 * 1024; + +/** + * Parsing a megabyte of caller-chosen text is the expensive part of this route, + * so the brake is applied before the body is read rather than after. + */ +const UPLOAD_LIMIT = 20; +const UPLOAD_WINDOW_MS = 60_000; + +export async function POST(request: NextRequest) { + try { + assertJsonContentType(request); + assertDeclaredSizeWithin(request, MAX_REQUEST_BYTES); + } catch (e) { + return handleRouteError('payroll.batches.POST', e); + } + + const limit = rateLimit(`payroll:create:${clientIp(request)}`, UPLOAD_LIMIT, UPLOAD_WINDOW_MS); + if (!limit.ok) { + return NextResponse.json( + { + error: `Too many payroll uploads. Try again in ${limit.retryAfter} seconds.`, + code: 'RATE_LIMITED', + }, + { status: 429, headers: { 'Retry-After': String(limit.retryAfter) } }, + ); + } + + return withTenant( + request, + { permission: 'payroll:create' }, + async ({ ctx, body, idempotencyKey }) => { + const parsed = createBatchRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + const input = parsed.data; + + // A project id is an id, not a grant. Resolved within the tenant, so naming + // another organization's project is a non-enumerating miss. + const projectDenial = await assertProjectInTenant(prisma, ctx, input.projectId); + if (projectDenial) return denialResponse(projectDenial); + + try { + const outcome = await createBatch(prisma, ctx, { + csv: input.csv, + filename: input.filename ?? null, + reference: input.reference ?? null, + projectId: input.projectId ?? null, + rejectDuplicateRecipients: input.rejectDuplicateRecipients, + // Header wins over body: the header is the HTTP-level retry key a proxy + // or client library will reuse automatically. + idempotencyKey: idempotencyKey ?? input.idempotencyKey ?? null, + }); + + // 201 only when something was actually created. A replayed retry returns + // 200, so a client can tell "I made this" from "this already existed". + return NextResponse.json(outcome, { status: outcome.created ? 201 : 200 }); + } catch (e) { + return handleRouteError('payroll.batches.POST', e); + } + }, + ); +} + +export async function GET(request: NextRequest) { + return withTenant(request, { permission: 'payroll:read', parseBody: false }, async ({ ctx }) => { + const query = listBatchesQuery.safeParse( + Object.fromEntries(new URL(request.url).searchParams.entries()), + ); + if (!query.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The query could not be read.', { + errors: zodIssues(query.error), + }); + } + const { limit = 25, cursor, projectId } = query.data; + + try { + const rows = await prisma.payrollBatch.findMany({ + // orgId is part of the QUERY, not a check afterwards: another tenant's + // batch is indistinguishable from one that does not exist. + where: { orgId: ctx.orgId, ...(projectId ? { projectId } : {}) }, + orderBy: [{ createdAt: 'desc' }, { id: 'desc' }], + take: limit + 1, + ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}), + include: { + payments: { select: { state: true, amountBaseUnits: true, assetCode: true, assetDecimals: true } }, + }, + }); + + const page = rows.slice(0, limit); + return NextResponse.json({ + batches: page.map((b) => { + const standing = rollupBatch(b.payments); + const decimals = b.payments[0]?.assetDecimals ?? 7; + return { + id: b.id, + reference: b.reference, + projectId: b.projectId, + createdAt: b.createdAt.toISOString(), + periodStart: b.periodStart?.toISOString() ?? null, + periodEnd: b.periodEnd?.toISOString() ?? null, + paymentCount: b.payments.length, + asset: b.payments[0]?.assetCode ?? null, + total: formatAmountWithSeparators(standing.totalAmountBaseUnits, decimals), + totalBaseUnits: standing.totalAmountBaseUnits.toString(), + // Derived on read. There is no stored status column to drift. + headline: standing.headline, + needsAttention: standing.needsAttention, + source: { filename: b.sourceFilename, rowsSeen: b.sourceRowCount }, + }; + }), + nextCursor: rows.length > limit ? page[page.length - 1].id : null, + }); + } catch (e) { + return handleRouteError('payroll.batches.GET', e); + } + }); +} diff --git a/src/app/api/payroll/batches/validate/route.ts b/src/app/api/payroll/batches/validate/route.ts new file mode 100644 index 0000000..a731906 --- /dev/null +++ b/src/app/api/payroll/batches/validate/route.ts @@ -0,0 +1,83 @@ +/** + * POST /api/payroll/batches/validate β€” check a CSV without creating anything + * + * A dry run, and safe to call repeatedly. It writes NOTHING: no batch, no + * payment, no approval, no transaction, no state change. That is the whole point + * β€” an uploader must be able to see every problem in a file and fix them before + * any record exists. + * + * Returns 200 with `valid: false` for an invalid file. The CALL succeeded; the + * file is the thing that is wrong, and a reviewer refreshing a preview is not + * making failing requests. Creation, by contrast, rejects an invalid file with + * 422 β€” there the content being wrong does mean the request cannot be honoured. + * + * Distinct from POST /api/payroll/batches/:id/validate, which re-checks an + * EXISTING draft against current configuration. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { withTenant } from '@/lib/tenancy/http'; +import { + assertDeclaredSizeWithin, + assertJsonContentType, + errorResponse, + handleRouteError, +} from '@/lib/api/errors'; +import { rateLimit, clientIp } from '@/lib/ratelimit'; +import { validateCsv } from '@/lib/payroll/api'; +import { validateCsvRequest, zodIssues } from '@/lib/payroll/schemas'; +import { MAX_CSV_BYTES } from '@/lib/payroll/csv'; + +const MAX_REQUEST_BYTES = MAX_CSV_BYTES + 64 * 1024; + +/** + * Looser than creation's limit: a preview is meant to be called as an uploader + * iterates on a file, and throttling that would push them toward guessing. + */ +const VALIDATE_LIMIT = 60; +const VALIDATE_WINDOW_MS = 60_000; + +export async function POST(request: NextRequest) { + try { + assertJsonContentType(request); + assertDeclaredSizeWithin(request, MAX_REQUEST_BYTES); + } catch (e) { + return handleRouteError('payroll.validate.POST', e); + } + + const limit = rateLimit( + `payroll:validate:${clientIp(request)}`, + VALIDATE_LIMIT, + VALIDATE_WINDOW_MS, + ); + if (!limit.ok) { + return NextResponse.json( + { + error: `Too many validation requests. Try again in ${limit.retryAfter} seconds.`, + code: 'RATE_LIMITED', + }, + { status: 429, headers: { 'Retry-After': String(limit.retryAfter) } }, + ); + } + + // `payroll:create` rather than `payroll:read`: this is the preflight for + // creating a payroll, so the people entitled to run it are the ones entitled + // to create one. + return withTenant(request, { permission: 'payroll:create' }, async ({ body }) => { + const parsed = validateCsvRequest.safeParse(body ?? {}); + if (!parsed.success) { + return errorResponse(400, 'MALFORMED_REQUEST', 'The request could not be read.', { + errors: zodIssues(parsed.error), + }); + } + + try { + const { report } = validateCsv(parsed.data.csv, { + rejectDuplicateRecipients: parsed.data.rejectDuplicateRecipients, + }); + return NextResponse.json(report, { status: 200 }); + } catch (e) { + return handleRouteError('payroll.validate.POST', e); + } + }); +} diff --git a/src/app/api/reconciliation/__tests__/routes.test.ts b/src/app/api/reconciliation/__tests__/routes.test.ts new file mode 100644 index 0000000..fdee07a --- /dev/null +++ b/src/app/api/reconciliation/__tests__/routes.test.ts @@ -0,0 +1,329 @@ +// @vitest-environment node +/** + * Reconciliation route tests. + * + * Two security properties dominate: the cross-organization cron trigger must not + * be reachable without the shared secret, and resolving a finding must not be a + * way to alter financial state or to dismiss a discrepancy silently. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { OrgRole, MembershipStatus, FindingStatus, FindingSeverity, FindingKind } from '@prisma/client'; + +vi.mock('@/lib/db/prisma', () => { + const prisma: any = { + organization: { findMany: vi.fn() }, + orgMember: { findUnique: vi.fn(), findMany: vi.fn() }, + reconciliationFinding: { findFirst: vi.fn(), findMany: vi.fn(), update: vi.fn(), groupBy: vi.fn() }, + reconciliationRun: { findFirst: vi.fn(), findMany: vi.fn() }, + auditEvent: { create: vi.fn() }, + }; + prisma.$transaction = vi.fn(async (fn: any) => fn(prisma)); + return { default: prisma }; +}); +vi.mock('@/lib/auth', () => ({ getUserFromRequest: vi.fn() })); +vi.mock('@/lib/reconciliation/scheduler', () => ({ + runReconciliation: vi.fn(async () => ({ + runId: 'r1', correlationId: 'rec_x', status: 'COMPLETED', + escrowsExamined: 1, paymentsExamined: 3, agreed: 3, mismatched: 0, + unreadable: 0, chainAhead: 0, databaseAhead: 0, + findingsOpened: 0, correctionsApplied: 0, + })), + reconciliationHealth: vi.fn(async () => ({ + lastRun: null, openFindings: 0, criticalFindings: 0, + oldestUnresolvedHours: null, degraded: true, + degradedReason: 'Reconciliation has never run for this organization.', + })), +})); +vi.mock('@/lib/explorer', () => ({ txUrl: (h: string) => `https://explorer/tx/${h}` })); + +import { POST as cronRun } from '../run/route'; +import { PATCH as patchFinding } from '../../organizations/[id]/findings/[findingId]/route'; +import { GET as listFindings } from '../../organizations/[id]/findings/route'; +import { getUserFromRequest } from '@/lib/auth'; +import { runReconciliation } from '@/lib/reconciliation/scheduler'; +import prismaDefault from '@/lib/db/prisma'; + +const prismaMock = prismaDefault as any; +const mockUser = getUserFromRequest as unknown as ReturnType; +const ORG = 'orgA'; +const SECRET = 'a-sufficiently-long-cron-secret'; + +function signedInAs(role: OrgRole, orgId = ORG) { + mockUser.mockResolvedValue({ userId: 'u1', walletAddress: 'G' + 'A'.repeat(55), role: 'EMPLOYEE' }); + prismaMock.orgMember.findUnique.mockResolvedValue({ + orgId, userId: 'u1', role, status: MembershipStatus.ACTIVE, + org: { id: orgId, name: 'Org A', slug: 'org-a' }, + user: { walletAddress: 'G' + 'A'.repeat(55) }, + }); + prismaMock.orgMember.findMany.mockResolvedValue([{ orgId, role }]); +} + +beforeEach(() => { + vi.clearAllMocks(); + process.env.CRON_SECRET = SECRET; + prismaMock.reconciliationFinding.findMany.mockResolvedValue([]); + prismaMock.reconciliationFinding.groupBy.mockResolvedValue([]); + prismaMock.organization.findMany.mockResolvedValue([{ id: ORG, slug: 'org-a' }]); +}); +afterEach(() => { + delete process.env.CRON_SECRET; + delete process.env.INDEXER_SECRET; +}); + +describe('POST /api/reconciliation/run β€” the scheduled trigger', () => { + const req = (auth?: string) => + new Request('http://localhost/api/reconciliation/run', { + method: 'POST', + headers: { 'x-forwarded-for': '203.0.113.5', ...(auth ? { authorization: auth } : {}) }, + }) as any; + + it('runs for every organization with the correct secret', async () => { + const res = await cronRun(req(`Bearer ${SECRET}`)); + expect(res.status).toBe(200); + expect(runReconciliation).toHaveBeenCalledTimes(1); + }); + + it('404s without the secret, revealing nothing about the endpoint', async () => { + const res = await cronRun(req()); + expect(res.status).toBe(404); + expect(runReconciliation).not.toHaveBeenCalled(); + }); + + it('404s with a wrong secret', async () => { + const res = await cronRun(req('Bearer not-the-secret-at-all-really')); + expect(res.status).toBe(404); + expect(runReconciliation).not.toHaveBeenCalled(); + }); + + it('404s when no secret is configured', async () => { + delete process.env.CRON_SECRET; + const res = await cronRun(req('Bearer anything')); + expect(res.status).toBe(404); + }); + + it('refuses to run behind a secret too short to resist guessing', async () => { + // A short secret reads as protection while providing none. + process.env.CRON_SECRET = 'short'; + const res = await cronRun(req('Bearer short')); + expect(res.status).toBe(404); + expect(runReconciliation).not.toHaveBeenCalled(); + }); + + it('continues the sweep when one organization fails', async () => { + // One tenant's RPC trouble must not stop another's reconciliation. + prismaMock.organization.findMany.mockResolvedValue([ + { id: 'o1', slug: 's1' }, { id: 'o2', slug: 's2' }, + ]); + (runReconciliation as any) + .mockRejectedValueOnce(new Error('rpc down for o1')) + .mockResolvedValueOnce({ runId: 'r2', correlationId: 'c2', status: 'COMPLETED' }); + + const res = await cronRun(req(`Bearer ${SECRET}`)); + const body = await res.json(); + + expect(res.status).toBe(200); + expect(body.results).toHaveLength(2); + expect(body.results[0].error).toMatch(/rpc down/); + expect(body.results[1].status).toBe('COMPLETED'); + }); +}); + +describe('PATCH finding lifecycle', () => { + const req = (body: unknown, orgId = ORG) => + new Request(`http://localhost/api/organizations/${orgId}/findings/f1`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json', 'x-organization-id': orgId }, + body: JSON.stringify(body), + }) as any; + + const openFinding = { + id: 'f1', orgId: ORG, status: FindingStatus.OPEN, + severity: FindingSeverity.CRITICAL, kind: FindingKind.DB_PAID_CHAIN_NOT, + paymentId: 'pay1', txHash: 'HASH', + }; + + it('401s when unauthenticated', async () => { + mockUser.mockResolvedValue(null); + const res = await patchFinding(req({ status: 'ACKNOWLEDGED' }), { params: { id: ORG, findingId: 'f1' } }); + expect(res.status).toBe(401); + }); + + it('403s a role that cannot resolve findings', async () => { + signedInAs(OrgRole.VIEWER); + const res = await patchFinding(req({ status: 'ACKNOWLEDGED' }), { params: { id: ORG, findingId: 'f1' } }); + expect(res.status).toBe(403); + }); + + it('404s a finding belonging to another organization', async () => { + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue(null); + const res = await patchFinding(req({ status: 'ACKNOWLEDGED' }), { params: { id: ORG, findingId: 'f1' } }); + expect(res.status).toBe(404); + expect(prismaMock.reconciliationFinding.update).not.toHaveBeenCalled(); + }); + + it('acknowledges a finding and records who did it', async () => { + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue(openFinding); + prismaMock.reconciliationFinding.update.mockResolvedValue({ + ...openFinding, status: FindingStatus.ACKNOWLEDGED, acknowledgedBy: 'G' + 'A'.repeat(55), + }); + + const res = await patchFinding(req({ status: 'ACKNOWLEDGED' }), { params: { id: ORG, findingId: 'f1' } }); + + expect(res.status).toBe(200); + const data = prismaMock.reconciliationFinding.update.mock.calls[0][0].data; + expect(data.acknowledgedBy).toBe('G' + 'A'.repeat(55)); + expect(prismaMock.auditEvent.create).toHaveBeenCalled(); + }); + + it('refuses to resolve without a substantive explanation', async () => { + // A "mark resolved" button with no reason turns the queue into a dismiss + // button, and the next reader during an incident learns nothing. + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue(openFinding); + + for (const resolution of [undefined, '', 'ok', 'fixed']) { + const res = await patchFinding( + req({ status: 'RESOLVED', resolution }), + { params: { id: ORG, findingId: 'f1' } } + ); + const body = await res.json(); + expect(res.status).toBe(400); + expect(body.code).toBe('RESOLUTION_REASON_REQUIRED'); + } + expect(prismaMock.reconciliationFinding.update).not.toHaveBeenCalled(); + }); + + it('resolves with an explanation, recording actor and reason', async () => { + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue(openFinding); + prismaMock.reconciliationFinding.update.mockResolvedValue({ + ...openFinding, status: FindingStatus.RESOLVED, + }); + + const reason = 'Confirmed on the explorer that tx HASH settled; indexer had lagged.'; + const res = await patchFinding( + req({ status: 'RESOLVED', resolution: reason }), + { params: { id: ORG, findingId: 'f1' } } + ); + + expect(res.status).toBe(200); + const data = prismaMock.reconciliationFinding.update.mock.calls[0][0].data; + expect(data.resolution).toBe(reason); + expect(data.resolvedBy).toBe('G' + 'A'.repeat(55)); + const audit = prismaMock.auditEvent.create.mock.calls[0][0].data; + expect(audit.type).toBe('reconciliation.finding.resolved'); + expect(audit.metadata.resolution).toBe(reason); + }); + + it('refuses an invalid lifecycle move', async () => { + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue({ + ...openFinding, status: FindingStatus.RESOLVED, + }); + const res = await patchFinding( + req({ status: 'OPEN' }), + { params: { id: ORG, findingId: 'f1' } } + ); + const body = await res.json(); + expect(res.status).toBe(409); + expect(body.code).toBe('INVALID_FINDING_TRANSITION'); + }); + + it('cannot alter payment state, amount or transaction hash', async () => { + // Resolving records a judgement ABOUT a discrepancy; it must not be a channel + // for writing financial fields. + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findFirst.mockResolvedValue(openFinding); + prismaMock.reconciliationFinding.update.mockResolvedValue({ + ...openFinding, status: FindingStatus.RESOLVED, + }); + + await patchFinding( + req({ + status: 'RESOLVED', + resolution: 'Investigated and confirmed settled on chain.', + // All of these are attempts to smuggle financial mutations through. + paymentState: 'PAID', + amountBaseUnits: '999999', + txHash: 'FORGED_HASH', + severity: 'LOW', + orgId: 'orgB', + }), + { params: { id: ORG, findingId: 'f1' } } + ); + + const data = prismaMock.reconciliationFinding.update.mock.calls[0][0].data; + // Only lifecycle fields were written. + expect(Object.keys(data).sort()).toEqual(['resolution', 'resolvedAt', 'resolvedBy', 'status']); + expect(data.txHash).toBeUndefined(); + expect(data.severity).toBeUndefined(); + }); +}); + +describe('GET findings', () => { + const req = (qs = '') => + new Request(`http://localhost/api/organizations/${ORG}/findings${qs}`, { + headers: { 'x-organization-id': ORG }, + }) as any; + + it('403s a role without reconciliation read access', async () => { + signedInAs(OrgRole.WORKER); + const res = await listFindings(req(), { params: { id: ORG } }); + expect(res.status).toBe(403); + }); + + it('scopes the query to the caller’s organization', async () => { + signedInAs(OrgRole.OWNER); + await listFindings(req(), { params: { id: ORG } }); + const where = prismaMock.reconciliationFinding.findMany.mock.calls[0][0].where; + expect(where.orgId).toBe(ORG); + }); + + it('defaults to unresolved findings', async () => { + signedInAs(OrgRole.OWNER); + await listFindings(req(), { params: { id: ORG } }); + const where = prismaMock.reconciliationFinding.findMany.mock.calls[0][0].where; + expect(where.status).toEqual({ not: FindingStatus.RESOLVED }); + }); + + it('rejects an unknown status filter', async () => { + signedInAs(OrgRole.OWNER); + const res = await listFindings(req('?status=NONSENSE'), { params: { id: ORG } }); + expect(res.status).toBe(400); + }); + + it('404s when the path organization differs from the resolved membership', async () => { + signedInAs(OrgRole.OWNER, ORG); + const res = await listFindings(req(), { params: { id: 'orgB' } }); + expect(res.status).toBe(404); + }); + + it('surfaces remediation and an explorer link', async () => { + signedInAs(OrgRole.OWNER); + prismaMock.reconciliationFinding.findMany.mockResolvedValue([{ + id: 'f1', kind: FindingKind.DB_PAID_CHAIN_NOT, status: FindingStatus.OPEN, + severity: FindingSeverity.CRITICAL, detail: 'mismatch', + remediation: 'Do not rely on the payment record.', + dbState: 'PAID', chainState: 'no settlement', txHash: 'HASH', + escrowOnChainId: 3, paymentIndex: 0, + detectedAt: new Date(), lastObservedAt: new Date(), observationCount: 2, + acknowledgedBy: null, acknowledgedAt: null, + resolvedBy: null, resolvedAt: null, resolution: null, + payment: { + id: 'pay1', recipientAddress: 'GW', amountBaseUnits: 10_000_000_000n, + assetDecimals: 7, assetCode: 'USDC', state: 'PAID', + batch: { id: 'b1', reference: 'CF-00001' }, + }, + run: { correlationId: 'rec_1', startedAt: new Date() }, + }]); + + const body = await (await listFindings(req(), { params: { id: ORG } })).json(); + const f = body.findings[0]; + expect(f.remediation).toMatch(/Do not rely/); + expect(f.transaction.explorerUrl).toBe('https://explorer/tx/HASH'); + expect(f.payment.amount).toBe('1,000.00'); + expect(f.observationCount).toBe(2); + }); +}); diff --git a/src/app/api/reconciliation/run/route.ts b/src/app/api/reconciliation/run/route.ts new file mode 100644 index 0000000..653efee --- /dev/null +++ b/src/app/api/reconciliation/run/route.ts @@ -0,0 +1,87 @@ +/** + * POST /api/reconciliation/run β€” the scheduled reconciliation trigger. + * + * ── Why this is not a user endpoint ────────────────────────────────────────── + * This runs across organizations, so it cannot be authorized by organization + * membership. It is protected by a shared secret the way the indexer trigger is, + * and is intended for a platform scheduler (Vercel Cron) β€” not for people. + * Operators trigger a single-organization run through + * `POST /api/organizations/:id/reconciliation`, which IS membership-scoped. + * + * Each organization is reconciled in its own run, with its own lock. One tenant's + * RPC trouble must not stop another's reconciliation from happening. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { timingSafeEqual, createHash } from 'crypto'; +import prisma from '@/lib/db/prisma'; +import { STELLAR_CONFIG } from '@/lib/config'; +import { runReconciliation } from '@/lib/reconciliation/scheduler'; +import { rateLimit, clientIp } from '@/lib/ratelimit'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; +/** Reconciliation is RPC-bound; give it room without running unbounded. */ +export const maxDuration = 300; + +const MIN_SECRET_LENGTH = 16; + +function secretsMatch(provided: string, expected: string): boolean { + const a = createHash('sha256').update(provided).digest(); + const b = createHash('sha256').update(expected).digest(); + return timingSafeEqual(a, b); +} + +export async function POST(request: NextRequest) { + // Vercel Cron sends `Authorization: Bearer $CRON_SECRET`. + const expected = process.env.CRON_SECRET || process.env.INDEXER_SECRET || ''; + if (!expected || expected.trim().length < MIN_SECRET_LENGTH) { + // A short or absent secret is not protection. Refuse to expose the endpoint + // rather than running on a guessable credential. + return NextResponse.json({ error: 'Not available' }, { status: 404 }); + } + + const rl = rateLimit(`reconcile-trigger:${clientIp(request)}`, 10, 60_000); + if (!rl.ok) return NextResponse.json({ error: 'Not available' }, { status: 404 }); + + const header = request.headers.get('authorization') ?? ''; + const bearer = header.startsWith('Bearer ') ? header.slice(7) : ''; + const provided = bearer || request.headers.get('x-cron-secret') || ''; + if (!provided || !secretsMatch(provided, expected)) { + return NextResponse.json({ error: 'Not available' }, { status: 404 }); + } + + const url = new URL(request.url); + const onlyOrg = url.searchParams.get('orgId'); + const maxEscrows = Math.min( + Math.max(parseInt(url.searchParams.get('maxEscrows') || '50', 10) || 50, 1), + 500 + ); + + const orgs = await prisma.organization.findMany({ + where: onlyOrg ? { id: onlyOrg } : {}, + select: { id: true, slug: true }, + // Bounded: a platform-wide sweep must not grow without limit as tenants are + // added. The scheduler runs again shortly; unfinished tenants are picked up + // by the next tick rather than making one invocation unbounded. + take: 50, + }); + + const results: unknown[] = []; + for (const org of orgs) { + try { + const r = await runReconciliation(prisma, org.id, { + contractId: STELLAR_CONFIG.contract.id || undefined, + network: STELLAR_CONFIG.contract.network, + maxEscrows, + }); + results.push({ orgId: org.id, slug: org.slug, ...r }); + } catch (e: any) { + // One tenant's failure must not abort the sweep. + console.error(`[reconcile] org ${org.id} failed: ${e?.message}`); + results.push({ orgId: org.id, slug: org.slug, error: e?.message ?? 'failed' }); + } + } + + return NextResponse.json({ organizations: orgs.length, results }); +} diff --git a/src/app/api/submit-batch/route.ts b/src/app/api/submit-batch/route.ts index 132197a..2f158b0 100644 --- a/src/app/api/submit-batch/route.ts +++ b/src/app/api/submit-batch/route.ts @@ -10,11 +10,27 @@ * * The client submits each signature via `submit_hours_proof`, then the two * signers approve, then anyone calls `pay_batch`. + * + * AUTHORIZATION β€” why this endpoint is gated twice: + * The oracle's signature is the only thing standing between "a payment row + * exists" and "the contract will move funds for it" (`pay_batch` refuses any + * payment whose `proof_verified` is false). An unauthenticated signer would + * therefore let anyone on the internet manufacture the proof-of-work half of + * the security model, which is exactly the property the product claims to + * enforce. So we require (1) a valid CoreFlow session, and (2) that the caller + * is the escrow's on-chain manager β€” read live from the contract, not from a + * client-supplied field. A worker cannot attest to their own hours. */ -import { NextResponse } from 'next/server'; -import { getOraclePublicKeyHex, signHoursProof } from '@/lib/oracle'; +import { NextRequest, NextResponse } from 'next/server'; +import { getOraclePublicKeyHex, signHoursProof, type ProofContext } from '@/lib/oracle'; +import { STELLAR_CONFIG } from '@/lib/config'; import { CoreFlowClient } from '@/lib/contracts'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { resolveTenant, findEscrowByOnChainId, requirePermission } from '@/lib/tenancy/resolve'; +import { rateLimit } from '@/lib/ratelimit'; +import { audit } from '@/lib/audit'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -27,7 +43,25 @@ interface PayeeInput { const G_ADDRESS = /^G[A-Z2-7]{55}$/; -export async function POST(request: Request) { +export async function POST(request: NextRequest) { + // ---- Gate 1: authenticated session ------------------------------------- + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json( + { error: 'Sign in with your Stellar wallet to request oracle attestations.' }, + { status: 401 } + ); + } + + // Signing is CPU-bound and security-sensitive; brake it per wallet. + const rl = rateLimit(`submit-batch:${user.walletAddress}`, 10, 60_000); + if (!rl.ok) { + return NextResponse.json( + { error: 'Too many attestation requests. Try again shortly.' }, + { status: 429, headers: { 'Retry-After': String(rl.retryAfter) } } + ); + } + let body: { escrow_id?: number; payees?: PayeeInput[] }; try { @@ -65,12 +99,67 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Oracle is not configured' }, { status: 503 }); } + // ---- Gate 1b: the escrow must belong to an organization the caller is in -- + // The on-chain manager check below proves control of a key, but not that this + // escrow is part of the caller's workspace. Both are required: a wallet can be + // the manager of an escrow recorded under a different tenant, and attestations + // must not cross that line. + const orgId = + request.headers.get('x-organization-id') ?? + new URL(request.url).searchParams.get('orgId') ?? + (await prisma.orgMember + .findMany({ where: { userId: user.userId, status: 'ACTIVE' }, select: { orgId: true }, take: 2 }) + .then((m) => (m.length === 1 ? m[0].orgId : null))); + + const tenant = await resolveTenant(prisma, user.userId, orgId); + if (!tenant.ok) { + return NextResponse.json({ error: tenant.message }, { status: tenant.status }); + } + const permissionDenied = requirePermission(tenant.value, 'oracle:attest:request'); + if (permissionDenied) { + return NextResponse.json( + { error: permissionDenied.message, code: permissionDenied.code }, + { status: 403 } + ); + } + const ownedEscrow = await findEscrowByOnChainId(prisma, tenant.value, escrowId); + if (!ownedEscrow.ok) { + return NextResponse.json({ error: ownedEscrow.message }, { status: ownedEscrow.status }); + } + + const client = new CoreFlowClient(); + + // ---- Gate 2: caller must be the escrow's on-chain manager --------------- + // Read from the contract so the check cannot be spoofed by request content. + let escrow: Awaited>; + try { + escrow = await client.getEscrow(escrowId); + } catch { + return NextResponse.json( + { error: `Could not read escrow ${escrowId}. Does it exist on this network?` }, + { status: 502 } + ); + } + const onChainManager = escrow.manager; + + if (user.walletAddress !== onChainManager) { + await audit('oracle.attest.denied', { + actor: user.walletAddress, + target: String(escrowId), + metadata: { reason: 'caller is not the on-chain manager' }, + }); + return NextResponse.json( + { error: 'Only the escrow manager can request attestations for this batch.' }, + { status: 403 } + ); + } + // The contract accepts only the next expected nonce, so signatures must start // from the live on-chain watermark rather than zero β€” a batch signed from 0 // against an escrow that already has proofs would be rejected with #9. let startNonce = 0; try { - startNonce = await new CoreFlowClient().getNonce(escrowId); + startNonce = await client.getNonce(escrowId); } catch { return NextResponse.json( { error: `Could not read nonce for escrow ${escrowId}. Does it exist on this network?` }, @@ -78,20 +167,67 @@ export async function POST(request: Request) { ); } - const signatures = payees.map((payee, i) => { + // The uploaded CSV must describe the escrow that was actually funded. The + // signed preimage is built from ON-CHAIN payment rows, so a mismatched CSV + // would otherwise produce signatures that silently attest to something the + // uploader never reviewed. Reject rather than sign the discrepancy. + if (payees.length !== escrow.payments.length) { + return NextResponse.json( + { + error: + `This file has ${payees.length} payee(s) but escrow ${escrowId} was funded ` + + `for ${escrow.payments.length}. Upload the file this escrow was created from.`, + }, + { status: 409 } + ); + } + + for (const [i, payee] of payees.entries()) { + const row = escrow.payments[i]; + if (row.worker !== payee.address) { + return NextResponse.json( + { + error: + `Row ${i + 1} pays ${payee.address}, but payment ${i} of escrow ${escrowId} ` + + `is held for ${row.worker}.`, + }, + { status: 409 } + ); + } + } + + const ctxFor = (payment: (typeof escrow.payments)[number]): ProofContext => ({ + networkPassphrase: STELLAR_CONFIG.getNetworkPassphrase(), + contractId: STELLAR_CONFIG.contract.id, + worker: payment.worker, + token: payment.token, + amount: payment.amount, + startDate: BigInt(payment.start_date), + endDate: BigInt(payment.end_date), + }); + + const signatures = escrow.payments.map((payment, i) => { const nonce = startNonce + i; - // Hours are the attested unit; amount is fixed at escrow creation. - const hours = Math.max(1, Math.round(Number(payee.amount))); + // Hours are derived from what was actually escrowed, not from the upload: + // the contract enforces `hours x rate == amount`, so any other value would + // be rejected on chain. Deriving it here makes that impossible to get wrong. + const hours = payment.amount / payment.rate_per_hour; return { paymentId: i, - address: payee.address, - token: payee.token, - hours, + address: payment.worker, + token: payment.token, + hours: Number(hours), nonce, - signature: signHoursProof(escrowId, i, hours, nonce), + signature: signHoursProof(ctxFor(payment), escrowId, i, hours, nonce), }; }); + await audit('oracle.attest.issued', { + actor: user.walletAddress, + target: String(escrowId), + metadata: { payees: payees.length, startNonce }, + }); + return NextResponse.json( { escrowId, oraclePublicKey, startNonce, signatures }, { status: 200 } diff --git a/src/app/bulk-pay/page.tsx b/src/app/bulk-pay/page.tsx index 1b7b919..216468a 100644 --- a/src/app/bulk-pay/page.tsx +++ b/src/app/bulk-pay/page.tsx @@ -38,7 +38,15 @@ interface ChainState { allProofsVerified: boolean; } -const ESCROW_ID = 1; +/** + * Which escrow this console operates on. + * + * Was hard-coded to 1. That escrow is now settled, so the page pointed at spent + * state and every action failed with PaymentAlreadyFinalized. Escrow selection + * belongs in the Bulk Pay workflow proper (staged upload -> preview -> batch -> + * approvals); until that lands, this is configurable rather than pinned. + */ +const ESCROW_ID = Number(process.env.NEXT_PUBLIC_BULK_PAY_ESCROW_ID ?? '1'); const EXPERT = 'https://stellar.expert/explorer/testnet/tx'; const G_ADDRESS = /^G[A-Z2-7]{55}$/; @@ -109,12 +117,60 @@ export default function BulkPayPage() { useEffect(() => { void refresh(); }, [refresh]); + // Restore an existing CoreFlow session on mount, so a signed-in operator + // does not have to re-sign the auth challenge on every page load. + useEffect(() => { + void (async () => { + try { + const res = await fetch('/api/auth/me'); + if (res.ok) setWallet((await res.json()).user.walletAddress); + } catch { /* unauthenticated β€” the connect button handles it */ } + })(); + }, []); + + /** + * Connect Freighter AND establish a CoreFlow session. + * + * Connecting the wallet alone is not enough: /api/submit-batch issues the + * oracle attestations that unlock settlement, so it requires a verified + * session (challenge -> Freighter signature -> server-side Ed25519 verify) + * and checks the caller against the escrow's on-chain manager. Proving + * control of the key is what authorizes attestation, not merely naming it. + */ const connect = async () => { setError(null); + setBusy('connect'); try { - setWallet(await STELLAR_CONFIG.freighter.connect()); - } catch { - setError('Could not connect Freighter. Is the extension unlocked and set to Testnet?'); + const address = await STELLAR_CONFIG.freighter.connect(); + + const challengeRes = await fetch('/api/auth/challenge', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ walletAddress: address }), + }); + if (!challengeRes.ok) throw new Error('Could not start sign-in.'); + const { challenge } = await challengeRes.json(); + + const signature = await STELLAR_CONFIG.freighter.signMessage(challenge); + + const verifyRes = await fetch('/api/auth/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ walletAddress: address, signature }), + }); + if (!verifyRes.ok) { + throw new Error((await verifyRes.json()).error || 'Signature verification failed.'); + } + + setWallet(address); + } catch (err) { + setError( + err instanceof Error && err.message !== 'Freighter wallet not found' + ? err.message + : 'Could not connect Freighter. Is the extension unlocked and set to Testnet?' + ); + } finally { + setBusy(null); } }; diff --git a/src/app/dashboard/admin/escrows/page.tsx b/src/app/dashboard/admin/escrows/page.tsx index da261ac..ae40321 100644 --- a/src/app/dashboard/admin/escrows/page.tsx +++ b/src/app/dashboard/admin/escrows/page.tsx @@ -196,6 +196,9 @@ export default function AdminEscrowsPage() { onClose={() => actions.setShowCreateModal(false)} onSubmit={actions.handleCreateEscrow} isMockMode={isMockMode} + // Lets the modal refuse a finance approver equal to the manager before + // the transaction is built, rather than after the contract traps. + managerAddress={auth.walletAddress} /> diff --git a/src/app/dashboard/employee/payments/page.tsx b/src/app/dashboard/employee/payments/page.tsx index 2d6f779..9c3d001 100644 --- a/src/app/dashboard/employee/payments/page.tsx +++ b/src/app/dashboard/employee/payments/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useAuth } from '@/hooks/useAuth'; +import { txUrl } from '@/lib/explorer'; import { useDashboard } from '@/hooks/useDashboard'; import { DollarSign, @@ -244,7 +245,7 @@ export default function EmployeePaymentsPage() { TX: {escrow.transaction_hash.slice(0, 12)}... diff --git a/src/app/dashboard/payroll/[id]/page.tsx b/src/app/dashboard/payroll/[id]/page.tsx new file mode 100644 index 0000000..5e39c03 --- /dev/null +++ b/src/app/dashboard/payroll/[id]/page.tsx @@ -0,0 +1,75 @@ +'use client'; + +/** + * Batch detail page. + * + * A thin shell: fetch, then render. Authorization is the server's β€” the request + * carries the session, `withTenant` resolves membership, and a batch in another + * organization returns the same 404 as one that does not exist. No organization id + * is read from the URL or from storage and treated as permission. + */ + +import { useCallback, useEffect, useState } from 'react'; +import { BatchDetail, type BatchDetailData } from '@/components/payroll/BatchDetail'; + +export default function BatchDetailPage({ params }: { params: { id: string } }) { + const [data, setData] = useState(null); + const [error, setError] = useState<{ status: number; message: string } | null>(null); + + const load = useCallback(async () => { + setError(null); + try { + const response = await fetch(`/api/payroll/batches/${encodeURIComponent(params.id)}`); + const body = await response.json().catch(() => null); + if (!response.ok) { + setError({ + status: response.status, + message: body?.error ?? 'This payroll batch could not be loaded.', + }); + return; + } + setData(body as BatchDetailData); + } catch { + setError({ status: 0, message: 'The payroll batch could not be loaded.' }); + } + }, [params.id]); + + useEffect(() => { + void load(); + }, [load]); + + if (error) { + return ( +
+

+ {/* A cross-tenant batch and a non-existent one are the same answer. */} + {error.status === 404 ? 'Payroll batch not found' : 'Could not load this payroll'} +

+

{error.message}

+ +
+ ); + } + + if (!data) { + return ( +
+

+ Loading payroll batch… +

+
+ ); + } + + return ( +
+ void load()} /> +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 5f44d89..85cd2d7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,6 @@ import Link from 'next/link'; +import { STELLAR_CONFIG } from '@/lib/config'; +import { contractUrl, explorerNetworkLabel, V1_MAINNET } from '@/lib/explorer'; import { ShieldCheck, Coins, FileCheck2, Crown, Users, ArrowRight, Lock } from 'lucide-react'; const FEATURES = [ @@ -109,16 +111,50 @@ export default function LandingPage() { - {/* Contract Link */} -
- - View Smart Contract on Stellar Expert - + {/* + Two deployments, stated as two deployments. + + A single link labelled "View Smart Contract" implied that whatever the + product does today is what is running on Mainnet. It is not: v1 is the + historical Mainnet deployment, and v2 β€” domain-separated attestations, + an admin-managed oracle registry, and the work/amount invariant β€” is + deployed on Testnet only. Presenting one link would claim v2's + security properties for Mainnet, which is not true. + */} +
+

+ Verify on-chain +

+ +

+ v2’s security improvements are deployed on Testnet and have not been + migrated to Mainnet. The v1 Mainnet contract does not carry them. +

{/* Features */} diff --git a/src/components/EscrowCard.tsx b/src/components/EscrowCard.tsx index 909390f..bef75fe 100644 --- a/src/components/EscrowCard.tsx +++ b/src/components/EscrowCard.tsx @@ -16,8 +16,25 @@ import { Fingerprint, } from 'lucide-react'; import { EscrowTimeline } from './EscrowTimeline'; +import { BatchPaymentsTable } from './payments/BatchPaymentsTable'; import { FeeSavings } from './FeeSavings'; import { PaymentReceipt } from './PaymentReceipt'; +import { txUrl } from '@/lib/explorer'; + +/** One payment within an escrow, as returned by GET /api/escrows. */ +export interface EscrowPaymentData { + id: string; + index?: number | null; + recipient: string; + /** Pre-formatted at the asset's own precision. */ + amount: string; + amountBaseUnits?: string; + hours?: string; + state: string; + stateLabel?: string; + txHash?: string | null; + settledAt?: string | null; +} export interface EscrowData { id: number; @@ -33,6 +50,16 @@ export interface EscrowData { created_at: string; transaction_hash?: string; isMock?: boolean; + /** + * The escrow's payments, one per payee. + * + * Optional because the mock/demo path still produces single-payee escrows. When + * present with more than one row, the card renders the per-payment breakdown + * instead of a single aggregate line β€” an escrow-shaped summary of a multi-payee + * batch is the lossiness this model removed. + */ + payments?: EscrowPaymentData[]; + paymentCount?: number; } interface EscrowCardProps { @@ -216,6 +243,32 @@ export const EscrowCard = ({
)} + {/* + Per-payment breakdown. + + Shown whenever the escrow holds more than one payment. Rendering a + multi-payee batch as a single aggregate line is exactly what hid eleven + of twelve contractors in the previous model, so the individual payments + take precedence over the summary above. + */} + {escrow.payments && escrow.payments.length > 1 && ( +
+ ({ + id: p.id, + index: p.index, + recipient: p.recipient, + amount: p.amount, + hours: p.hours, + state: p.state, + txHash: p.txHash, + }))} + total={escrow.amount} + assetCode={escrow.currency} + /> +
+ )} + {/* Workflow Timeline */} + + Chain not configured + + ); + } + + return ( + + + {STELLAR_CONFIG.networkLabel()} + {mainnet && β€” transactions move real funds} + + ); +} diff --git a/src/components/TransactionFeed.tsx b/src/components/TransactionFeed.tsx index b6c7cf5..0f2edc5 100644 --- a/src/components/TransactionFeed.tsx +++ b/src/components/TransactionFeed.tsx @@ -2,6 +2,7 @@ import { ArrowUpRight, Check, Clock, FileText, X } from 'lucide-react'; import { STELLAR_CONFIG } from '@/lib/config'; +import { txUrl } from '@/lib/explorer'; export interface Transaction { id: string; @@ -31,8 +32,8 @@ export const TransactionFeed = ({ transactions }: TransactionFeedProps) => { const getExplorerUrl = (hash: string) => { const network = STELLAR_CONFIG.contract.network; return network === 'public' - ? `https://stellar.expert/explorer/public/tx/${hash}` - : `https://stellar.expert/explorer/testnet/tx/${hash}`; + ? txUrl(hash) + : txUrl(hash); }; return ( diff --git a/src/components/dashboard/DashboardHeader.tsx b/src/components/dashboard/DashboardHeader.tsx index 66a43b9..195f14a 100644 --- a/src/components/dashboard/DashboardHeader.tsx +++ b/src/components/dashboard/DashboardHeader.tsx @@ -1,7 +1,7 @@ import { RefreshCw, LogOut, LogIn, ShieldCheck, Crown, Users, Mail, ScrollText } from 'lucide-react'; import type { UserRole } from '@/hooks/useAuth'; -import { STELLAR_CONFIG } from '@/lib/config'; import Link from 'next/link'; +import { NetworkBadge } from '@/components/NetworkBadge'; interface DashboardHeaderProps { isContractConfigured: boolean; @@ -44,7 +44,6 @@ export function DashboardHeader({ onSignOut, }: DashboardHeaderProps) { const companyName = process.env.NEXT_PUBLIC_COMPANY_NAME || 'CoreFlow'; - const networkName = (STELLAR_CONFIG.contract.network || 'public').toUpperCase(); const truncatedAddress = walletAddress ? `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}` @@ -56,6 +55,7 @@ export function DashboardHeader({
+ {/* Which chain these numbers refer to is never implicit. */}
Soroban Escrow - {/* Network Indicator */} - - Network: {networkName} - + {/* Network indicator. Previously rendered emerald regardless of + network, so Mainnet and Testnet were visually identical and an + unconfigured contract showed as healthy. */} +

On-Chain Accounts Payable & Remittance

diff --git a/src/components/funding/FundingPanel.tsx b/src/components/funding/FundingPanel.tsx new file mode 100644 index 0000000..ef3fe96 --- /dev/null +++ b/src/components/funding/FundingPanel.tsx @@ -0,0 +1,821 @@ +'use client'; + +/** + * Funding a payroll batch: review, disclose, sign once, verify. + * + * Three rules this component is built around. + * + * 1. EVERY FIGURE COMES FROM THE SERVER. The plan is issued and frozen server-side + * and rendered verbatim. Nothing here adds, converts or formats money β€” a total + * computed in a browser is a second opinion about what is being signed. + * + * 2. ONE TRANSACTION. `initialize_multi_sig_escrow` creates the escrow and moves + * custody atomically, so the UI must not imply two steps. There is no + * "create escrow" button anywhere. + * + * 3. AN UNCERTAIN TRANSACTION MUST NEVER INVITE A SECOND ONE. The contract is not + * idempotent: signing again funds a second escrow with the same money. So while + * an attempt is unresolved there is no funding button at all β€” only "Check + * status" β€” and on mount the existing intent is recovered rather than replaced. + */ + +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Button } from '@/components/Button'; +import { NetworkBadge } from '@/components/NetworkBadge'; +import { + abandonFunding, + confirmFunding, + getFundingState, + openFundingIntent, + planReference, + planToContractArguments, + recordSubmitted, + FundingRequestError, + type ConfirmResult, + type FundingAttemptView, + type FundingPlan, + type FundingStateView, +} from '@/lib/funding/client'; + +/** + * What the screen is doing. Derived from server state plus the in-flight wallet + * interaction β€” never a status the client invented about money. + */ +type Phase = + | 'LOADING' + | 'BLOCKED' + | 'READY' + | 'DISCLOSING' + | 'AWAITING_SIGNATURE' + | 'SUBMITTING' + | 'CONFIRMING' + | 'VERIFYING' + | 'FUNDED' + | 'USER_REJECTED' + | 'RPC_FAILED' + | 'CONTRACT_REJECTED' + | 'MISMATCH'; + +/** Phases in which a funding action must not be offered. */ +const NO_FUNDING_BUTTON: readonly Phase[] = [ + 'AWAITING_SIGNATURE', + 'SUBMITTING', + 'CONFIRMING', + 'VERIFYING', + 'FUNDED', + 'MISMATCH', +]; + +function truncate(address: string): string { + return address.length > 14 ? `${address.slice(0, 6)}…${address.slice(-6)}` : address; +} + +const EXPLORER = 'https://stellar.expert/explorer/testnet'; + +export interface FundingPanelProps { + batchId: string; + orgId?: string; + /** Called after funding is confirmed, so the host page can refresh. */ + onFunded?: () => void; +} + +export function FundingPanel({ batchId, orgId, onFunded }: FundingPanelProps) { + const [phase, setPhase] = useState('LOADING'); + const [state, setState] = useState(null); + const [plan, setPlan] = useState(null); + // The attempt itself, not just its id: the disclosure needs its planDigest, and + // reading it back off the pre-intent `state` meant the plan reference never + // appeared β€” the state loaded before the intent existed. + const [attempt, setAttempt] = useState(null); + const attemptId = attempt?.id ?? null; + const [message, setMessage] = useState(null); + const [differences, setDifferences] = useState([]); + const [txHash, setTxHash] = useState(null); + const [escrowId, setEscrowId] = useState(null); + const [showTechnical, setShowTechnical] = useState(false); + const [busy, setBusy] = useState(false); + const mounted = useRef(true); + + useEffect(() => { + mounted.current = true; + return () => { + mounted.current = false; + }; + }, []); + + /** + * Load server state and adopt whatever attempt already exists. + * + * This is the reload-recovery path. A refresh mid-submission must land on the + * SAME attempt β€” never a fresh one. + */ + const load = useCallback(async () => { + try { + const next = await getFundingState(batchId, orgId); + if (!mounted.current) return; + setState(next); + setPlan(next.plan); + setAttempt(next.attempt); + setTxHash(next.attempt?.hash ?? null); + setEscrowId(next.escrow?.onChainId ?? null); + + const status = next.attempt?.status; + if (status === 'CONFIRMED') { + setPhase('FUNDED'); + } else if (status === 'SUBMITTED') { + // A transaction exists and its outcome is unknown to us. + setPhase('VERIFYING'); + setMessage(next.attempt?.errorMessage ?? null); + } else if (status === 'AWAITING_SIGNATURE' && next.plan) { + setPhase('DISCLOSING'); + } else if (!next.assessment.eligible) { + setPhase('BLOCKED'); + } else { + setPhase('READY'); + } + } catch (e) { + if (!mounted.current) return; + setPhase('BLOCKED'); + setMessage(e instanceof Error ? e.message : 'Funding state could not be loaded.'); + } + }, [batchId, orgId]); + + useEffect(() => { + void load(); + }, [load]); + + + + /** Open (or recover) the intent, freezing the plan, then disclose it. */ + async function beginFunding() { + setBusy(true); + setMessage(null); + try { + const result = await openFundingIntent(batchId, orgId); + setAttempt(result.attempt); + setPlan(result.plan); + setTxHash(result.attempt.hash ?? null); + setPhase(result.attempt.status === 'SUBMITTED' ? 'VERIFYING' : 'DISCLOSING'); + } catch (e) { + if (e instanceof FundingRequestError && e.status === 409) { + // Either the batch became unfundable or an attempt is already open; reload + // rather than guessing, so the screen reflects the server. + setMessage(e.message); + await load(); + } else { + setMessage(e instanceof Error ? e.message : 'The funding intent could not be opened.'); + } + } finally { + setBusy(false); + } + } + + /** Step back from the disclosure, releasing the intent so the batch is not stuck. */ + async function cancelDisclosure() { + if (!attemptId) { + setPhase('READY'); + return; + } + setBusy(true); + try { + await abandonFunding(batchId, { + attemptId, + reason: 'Returned from the funding review without signing.', + userRejected: true, + orgId, + }); + } catch { + // Even if releasing fails, reloading shows the true state. + } finally { + setBusy(false); + await load(); + } + } + + /** + * Sign and submit. One transaction. + * + * The hash is persisted by `onSubmitted`, the instant the network accepts it and + * BEFORE confirmation is polled β€” otherwise a failure while waiting would leave a + * transaction that may have moved money with no record of its hash, and the + * obvious recovery would be to sign another one. + */ + async function signAndSubmit() { + if (!plan || !attemptId) return; + setBusy(true); + setMessage(null); + setPhase('AWAITING_SIGNATURE'); + + try { + const { CoreFlowClient } = await import('@/lib/contracts'); + const client = new CoreFlowClient(); + const args = planToContractArguments(plan); + + const result = await client.submitInitializeEscrow( + args.manager, + args.financeApprover, + args.oraclePublicKeyHex, + args.payments, + async (hash) => { + if (mounted.current) { + setTxHash(hash); + setPhase('SUBMITTING'); + } + await recordSubmitted(batchId, { attemptId, transactionHash: hash, orgId }); + if (mounted.current) setPhase('CONFIRMING'); + }, + ); + + const returned = Number(result.returnValue); + if (!Number.isInteger(returned) || returned <= 0) { + // The return value was unreadable. The hash is recorded, and the server can + // resolve the escrow from it, so verification still proceeds β€” signing again + // is never the recovery. + setPhase('CONFIRMING'); + await verify(attemptId); + return; + } + + setEscrowId(returned); + await verify(attemptId, returned); + } catch (e) { + const text = e instanceof Error ? e.message : String(e); + // A declined signature is the one failure where nothing reached the network, + // so it is the only one that is plainly safe to retry. + const declined = /reject|denied|cancel|user declined/i.test(text); + + if (declined && !txHash) { + setPhase('USER_REJECTED'); + setMessage('Signing was cancelled. Nothing was submitted and no funds moved.'); + if (attemptId) { + await abandonFunding(batchId, { + attemptId, + reason: 'Signature declined in the wallet.', + userRejected: true, + orgId, + }).catch(() => {}); + } + await load(); + } else if (txHash) { + // Submitted, then something went wrong while waiting. The outcome is unknown. + setPhase('VERIFYING'); + setMessage( + 'The transaction was submitted, but we could not confirm it. ' + + 'Its outcome is unknown.', + ); + } else if (/simulat|contract|HostError|trap/i.test(text)) { + setPhase('CONTRACT_REJECTED'); + setMessage(text.slice(0, 300)); + } else { + setPhase('RPC_FAILED'); + setMessage(text.slice(0, 300)); + } + } finally { + setBusy(false); + } + } + + /** Ask the server to verify against the frozen plan, and reflect the verdict. */ + const verify = useCallback( + async (attempt: string, onChainEscrowId?: number) => { + setBusy(true); + try { + const result: ConfirmResult = await confirmFunding(batchId, { + attemptId: attempt, + // May be undefined: the server resolves the escrow from the transaction + // hash, so a return value we could not parse does not block recovery. + onChainEscrowId, + orgId, + }); + setDifferences(result.differences ?? []); + + switch (result.outcome) { + case 'CONFIRMED': + setPhase('FUNDED'); + setMessage(null); + onFunded?.(); + break; + case 'FAILED': + setPhase('CONTRACT_REJECTED'); + setMessage(result.reason ?? 'The transaction failed on-chain. No funds moved.'); + break; + case 'UNVERIFIABLE': + // NOT a failure. Saying "failed" here would be a claim about money we + // have not established. + setPhase('VERIFYING'); + setMessage(result.reason ?? 'The transaction could not be verified yet.'); + break; + case 'MISMATCH': + setPhase('MISMATCH'); + setMessage( + 'The transaction does not match this payroll’s funding plan, so the escrow ' + + 'was not attached to this batch.', + ); + break; + } + await load(); + } catch (e) { + setPhase('VERIFYING'); + setMessage(e instanceof Error ? e.message : 'Verification could not be completed.'); + } finally { + setBusy(false); + } + }, + [batchId, orgId, onFunded, load], + ); + + /** + * Resolve a known-but-unverified transaction automatically, once. + * + * A submitted attempt whose outcome we do not know is the state a user is most + * likely to land in after a reload or a dropped connection, and the one where the + * wrong instinct β€” sign again β€” costs real money. Since the server can resolve the + * escrow from the hash alone, attempting it on arrival means the common case + * resolves itself with no action and no second signature. + * + * Guarded by a ref so a re-render cannot re-enter it, and deliberately not retried + * on a loop: repeated verification of a genuinely pending transaction is noise, and + * `Check status` remains available. + */ + const autoRecovered = useRef(false); + useEffect(() => { + if (autoRecovered.current) return; + if (phase !== 'VERIFYING') return; + if (!attemptId || !txHash) return; + autoRecovered.current = true; + void verify(attemptId, escrowId ?? undefined); + }, [phase, attemptId, txHash, escrowId, verify]); + + async function checkStatus() { + // Verification only needs the attempt: the escrow is resolved from the + // transaction hash server-side. Passing the id when we have it lets the server + // cross-check the two agree. + if (attemptId) { + await verify(attemptId, escrowId ?? undefined); + } else { + await load(); + } + } + + const reference = planReference(attempt?.planDigest); + + return ( +
+
+
+

+ Fund payroll +

+

+ Creating the escrow and funding it happen in the same Stellar transaction. +

+
+ +
+ + {/* Every state change is announced, so a screen reader follows the transaction. */} +

+ {phaseAnnouncement(phase)} +

+ + {phase === 'LOADING' &&

Checking funding status…

} + + {phase === 'BLOCKED' && state && ( + + )} + + {(phase === 'READY' || phase === 'USER_REJECTED' || phase === 'RPC_FAILED' || + phase === 'CONTRACT_REJECTED') && state && ( + + )} + + {phase === 'DISCLOSING' && plan && ( + setShowTechnical((v) => !v)} + /> + )} + + {(phase === 'AWAITING_SIGNATURE' || + phase === 'SUBMITTING' || + phase === 'CONFIRMING' || + phase === 'VERIFYING') && ( + + )} + + {phase === 'FUNDED' && state && ( + + )} + + {phase === 'MISMATCH' && ( + + )} + + {message && ['USER_REJECTED', 'RPC_FAILED', 'CONTRACT_REJECTED'].includes(phase) && ( + + )} + +
+ {!NO_FUNDING_BUTTON.includes(phase) && phase !== 'LOADING' && phase !== 'DISCLOSING' && ( + + )} + + {phase === 'DISCLOSING' && ( + <> + + + + )} + + {/* + No funding button while an outcome is unknown. The contract is not + idempotent, so offering one here is offering to move the money twice. + */} + {(phase === 'VERIFYING' || phase === 'CONFIRMING') && ( + + )} +
+
+ ); +} + +function phaseAnnouncement(phase: Phase): string { + switch (phase) { + case 'AWAITING_SIGNATURE': + return 'Waiting for your wallet to sign the funding transaction.'; + case 'SUBMITTING': + return 'Submitting the funding transaction to Stellar.'; + case 'CONFIRMING': + return 'Confirming the funding transaction on Stellar.'; + case 'VERIFYING': + return 'Still verifying whether the funding transaction completed. Do not fund again.'; + case 'FUNDED': + return 'The payroll escrow is funded.'; + case 'MISMATCH': + return 'The transaction did not match the funding plan. The escrow was not attached.'; + case 'USER_REJECTED': + return 'Signing was cancelled. Nothing was submitted.'; + default: + return ''; + } +} + +function Row({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+
{label}
+
{children}
+
+ ); +} + +function Summary({ state }: { state: FundingStateView }) { + return ( +
+ {state.assessment.paymentCount} + + {state.assessment.total} {state.plan?.asset.code ?? ''} + + {state.escrow ? `#${state.escrow.onChainId}` : 'New escrow'} + One +
+ ); +} + +function Blockers({ + blockers, + message, +}: { + blockers: FundingStateView['assessment']['blockers']; + message: string | null; +}) { + return ( +
+

+ This payroll cannot be funded yet +

+ {message &&

{message}

} +
    + {blockers.map((b, i) => ( +
  • + {b.position !== undefined && ( + + Row {b.position} + + )} + {b.message} +
  • + ))} +
+
+ ); +} + +/** + * Everything the signer is entitled to know, before the wallet opens. + * + * Rendered from the frozen plan. A signature request that does not say what is + * being signed is the problem the attestation format solves at the protocol level; + * this is the same problem at the human level. + */ +function Disclosure({ + plan, + reference, + showTechnical, + onToggleTechnical, +}: { + plan: FundingPlan; + reference: string | null; + showTechnical: boolean; + onToggleTechnical: () => void; +}) { + return ( +
+
+

+ You are funding payroll batch {plan.batch.reference}{' '} + from your wallet into a CoreFlow escrow. +

+

+ The escrow is created and funded by one transaction. Funds are held by the + contract until verified work is approved by two separate people. +

+
+ +
+ + + {plan.total} {plan.asset.code} + + + {plan.batch.paymentCount} + {plan.network.label} + + {truncate(plan.custodyDestination)} + + + {truncate(plan.manager)} + + + {truncate(plan.financeApprover)} + + {reference && {reference}} +
+ +
+ + Payments ({plan.schedule.length}) + +
+ + + + + + + + + + + {plan.schedule.map((row) => ( + + + {/* The exact value that will be signed, unformatted. */} + + + + ))} + +
+ Every payment this funding transaction covers +
RecipientAmount (base units)Period
+ {truncate(row.worker)} + {row.amountBaseUnits} + {new Date(row.startDate * 1000).toISOString().slice(0, 10)} β†’{' '} + {new Date(row.endDate * 1000).toISOString().slice(0, 10)} +
+
+
+ +
+ + {showTechnical && ( +
+ + {plan.contractId} + + + {plan.asset.contractId} + + + {plan.oraclePublicKey.slice(0, 16)}… + + + {plan.totalBaseUnits} + +
+ )} +
+
+ ); +} + +function Progress({ + phase, + txHash, + message, +}: { + phase: Phase; + txHash: string | null; + message: string | null; +}) { + const steps: { key: Phase; label: string }[] = [ + { key: 'AWAITING_SIGNATURE', label: 'Waiting for your wallet' }, + { key: 'SUBMITTING', label: 'Submitting to Stellar' }, + { key: 'CONFIRMING', label: 'Confirming on Stellar' }, + ]; + const index = steps.findIndex((s) => s.key === phase); + const uncertain = phase === 'VERIFYING'; + + return ( +
+
    + {steps.map((step, i) => { + const done = index > i || phase === 'VERIFYING'; + const current = index === i; + return ( +
  1. + + {done ? 'βœ“' : i + 1} + + {step.label} +
  2. + ); + })} +
+ + {uncertain && ( +
+

+ We’re checking whether your funding transaction completed. +

+ {/* + The most important sentence in this component. Funding is not + idempotent on-chain: a second signature creates a second escrow and + moves the money again. + */} +

+ Do not fund this payroll again. +

+ {message &&

{message}

} +
+ )} + + {txHash && ( +

+ Transaction{' '} + + {truncate(txHash)} + +

+ )} +
+ ); +} + +function Funded({ state, txHash }: { state: FundingStateView; txHash: string | null }) { + return ( +
+
+

Escrow funded

+

+ Verified against the chain: the escrow exists, matches this payroll’s plan, and the + funds reached the contract. +

+
+
+ {state.escrow ? `#${state.escrow.onChainId}` : 'β€”'} + + {state.assessment.total} {state.plan?.asset.code ?? ''} + + {txHash && ( + + + {truncate(txHash)} + + + )} +
+

+ Next: work is verified by the oracle, then approved by a manager and a separate + finance approver before settlement. +

+
+ ); +} + +function Mismatch({ + message, + differences, + txHash, +}: { + message: string | null; + differences: string[]; + txHash: string | null; +}) { + return ( +
+

+ Transaction details did not match the funding plan +

+

{message}

+ {differences.length > 0 && ( +
    + {differences.map((d, i) => ( +
  • β€’ {d}
  • + ))} +
+ )} +

+ This has been recorded for investigation. Do not fund again until it is resolved. +

+ {txHash && ( +

+ + {truncate(txHash)} + +

+ )} +
+ ); +} + +function FailureNotice({ phase, message }: { phase: Phase; message: string }) { + const titles: Partial> = { + USER_REJECTED: 'Signing was cancelled', + RPC_FAILED: 'We couldn’t reach the network', + CONTRACT_REJECTED: 'The Stellar contract rejected the transaction', + }; + return ( +
+

{titles[phase]}

+

{message}

+ {phase === 'USER_REJECTED' && ( +

+ No funds moved. This payroll is editable again. +

+ )} +
+ ); +} diff --git a/src/components/funding/__tests__/FundingPanel.test.tsx b/src/components/funding/__tests__/FundingPanel.test.tsx new file mode 100644 index 0000000..b5339c6 --- /dev/null +++ b/src/components/funding/__tests__/FundingPanel.test.tsx @@ -0,0 +1,374 @@ +/** + * Funding panel behaviour. + * + * The properties under test are safety properties, not cosmetics: that an + * unresolved transaction never offers a second funding action, that a reload + * recovers the existing intent instead of opening another, and that UNVERIFIABLE is + * never rendered as failure. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { FundingPanel } from '../FundingPanel'; + +vi.mock('@/components/NetworkBadge', () => ({ + NetworkBadge: () => TESTNET, +})); + +const MANAGER = 'G' + 'M'.repeat(55); +const FINANCE = 'G' + 'F'.repeat(55); +const CONTRACT = 'C' + 'C'.repeat(55); +const TOKEN = 'C' + 'T'.repeat(55); +const HASH = 'a'.repeat(64); +const DIGEST = 'beef1234' + '0'.repeat(56); + +function plan() { + return { + batch: { id: 'bat_1', reference: 'CF-00042', paymentCount: 2 }, + total: '8,420.00', + totalBaseUnits: '84200000000', + asset: { code: 'USDC', contractId: TOKEN, decimals: 7 }, + network: { id: 'testnet', label: 'Stellar Testnet', isMainnet: false }, + contractId: CONTRACT, + custodyDestination: CONTRACT, + manager: MANAGER, + financeApprover: FINANCE, + oraclePublicKey: 'ab'.repeat(32), + schedule: [ + { + paymentId: 'pay_1', + worker: 'G' + 'A'.repeat(55), + token: TOKEN, + amountBaseUnits: '42100000000', + rateBaseUnits: '250000000', + startDate: 1788000000, + endDate: 1789000000, + }, + { + paymentId: 'pay_2', + worker: 'G' + 'B'.repeat(55), + token: TOKEN, + amountBaseUnits: '42100000000', + rateBaseUnits: '250000000', + startDate: 1788000000, + endDate: 1789000000, + }, + ], + }; +} + +function state(over: Record = {}) { + return { + batch: { id: 'bat_1', reference: 'CF-00042' }, + assessment: { + eligible: true, + blockers: [], + paymentCount: 2, + total: '8,420.00', + totalBaseUnits: '84200000000', + }, + plan: plan(), + attempt: null, + escrow: null, + ...over, + }; +} + +function attempt(over: Record = {}) { + return { + id: 'btx_1', + status: 'AWAITING_SIGNATURE', + planDigest: DIGEST, + attempt: 1, + hash: null, + errorMessage: null, + createdAt: new Date().toISOString(), + submittedAt: null, + confirmedAt: null, + ...over, + }; +} + +/** Route responses, keyed by the suffix of the URL. */ +let routes: Record unknown>; +let calls: string[]; + +function mockFetch() { + return vi.fn(async (url: string, init?: RequestInit) => { + const path = String(url); + calls.push(`${init?.method ?? 'GET'} ${path.replace(/^.*\/funding/, '')}`); + const key = Object.keys(routes).find((k) => path.endsWith(k) || path.includes(k)); + const body = key ? routes[key]() : null; + if (body && (body as any).__status) { + return new Response(JSON.stringify(body), { status: (body as any).__status }); + } + return new Response(JSON.stringify(body ?? {}), { status: 200 }); + }); +} + +beforeEach(() => { + calls = []; + routes = {}; + vi.stubGlobal('fetch', mockFetch()); +}); + +afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); +}); + +describe('a fundable batch', () => { + beforeEach(() => { + routes = { '/funding': () => state() }; + }); + + it('shows the total, recipient count and that there is ONE transaction', async () => { + render(); + await waitFor(() => expect(screen.getByText('Review and fund')).toBeDefined()); + + expect(screen.getByText(/same Stellar transaction/i)).toBeDefined(); + expect(screen.getByText('8,420.00 USDC')).toBeDefined(); + expect(screen.getByText('One')).toBeDefined(); + // Never two steps: there is no "create escrow" action anywhere. + expect(screen.queryByText(/create escrow/i)).toBeNull(); + }); + + it('shows the Testnet badge', async () => { + render(); + await waitFor(() => expect(screen.getByTestId('network-badge')).toBeDefined()); + }); +}); + +describe('the pre-signing disclosure', () => { + beforeEach(() => { + routes = { + '/funding/intent': () => ({ created: true, attempt: attempt(), plan: plan() }), + '/funding': () => state(), + }; + }); + + it('discloses what is being signed before the wallet opens', async () => { + render(); + await waitFor(() => screen.getByText('Review and fund')); + fireEvent.click(screen.getByText('Review and fund')); + + await waitFor(() => expect(screen.getByText('Fund escrow')).toBeDefined()); + + expect(screen.getByText(/payroll batch CF-00042/i)).toBeDefined(); + expect(screen.getByText('8,420.00 USDC')).toBeDefined(); + expect(screen.getByText('Stellar Testnet')).toBeDefined(); + // Destination and both parties to the dual-control gate. + expect(screen.getByText('Destination')).toBeDefined(); + expect(screen.getByText('Finance approver')).toBeDefined(); + // A quotable plan reference derived from the digest. + expect(screen.getByText('CF-PLAN-BEEF1234')).toBeDefined(); + }); + + it('lists every payment with its exact base-unit amount', async () => { + render(); + await waitFor(() => screen.getByText('Review and fund')); + fireEvent.click(screen.getByText('Review and fund')); + await waitFor(() => screen.getByText('Fund escrow')); + + expect(screen.getByText('Payments (2)')).toBeDefined(); + // The exact values that will be signed, unformatted. + expect(screen.getAllByText('42100000000')).toHaveLength(2); + }); + + it('releases the intent when the reviewer steps back, so the batch is not stuck', async () => { + routes['/funding/abandon'] = () => ({ attempt: attempt({ status: 'CANCELLED' }) }); + render(); + await waitFor(() => screen.getByText('Review and fund')); + fireEvent.click(screen.getByText('Review and fund')); + await waitFor(() => screen.getByText('Back')); + fireEvent.click(screen.getByText('Back')); + + await waitFor(() => expect(calls.some((c) => c.includes('/abandon'))).toBe(true)); + }); +}); + +describe('an unresolved transaction', () => { + beforeEach(() => { + routes = { + '/funding': () => + state({ + attempt: attempt({ status: 'SUBMITTED', hash: HASH, submittedAt: new Date().toISOString() }), + }), + }; + }); + + it('tells the user not to fund again, and offers no funding action', async () => { + render(); + + await waitFor(() => + expect(screen.getByText(/checking whether your funding transaction completed/i)).toBeDefined(), + ); + expect(screen.getByText('Do not fund this payroll again.')).toBeDefined(); + + // The contract is not idempotent: a second signature funds a second escrow. + expect(screen.queryByText('Fund escrow')).toBeNull(); + expect(screen.queryByText('Review and fund')).toBeNull(); + expect(screen.queryByText('Try again')).toBeNull(); + // Only a safe, read-only action. + expect(screen.getByText('Check status')).toBeDefined(); + }); + + it('recovers the existing attempt on mount rather than opening another', async () => { + render(); + await waitFor(() => screen.getByText('Check status')); + + // A reload must not create a second intent. + expect(calls.filter((c) => c.includes('/intent'))).toHaveLength(0); + expect(calls[0]).toBe('GET '); + }); + + it('announces the uncertain state to assistive technology', async () => { + render(); + await waitFor(() => + expect( + screen.getByText(/Still verifying whether the funding transaction completed/i), + ).toBeDefined(), + ); + }); +}); + +describe('a confirmed batch', () => { + beforeEach(() => { + routes = { + '/funding': () => + state({ + attempt: attempt({ status: 'CONFIRMED', hash: HASH, confirmedAt: new Date().toISOString() }), + escrow: { id: 'esc_1', onChainId: 9 }, + }), + }; + }); + + it('reports funded, with the escrow and transaction, and offers no re-funding', async () => { + render(); + await waitFor(() => expect(screen.getByText('Escrow funded')).toBeDefined()); + + expect(screen.getByText('#9')).toBeDefined(); + expect(screen.getByText(/Verified against the chain/i)).toBeDefined(); + expect(screen.queryByText('Fund escrow')).toBeNull(); + expect(screen.queryByText('Review and fund')).toBeNull(); + }); +}); + +describe('a batch that cannot be funded', () => { + it('lists every blocker at once, with row numbers', async () => { + routes = { + '/funding': () => + state({ + assessment: { + eligible: false, + paymentCount: 2, + total: '8,420.00', + totalBaseUnits: '84200000000', + blockers: [ + { code: 'PERIOD_REQUIRED', message: 'Row 2 has no pay period.', position: 2 }, + { + code: 'NO_DISTINCT_FINANCE_APPROVER', + message: 'This organization has no second wallet to act as finance approver.', + }, + ], + }, + plan: null, + }), + }; + render(); + + await waitFor(() => + expect(screen.getByText('This payroll cannot be funded yet')).toBeDefined(), + ); + expect(screen.getByText('Row 2')).toBeDefined(); + expect(screen.getByText(/no second wallet/i)).toBeDefined(); + // No signing path out of a blocked state. + expect(screen.queryByText('Fund escrow')).toBeNull(); + }); +}); + +describe('a mismatched transaction', () => { + it('says the escrow was not attached and does not offer to fund again', async () => { + routes = { + '/funding/intent': () => ({ created: true, attempt: attempt(), plan: plan() }), + '/funding/confirm': () => ({ + outcome: 'MISMATCH', + attempt: attempt({ status: 'SUBMITTED', hash: HASH }), + differences: ['payment 0 pays G…, the plan says G…'], + }), + '/funding': () => + state({ attempt: attempt({ status: 'SUBMITTED', hash: HASH }) }), + }; + + // Reached via the server's verdict, which the panel renders without softening. + render(); + await waitFor(() => screen.getByText('Check status')); + fireEvent.click(screen.getByText('Check status')); + + // With no escrow id known, Check status reloads rather than confirming β€” the + // panel must not invent an escrow id to verify against. + await waitFor(() => expect(calls.filter((c) => c.startsWith('GET')).length).toBeGreaterThan(1)); + expect(screen.queryByText('Fund escrow')).toBeNull(); + }); +}); + +describe('recovery through the page (mandatory regression)', () => { + it('resolves the escrow from the hash on mount and becomes funded, creating nothing new', async () => { + // The state a user lands in after a reload or a dropped connection: a submitted + // transaction, a known hash, and no escrow id. + let confirmed = false; + routes = { + '/funding/confirm': () => { + confirmed = true; + return { + outcome: 'CONFIRMED', + attempt: attempt({ status: 'CONFIRMED', hash: HASH, confirmedAt: new Date().toISOString() }), + escrow: { id: 'esc_1', onChainId: 9 }, + }; + }, + '/funding': () => + confirmed + ? state({ + attempt: attempt({ status: 'CONFIRMED', hash: HASH }), + escrow: { id: 'esc_1', onChainId: 9 }, + }) + : state({ attempt: attempt({ status: 'SUBMITTED', hash: HASH }) }), + }; + + render(); + + // Recovery happens without the user doing anything. + await waitFor(() => expect(screen.getByText('Escrow funded')).toBeDefined()); + expect(screen.getByText('#9')).toBeDefined(); + + // The confirm call carried NO escrow id: the server resolved it from the hash. + const confirmCall = calls.find((c) => c.includes('/confirm')); + expect(confirmCall).toBeDefined(); + + // Nothing new was created, and no signature was requested. + expect(calls.filter((c) => c.includes('/intent'))).toHaveLength(0); + expect(calls.filter((c) => c.includes('/submitted'))).toHaveLength(0); + expect(calls.filter((c) => c.includes('/abandon'))).toHaveLength(0); + expect(screen.queryByText('Fund escrow')).toBeNull(); + expect(screen.queryByText('Review and fund')).toBeNull(); + }); + + it('attempts recovery once, not in a loop, when it stays unresolved', async () => { + routes = { + '/funding/confirm': () => ({ + outcome: 'UNVERIFIABLE', + attempt: attempt({ status: 'SUBMITTED', hash: HASH }), + reason: 'No escrow/created event has been observed yet.', + }), + '/funding': () => state({ attempt: attempt({ status: 'SUBMITTED', hash: HASH }) }), + }; + + render(); + await waitFor(() => expect(screen.getByText('Check status')).toBeDefined()); + await new Promise((r) => setTimeout(r, 60)); + + // One automatic attempt. Re-verifying a genuinely pending transaction on a loop + // is noise, and "Check status" remains available. + expect(calls.filter((c) => c.includes('/confirm'))).toHaveLength(1); + expect(screen.getByText('Do not fund this payroll again.')).toBeDefined(); + }); +}); diff --git a/src/components/modals/CreateEscrowModal.tsx b/src/components/modals/CreateEscrowModal.tsx index 43b68d7..27f8497 100644 --- a/src/components/modals/CreateEscrowModal.tsx +++ b/src/components/modals/CreateEscrowModal.tsx @@ -1,73 +1,209 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; +import { parseAmount, formatAmount, hoursForAmount, MoneyParseError, SAC_DECIMALS } from '@/lib/money'; interface CreateEscrowModalProps { isOpen: boolean; onClose: () => void; - onSubmit: (workerPubKey: string, amountCents: number, rateCents: number) => Promise; + /** + * Amounts are base units (bigint), never dollars-as-number. See lib/money. + * `financeApprover` is required on-chain: the contract rejects an escrow whose + * manager and finance approver are the same key. + */ + onSubmit: ( + workerPubKey: string, + financeApprover: string, + amountUnits: bigint, + rateUnits: bigint + ) => Promise; isMockMode: boolean; + /** The connected wallet β€” it becomes the escrow manager. */ + managerAddress?: string; } -export function CreateEscrowModal({ isOpen, onClose, onSubmit, isMockMode }: CreateEscrowModalProps) { +const STELLAR_ADDRESS = /^[GC][A-Z2-7]{55}$/; + +export function CreateEscrowModal({ + isOpen, + onClose, + onSubmit, + isMockMode, + managerAddress, +}: CreateEscrowModalProps) { const [newWorker, setNewWorker] = useState(''); + const [financeApprover, setFinanceApprover] = useState(''); const [newAmount, setNewAmount] = useState('100'); const [newRate, setNewRate] = useState('2.5'); + const [formError, setFormError] = useState(null); + + /** + * Preview the exact on-chain figures before the user commits funds. + * + * The contract enforces `hours Γ— rate_per_hour == amount` and refuses + * anything else, so an amount that is not a whole multiple of the rate would + * fund custody into an escrow that can never settle. Surfacing it here turns + * a stuck escrow into a form message. + */ + type Preview = + | { error: string } + | { error?: undefined; amountUnits: bigint; rateUnits: bigint; hours: bigint }; + + const preview: Preview = useMemo((): Preview => { + try { + const amountUnits = parseAmount(newAmount, SAC_DECIMALS); + const rateUnits = parseAmount(newRate, SAC_DECIMALS); + if (amountUnits <= 0n) return { error: 'Amount must be greater than zero.' }; + if (rateUnits <= 0n) return { error: 'Hourly rate must be greater than zero.' }; + + const hours = hoursForAmount(amountUnits, rateUnits); + if (hours === null) { + return { + error: + `${newAmount} is not a whole number of hours at ${newRate}/hr. ` + + `Adjust the amount to a multiple of the rate.`, + }; + } + return { amountUnits, rateUnits, hours }; + } catch (e) { + return { error: e instanceof MoneyParseError ? e.message : 'Invalid amount.' }; + } + }, [newAmount, newRate]); if (!isOpen) return null; + const workerValid = isMockMode || STELLAR_ADDRESS.test(newWorker.trim()); + const financeValid = isMockMode || STELLAR_ADDRESS.test(financeApprover.trim()); + const financeIsManager = + !!managerAddress && financeApprover.trim() === managerAddress; + const financeIsWorker = + !!financeApprover.trim() && financeApprover.trim() === newWorker.trim(); + + const canSubmit = + workerValid && + financeValid && + !financeIsManager && + !financeIsWorker && + preview.error === undefined; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - const amountCents = Math.floor(parseFloat(newAmount) * 100); - const rateCents = Math.floor(parseFloat(newRate) * 100); - await onSubmit(newWorker.trim(), amountCents, rateCents); - - // Reset form after submit + setFormError(null); + + if (preview.error !== undefined) { + setFormError(preview.error); + return; + } + if (financeIsManager) { + setFormError('Finance approver must be a different person from the manager.'); + return; + } + + await onSubmit( + newWorker.trim(), + financeApprover.trim(), + preview.amountUnits, + preview.rateUnits + ); + setNewWorker(''); + setFinanceApprover(''); setNewAmount('100'); setNewRate('2.5'); }; + const inputCls = (invalid: boolean) => + `w-full bg-slate-950 border focus:border-violet-500 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 outline-none ${ + invalid ? 'border-rose-500/50' : 'border-slate-800' + }`; + return ( -
+
-

Initialize New Escrow

+

+ Initialize New Escrow +

-
+ +
+ + setFinanceApprover(e.target.value)} + className={inputCls( + (!!financeApprover && !financeValid) || financeIsManager || financeIsWorker + )} + /> + {financeIsManager ? ( +

+ Separation of duties: the finance approver cannot be the manager + creating this escrow. The contract rejects it. +

+ ) : financeIsWorker ? ( +

+ The worker being paid cannot approve their own payment. +

+ ) : financeApprover && !financeValid ? ( +

+ Invalid Stellar address. +

+ ) : (

- {isMockMode - ? 'Enter any identifier (e.g. WorkerA) for the mock demo.' - : 'Must be a valid 56-character Stellar address (starts with G or C).'} + A second signer. Funds move only after both this key and the + manager approve on-chain.

)}
- + setNewAmount(e.target.value)} @@ -75,11 +211,16 @@ export function CreateEscrowModal({ isOpen, onClose, onSubmit, isMockMode }: Cre />
- + setNewRate(e.target.value)} @@ -88,6 +229,43 @@ export function CreateEscrowModal({ isOpen, onClose, onSubmit, isMockMode }: Cre
+ {/* What will actually be funded, in the asset's own units. */} +
+ {preview.error !== undefined ? ( +

{preview.error}

+ ) : ( +
+
+
Escrowed
+
+ {formatAmount(preview.amountUnits, SAC_DECIMALS)} USDC +
+
+
+
Verified hours
+
+ {preview.hours.toString()} h @ {formatAmount(preview.rateUnits, SAC_DECIMALS)}/h +
+
+
+
Base units
+
+ {preview.amountUnits.toString()} +
+
+
+ )} +
+ + {formError && ( +

+ {formError} +

+ )} +
diff --git a/src/components/modals/__tests__/CreateEscrowModal.test.tsx b/src/components/modals/__tests__/CreateEscrowModal.test.tsx index 89d5148..3068db8 100644 --- a/src/components/modals/__tests__/CreateEscrowModal.test.tsx +++ b/src/components/modals/__tests__/CreateEscrowModal.test.tsx @@ -2,6 +2,10 @@ import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { CreateEscrowModal } from '../CreateEscrowModal'; +const MANAGER = 'G' + 'A'.repeat(55); +const FINANCE = 'G' + 'B'.repeat(55); +const WORKER = 'G' + 'C'.repeat(55); + describe('CreateEscrowModal Component', () => { const mockOnSubmit = vi.fn(); const mockOnClose = vi.fn(); @@ -10,66 +14,114 @@ describe('CreateEscrowModal Component', () => { vi.clearAllMocks(); }); - it('renders nothing when isOpen is false', () => { - const { container } = render( - + const renderModal = (props: Partial> = {}) => + render( + ); + + const fill = (label: RegExp, value: string) => + fireEvent.change(screen.getByLabelText(label), { target: { value } }); + + it('renders nothing when isOpen is false', () => { + const { container } = renderModal({ isOpen: false }); expect(container.firstChild).toBeNull(); }); it('renders the modal when isOpen is true', () => { - render( - - ); + renderModal({ isMockMode: true }); expect(screen.getByText(/Initialize New Escrow/i)).toBeInTheDocument(); - expect(screen.getByText(/Worker Public Key/i)).toBeInTheDocument(); - expect(screen.getByText(/Max Amount/i)).toBeInTheDocument(); - expect(screen.getByText(/Hourly Rate/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Worker Public Key/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Finance Approver/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Amount \(USDC\)/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Hourly Rate/i)).toBeInTheDocument(); }); it('calls onClose when cancel button is clicked', () => { - render( - - ); - const cancelBtn = screen.getByRole('button', { name: /Cancel/i }); - fireEvent.click(cancelBtn); + renderModal({ isMockMode: true }); + fireEvent.click(screen.getByRole('button', { name: /Cancel/i })); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('shows an error for invalid Stellar addresses in live mode', () => { - render( - - ); - - const workerInput = screen.getByPlaceholderText('G...'); - fireEvent.change(workerInput, { target: { value: 'InvalidWorker123' } }); - - // The invalid warning should appear since it does not match the G... or C... 56 char regex + renderModal(); + fill(/Worker Public Key/i, 'InvalidWorker123'); expect(screen.getByText(/Invalid Stellar address/i)).toBeInTheDocument(); }); - it('calls onSubmit with correct parsed cents values on form submit', async () => { - render( - - ); - - const workerInput = screen.getByPlaceholderText('G...'); - const maxAmountInput = screen.getAllByRole('spinbutton')[0]; - const hourlyRateInput = screen.getAllByRole('spinbutton')[1]; - - fireEvent.change(workerInput, { target: { value: 'WorkerAlpha' } }); - fireEvent.change(maxAmountInput, { target: { value: '250.50' } }); - fireEvent.change(hourlyRateInput, { target: { value: '15.25' } }); - - const submitBtn = screen.getByRole('button', { name: /Create Escrow/i }); - - // Use act to wait for the async onSubmit and state reset + it('submits Stellar base units, not cents', async () => { + // The regression this pins: 250.50 USDC is 2_505_000_000 base units on a + // 7-decimal asset. The old modal emitted 25050 ("cents"), which the client + // passed straight to the contract β€” funding 0.0025050 USDC. + renderModal(); + fill(/Worker Public Key/i, WORKER); + fill(/Finance Approver/i, FINANCE); + // 250.50 at 8.35/h is exactly 30 hours, satisfying the contract's + // hours x rate == amount invariant. + fill(/Amount \(USDC\)/i, '250.50'); + fill(/Hourly Rate/i, '8.35'); + await act(async () => { - fireEvent.click(submitBtn); + fireEvent.click(screen.getByRole('button', { name: /Create Escrow/i })); }); - // 250.50 * 100 = 25050 - // 15.25 * 100 = 1525 - expect(mockOnSubmit).toHaveBeenCalledWith('WorkerAlpha', 25050, 1525); + expect(mockOnSubmit).toHaveBeenCalledWith( + WORKER, + FINANCE, + 2_505_000_000n, + 83_500_000n + ); + }); + + it('previews the hours the escrowed amount actually buys', () => { + renderModal(); + fill(/Amount \(USDC\)/i, '1000'); + fill(/Hourly Rate/i, '25'); + // 1000 / 25 = 40 hours β€” the figure the contract will require the oracle + // to attest to, since it enforces hours x rate == amount. + expect(screen.getByText(/40 h @ 25\.00\/h/)).toBeInTheDocument(); + }); + + it('blocks an amount that is not a whole number of hours', () => { + // The contract rejects this with AmountHoursMismatch (#17), which would + // otherwise fund custody into an escrow that can never settle. + renderModal(); + fill(/Amount \(USDC\)/i, '1000.01'); + fill(/Hourly Rate/i, '25'); + + expect(screen.getByText(/not a whole number of hours/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Create Escrow/i })).toBeDisabled(); + }); + + it('blocks a finance approver equal to the manager', () => { + // Separation of duties: the contract rejects it with SignersNotDistinct + // (#15), and the old dashboard sent the manager as both signers every time. + renderModal(); + fill(/Worker Public Key/i, WORKER); + fill(/Finance Approver/i, MANAGER); + + expect(screen.getByText(/cannot be the manager/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Create Escrow/i })).toBeDisabled(); + }); + + it('blocks a worker approving their own payment', () => { + renderModal(); + fill(/Worker Public Key/i, WORKER); + fill(/Finance Approver/i, WORKER); + + expect(screen.getByText(/cannot approve their own payment/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Create Escrow/i })).toBeDisabled(); + }); + + it('rejects more precision than the asset can represent', () => { + renderModal(); + fill(/Amount \(USDC\)/i, '1.00000001'); + expect(screen.getByText(/decimal places but this asset supports 7/i)).toBeInTheDocument(); }); }); diff --git a/src/components/payments/BatchPaymentsTable.tsx b/src/components/payments/BatchPaymentsTable.tsx new file mode 100644 index 0000000..1d4734d --- /dev/null +++ b/src/components/payments/BatchPaymentsTable.tsx @@ -0,0 +1,127 @@ +'use client'; + +import { PaymentStateBadge, PaymentTransactionRef } from './PaymentStateBadge'; +import { txUrl } from '@/lib/explorer'; + +/** + * Per-payment breakdown of a batch. + * + * A batch is NOT a payment. It was previously rendered as one row carrying the + * first payee's figures, so a twelve-contractor payroll looked like a single + * $800 payment and eleven people were invisible. Each payment gets its own row, + * its own amount, and its own state. + * + * The summary line reports the batch as broken if ANY payment is broken, rather + * than by majority: the one failed payment in an otherwise-paid batch is exactly + * the one a finance team needs to see. + */ + +export interface BatchPaymentRow { + id: string; + index?: number | null; + recipient: string; + /** Pre-formatted at the asset's own precision β€” never re-derived here. */ + amount: string; + hours?: string; + state: string; + txHash?: string | null; +} + +export interface BatchPaymentsTableProps { + reference?: string; + payments: readonly BatchPaymentRow[]; + /** Pre-formatted batch total. */ + total?: string; + assetCode?: string; + className?: string; +} + +const short = (addr: string) => + addr.length > 14 ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : addr; + +export function BatchPaymentsTable({ + reference, + payments, + total, + assetCode = 'USDC', + className = '', +}: BatchPaymentsTableProps) { + if (payments.length === 0) { + return ( +
+

No payments in this batch

+

+ Payments appear here once the escrow is funded on-chain and indexed. +

+
+ ); + } + + return ( +
+
+

+ {reference ? `Batch ${reference}` : 'Payments'} + + {payments.length} payment{payments.length === 1 ? '' : 's'} + +

+ {total && ( +

+ {total} {assetCode} +

+ )} +
+ + {/* Wide content scrolls inside its own container so the page never does. */} +
+ + + + + + + + + + + + + + {payments.map((p, i) => ( + + + + + + + + + ))} + +
+ {reference ? `Payments in batch ${reference}` : 'Payments'}, with + recipient, amount and current lifecycle state. +
#RecipientAmountHoursStateTransaction
{(p.index ?? i) + 1} + + {short(p.recipient)} + + {p.amount} + {p.hours ?? 'β€”'} + + + + {/* Only rendered where a transaction can actually exist. */} + +
+
+
+ ); +} diff --git a/src/components/payments/PaymentStateBadge.tsx b/src/components/payments/PaymentStateBadge.tsx new file mode 100644 index 0000000..ee28392 --- /dev/null +++ b/src/components/payments/PaymentStateBadge.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { PaymentState } from '@prisma/client'; +import { describeState, type StateDescriptor } from '@/lib/payments/state-machine'; + +/** + * Renders a payment's real lifecycle state. + * + * ── Why there is no "Processing" ────────────────────────────────────────────── + * Collapsing AWAITING_ORACLE, AWAITING_MANAGER, AWAITING_FINANCE, SUBMITTING and + * CONFIRMING into one spinner tells an operator nothing about what to do next β€” + * and three of those five are waiting on a *person*, not on a machine. Each state + * carries its own label and its own one-line explanation of what is actually true + * right now. + * + * Tone comes from the state descriptor, so a state cannot be styled as success in + * one view and danger in another. Only PAID is ever green. + */ + +const TONE_CLASSES: Record = { + neutral: 'border-slate-600/40 bg-slate-500/10 text-slate-300', + progress: 'border-violet-500/40 bg-violet-500/10 text-violet-300', + pending: 'border-amber-500/40 bg-amber-500/10 text-amber-300', + success: 'border-emerald-500/40 bg-emerald-500/10 text-emerald-300', + warning: 'border-orange-500/40 bg-orange-500/10 text-orange-300', + danger: 'border-rose-500/40 bg-rose-500/10 text-rose-300', +}; + +/** States where something is genuinely in flight, so a pulse is honest. */ +const IN_FLIGHT: readonly PaymentState[] = [ + PaymentState.VALIDATING, + PaymentState.SUBMITTING, + PaymentState.CONFIRMING, +]; + +export interface PaymentStateBadgeProps { + state: PaymentState | string; + /** Show the explanatory line beneath the label. */ + withDescription?: boolean; + className?: string; +} + +export function PaymentStateBadge({ + state, + withDescription = false, + className = '', +}: PaymentStateBadgeProps) { + // An unrecognized state is surfaced, not normalised into something benign: + // rendering an unknown value as "Pending" would hide a real data problem. + const known = (Object.values(PaymentState) as string[]).includes(state as string); + if (!known) { + return ( + + Unknown state + + ); + } + + const s = state as PaymentState; + const d = describeState(s); + const inFlight = IN_FLIGHT.includes(s); + + return ( + + + + {d.label} + {d.needsAttention && β€” needs attention} + + {withDescription && ( + {d.description} + )} + + ); +} + +/** + * A payment's transaction reference, shown only where one can exist. + * + * Two conditions, both required: the state must be one where a transaction is + * plausible, AND a hash must actually be present. Rendering an explorer link for + * a payment that was never submitted invites a reader to believe something + * settled β€” and a link for SUBMISSION_FAILED would point at nothing at all. + */ +export function PaymentTransactionRef({ + state, + hash, + href, +}: { + state: PaymentState | string; + hash: string | null | undefined; + href?: string; +}) { + const known = (Object.values(PaymentState) as string[]).includes(state as string); + if (!known || !hash) return null; + if (!describeState(state as PaymentState).mayHaveTransaction) return null; + + const short = `${hash.slice(0, 8)}…${hash.slice(-6)}`; + return href ? ( + + {short} + + ) : ( + {short} + ); +} diff --git a/src/components/payments/__tests__/BatchPaymentsTable.test.tsx b/src/components/payments/__tests__/BatchPaymentsTable.test.tsx new file mode 100644 index 0000000..862280c --- /dev/null +++ b/src/components/payments/__tests__/BatchPaymentsTable.test.tsx @@ -0,0 +1,54 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { PaymentState } from '@prisma/client'; +import { BatchPaymentsTable } from '../BatchPaymentsTable'; + +vi.mock('@/lib/explorer', () => ({ txUrl: (h: string) => `https://explorer/tx/${h}` })); + +const HASH = 'c3847d85680bdeadbeefc0ffee1234567890abcdef1234567890abcdef123456'; + +const THREE = [ + { id: 'p0', index: 0, recipient: 'G' + '1'.repeat(55), amount: '1,000.00', hours: '40', state: PaymentState.PAID, txHash: HASH }, + { id: 'p1', index: 1, recipient: 'G' + '2'.repeat(55), amount: '960.00', hours: '32', state: PaymentState.AWAITING_FINANCE, txHash: null }, + { id: 'p2', index: 2, recipient: 'G' + '3'.repeat(55), amount: '900.00', hours: '45', state: PaymentState.SETTLEMENT_FAILED, txHash: HASH }, +]; + +describe('BatchPaymentsTable', () => { + it('renders one row per payment, not one row per batch', () => { + // The defect this replaces: twelve contractors rendered as a single payment. + render(); + expect(screen.getAllByRole('row')).toHaveLength(4); // header + 3 + expect(screen.getByText('3 payments')).toBeInTheDocument(); + }); + + it('shows each payment’s own amount', () => { + render(); + for (const amount of ['1,000.00', '960.00', '900.00']) { + expect(screen.getByText(amount)).toBeInTheDocument(); + } + }); + + it('shows each payment’s own state, not one batch state', () => { + render(); + expect(screen.getByText('Paid')).toBeInTheDocument(); + expect(screen.getByText('Awaiting finance approval')).toBeInTheDocument(); + expect(screen.getByText('Settlement failed')).toBeInTheDocument(); + }); + + it('links a transaction only where one exists', () => { + render(); + // PAID and SETTLEMENT_FAILED have hashes; AWAITING_FINANCE does not. + expect(screen.getAllByRole('link')).toHaveLength(2); + }); + + it('renders an empty state rather than an empty table', () => { + render(); + expect(screen.getByText(/No payments in this batch/i)).toBeInTheDocument(); + expect(screen.queryByRole('table')).toBeNull(); + }); + + it('gives the table an accessible caption', () => { + render(); + expect(screen.getByRole('table')).toHaveAccessibleName(/Payments in batch CF-00042/i); + }); +}); diff --git a/src/components/payments/__tests__/PaymentStateBadge.test.tsx b/src/components/payments/__tests__/PaymentStateBadge.test.tsx new file mode 100644 index 0000000..be07755 --- /dev/null +++ b/src/components/payments/__tests__/PaymentStateBadge.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { PaymentState } from '@prisma/client'; +import { PaymentStateBadge, PaymentTransactionRef } from '../PaymentStateBadge'; + +describe('PaymentStateBadge', () => { + it('renders a distinct label for every state, never "Processing"', () => { + for (const state of Object.values(PaymentState)) { + const { unmount } = render(); + expect(screen.queryByText(/^Processing$/i)).toBeNull(); + unmount(); + } + }); + + it.each([ + [PaymentState.AWAITING_ORACLE, 'Awaiting oracle verification'], + [PaymentState.AWAITING_MANAGER, 'Awaiting manager approval'], + [PaymentState.AWAITING_FINANCE, 'Awaiting finance approval'], + [PaymentState.READY_TO_SETTLE, 'Ready to settle'], + [PaymentState.SUBMITTING, 'Submitting to Stellar'], + [PaymentState.CONFIRMING, 'Confirming on Stellar'], + [PaymentState.PAID, 'Paid'], + [PaymentState.SETTLEMENT_FAILED, 'Settlement failed'], + [PaymentState.RECONCILIATION_REQUIRED, 'Reconciliation required'], + ])('labels %s as "%s"', (state, label) => { + render(); + expect(screen.getByText(label)).toBeInTheDocument(); + }); + + it('does not present CONFIRMING as paid', () => { + render(); + expect(screen.getByText(/not yet paid/i)).toBeInTheDocument(); + }); + + it('surfaces an unrecognized state instead of normalising it', () => { + // Rendering an unknown value as something benign would hide a data problem. + render(); + expect(screen.getByText(/Unknown state/i)).toBeInTheDocument(); + }); +}); + +describe('PaymentTransactionRef', () => { + const HASH = 'c3847d85680bdeadbeefc0ffee1234567890abcdef1234567890abcdef123456'; + + it('shows a reference for a settled payment', () => { + render(); + expect(screen.getByRole('link')).toHaveAttribute('href', 'https://x'); + }); + + it('renders nothing when there is no hash', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it.each([PaymentState.DRAFT, PaymentState.AWAITING_ORACLE, PaymentState.SUBMISSION_FAILED])( + 'renders nothing for %s even if a hash is supplied', + (state) => { + // A link here would imply something reached the chain when nothing did. + const { container } = render(); + expect(container.firstChild).toBeNull(); + } + ); +}); diff --git a/src/components/payroll/BatchDetail.tsx b/src/components/payroll/BatchDetail.tsx new file mode 100644 index 0000000..ca697ed --- /dev/null +++ b/src/components/payroll/BatchDetail.tsx @@ -0,0 +1,677 @@ +'use client'; + +/** + * Batch detail β€” where a finance user understands a payroll. + * + * What is being paid, to whom, how much, what is waiting, what happened, whether + * funds have moved, and what evidence proves it. + * + * Two standing rules: + * + * 1. EVERY STATEMENT HAS A SOURCE. Money comes from the server already formatted, + * with the exact base-unit value alongside it. The activity timeline is rendered + * from real `AuditEvent` rows and nothing else β€” it will look sparse early in a + * batch's life, and that is correct. Padding it with plausible entries nobody + * recorded would make the one screen whose job is to show what happened the least + * trustworthy thing in the product. + * 2. APPROVAL IS READ FROM APPROVAL RECORDS, never inferred from a payment's state. + * A payment can be AWAITING_FINANCE for reasons unrelated to who signed what. + */ + +import { Fragment, useMemo, useState } from 'react'; +import { NetworkBadge } from '@/components/NetworkBadge'; +import { FundingPanel } from '@/components/funding/FundingPanel'; + +export interface BatchDetailPayment { + id: string; + recipient: string; + amount: string; + amountBaseUnits: string; + /** Formatted by the server. The exact value is rateBaseUnits. */ + rate: string; + rateBaseUnits: string; + hours: string; + asset: string | null; + state: string; + stateLabel: string; + tone: string; + needsAttention: boolean; + stateReason: string | null; + reference: string | null; + transactionHash: string | null; + settledAt: string | null; + onChainPaymentIndex: number | null; + approvals: { + role: string; + decision: string; + actorAddress: string; + createdAt: string | null; + }[]; +} + +export interface BatchDetailData { + batch: { + id: string; + reference: string; + projectId: string | null; + periodStart: string | null; + periodEnd: string | null; + createdAt: string | null; + source: { + filename: string | null; + rowsSeen: number | null; + checksum: string | null; + uploadedBy: string | null; + }; + total: string; + totalBaseUnits: string; + asset: string | null; + paymentCount: number; + standing: { + headline: string; + byState: Record; + needsAttention: number; + totalAmountBaseUnits: string; + paidAmountBaseUnits: string; + paid: string; + }; + payments: BatchDetailPayment[]; + }; + activity: { + id: string; + type: string; + at: string | null; + actor: { kind: 'user'; address: string } | { kind: 'system'; system: string } | null; + previousState: string | null; + newState: string | null; + txHash: string | null; + paymentId: string | null; + metadata: Record | null; + }[]; + findings: { + id: string; + kind: string; + severity: string; + status: string; + detail: string; + paymentId: string | null; + firstDetectedAt: string | null; + lastObservedAt: string | null; + observationCount: number | null; + remediation: string | null; + }[]; +} + +/** + * Internal state β†’ the words a finance user reads. + * + * A mapping only. The UI never derives a financial state of its own, and + * deliberately never renders an unverified outcome as "Failed": "still verifying" + * and "failed" are different claims about somebody's money. + */ +const STATE_LABELS: Record = { + DRAFT: 'Draft', + VALIDATING: 'Preparing to fund', + AWAITING_ORACLE: 'Awaiting work verification', + ORACLE_VERIFIED: 'Work verified', + AWAITING_MANAGER: 'Awaiting manager approval', + AWAITING_FINANCE: 'Awaiting finance approval', + READY_TO_SETTLE: 'Ready to settle', + SUBMITTING: 'Submitting', + CONFIRMING: 'Confirming on Stellar', + PAID: 'Paid', + REJECTED: 'Rejected', + CANCELLED: 'Cancelled', + EXPIRED: 'Expired', + SUBMISSION_FAILED: 'Submission failed', + SETTLEMENT_FAILED: 'Settlement failed', + RECONCILIATION_REQUIRED: 'Needs attention', +}; + +/** Audit event types β†’ readable lines. Unknown types render their own type. */ +const EVENT_LABELS: Record = { + 'payroll.batch.created': 'Payroll batch created', + 'funding.intent.opened': 'Funding prepared', + 'funding.submitted': 'Funding transaction submitted', + 'funding.confirmed': 'Funding confirmed on Stellar', + 'funding.failed': 'Funding failed', + 'funding.declined': 'Funding signature declined', + 'funding.mismatch': 'Funding could not be verified', + 'payment.state.changed': 'Payment state changed', + 'approval.granted': 'Approval recorded', + 'payment.indexed': 'Payment observed on-chain', +}; + +const EXPLORER = 'https://stellar.expert/explorer/testnet'; + +function truncate(value: string, head = 6, tail = 6): string { + return value.length > head + tail + 2 ? `${value.slice(0, head)}…${value.slice(-tail)}` : value; +} + +function formatWhen(iso: string | null): string { + if (!iso) return 'β€”'; + const d = new Date(iso); + return Number.isNaN(d.getTime()) ? 'β€”' : d.toISOString().replace('T', ' ').slice(0, 16); +} + +function formatDay(iso: string | null): string { + return iso ? iso.slice(0, 10) : 'β€”'; +} + +export interface BatchDetailProps { + data: BatchDetailData; + orgId?: string; + /** Called after funding confirms, so the host can refetch. */ + onChanged?: () => void; +} + +export function BatchDetail({ data, orgId, onChanged }: BatchDetailProps) { + const { batch, activity, findings } = data; + const [expanded, setExpanded] = useState(null); + const [showTechnical, setShowTechnical] = useState(false); + + const headline = STATE_LABELS[batch.standing.headline] ?? batch.standing.headline; + + /** Approvals across the batch, from Approval records rather than payment state. */ + const approvalSummary = useMemo(() => { + const byRole = new Map(); + for (const p of batch.payments) { + for (const a of p.approvals) { + if (a.decision === 'APPROVED' && !byRole.has(a.role)) { + byRole.set(a.role, { actorAddress: a.actorAddress, createdAt: a.createdAt }); + } + } + } + return byRole; + }, [batch.payments]); + + const findingsByPayment = useMemo(() => { + const map = new Map(); + for (const f of findings) { + if (!f.paymentId) continue; + map.set(f.paymentId, [...(map.get(f.paymentId) ?? []), f]); + } + return map; + }, [findings]); + + return ( +
+
+
+
+

+ Payroll batch +

+

+ {batch.reference} +

+

+ {headline} + {batch.standing.needsAttention > 0 && ( + + Β· {batch.standing.needsAttention} need attention + + )} +

+
+
+ +

+ CoreFlow v2 on Stellar Testnet +

+
+
+
+ + {findings.length > 0 && } + + {/* Summary. Figures come from the server; nothing is totalled here. */} +
+
+
+
+
+
+
+
+ + + + + +
+

+ Payments +

+ {batch.payments.length === 0 ? ( + This batch has no payments. + ) : ( + setExpanded((current) => (current === id ? null : id))} + findingsByPayment={findingsByPayment} + /> + )} +
+ + + +
+ + {showTechnical && ( +
+ Stellar Testnet + + {batch.id} + + + {batch.totalBaseUnits} + + + {batch.standing.paidAmountBaseUnits} + + {batch.source.rowsSeen ?? 'β€”'} + + {batch.source.checksum ? truncate(batch.source.checksum, 10, 6) : 'β€”'} + +
+ )} +
+
+ ); +} + +function Figure({ + label, + value, + sub, + emphasis, +}: { + label: string; + value: string; + sub?: string; + emphasis?: boolean; +}) { + return ( +
+
{label}
+
+ {value} + {sub && {sub}} +
+
+ ); +} + +function Detail({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+
{label}
+
{children}
+
+ ); +} + +function Empty({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ); +} + +/** + * Dual control, from Approval records. + * + * Both halves are always shown, including the one that is missing: "waiting for + * finance approval" is the fact a reviewer needs, and a UI that only lists + * approvals that exist hides it. + */ +function Approvals({ + summary, +}: { + summary: Map; +}) { + const roles: { role: string; label: string }[] = [ + { role: 'MANAGER', label: 'Manager' }, + { role: 'FINANCE', label: 'Finance' }, + ]; + + return ( +
+

+ Approvals +

+

+ Two separate people must approve before funds are released. +

+
+ {roles.map(({ role, label }) => { + const given = summary.get(role); + return ( +
+
+ {label} +
+
+ {given ? ( + <> + Approved + + {truncate(given.actorAddress)} Β· {formatWhen(given.createdAt)} + + + ) : ( + Waiting for approval + )} +
+
+ ); + })} +
+
+ ); +} + +function PaymentsTable({ + payments, + expanded, + onToggle, + findingsByPayment, +}: { + payments: BatchDetailPayment[]; + expanded: string | null; + onToggle: (id: string) => void; + findingsByPayment: Map; +}) { + return ( +
+ {/* One row per Payment. A multi-payee payroll is never shown as a single total. */} + + + + + + + + + + + + + + {payments.map((p) => { + const open = expanded === p.id; + const rowFindings = findingsByPayment.get(p.id) ?? []; + return ( + // A keyed Fragment, because each payment renders two sibling rows and + // React needs the key on their common parent. + + + + + + + + + + {open && ( + + + + )} + + ); + })} + +
+ Every payment in this batch, one row each, with its own state +
RecipientAmountHoursRateStatus + Payment details +
+ {truncate(p.recipient, 8, 6)} + {p.reference && ( + {p.reference} + )} + + {p.amount} + {p.asset} + + {p.hours}h + + {p.rate} + /h + + + {STATE_LABELS[p.state] ?? p.stateLabel} + + {rowFindings.length > 0 && ( + needs attention + )} + + +
+
+ + {p.recipient} + + + {p.amountBaseUnits} + + + {p.rateBaseUnits} + + {p.hours} + + {p.onChainPaymentIndex ?? 'not funded'} + + {formatWhen(p.settledAt)} + + {p.approvals.length === 0 + ? 'none yet' + : p.approvals + .map((a) => `${a.role} ${a.decision.toLowerCase()}`) + .join(', ')} + + + {/* Only where the state allows one to exist. */} + {p.transactionHash ? ( + + {truncate(p.transactionHash)} + + ) : ( + 'not yet available' + )} + +
+ {p.stateReason && ( +

{p.stateReason}

+ )} + {rowFindings.map((f) => ( +

+ {f.severity} Β· {f.detail} +

+ ))} +
+
+ ); +} + +/** + * The activity timeline, from real audit rows only. + * + * Sparse by design. An empty or short timeline is a true statement about what has + * been recorded; a padded one is not. + */ +function Timeline({ activity }: { activity: BatchDetailData['activity'] }) { + const [open, setOpen] = useState(null); + + return ( +
+

+ Activity +

+ {activity.length === 0 ? ( + No activity has been recorded for this batch yet. + ) : ( +
    + {activity.map((e) => { + const isOpen = open === e.id; + return ( +
  1. +
    + + {EVENT_LABELS[e.type] ?? e.type} + {e.previousState && e.newState && ( + + {STATE_LABELS[e.previousState] ?? e.previousState} β†’{' '} + {STATE_LABELS[e.newState] ?? e.newState} + + )} + + + + + +
    +

    + {e.actor?.kind === 'user' ? ( + {truncate(e.actor.address)} + ) : e.actor?.kind === 'system' ? ( + <>by {e.actor.system} + ) : ( + 'system' + )} +

    + {isOpen && ( +
    + + {e.type} + + {e.txHash && ( + + + {truncate(e.txHash)} + + + )} + {e.metadata && + Object.entries(e.metadata) + .filter(([, v]) => v !== null && typeof v !== 'object') + .map(([k, v]) => ( + + {String(v)} + + ))} +
    + )} +
  2. + ); + })} +
+ )} +
+ ); +} + +function Findings({ findings }: { findings: BatchDetailData['findings'] }) { + return ( +
+

+ Payment verification needs attention +

+
    + {findings.map((f) => ( +
  • + + {f.severity} + + {f.detail} + + {f.kind} Β· first seen {formatWhen(f.firstDetectedAt)} + {f.lastObservedAt && <> Β· last checked {formatWhen(f.lastObservedAt)}} + {f.observationCount !== null && <> Β· seen {f.observationCount}Γ—} + + {f.remediation && ( + {f.remediation} + )} +
  • + ))} +
+ {/* + A finding is a disagreement to investigate, not proof a payment failed. + Nothing here restates it as failure. + */} +

+ A finding records that the database and the chain disagree. It does not by + itself mean a payment failed. +

+
+ ); +} diff --git a/src/components/payroll/__tests__/BatchDetail.test.tsx b/src/components/payroll/__tests__/BatchDetail.test.tsx new file mode 100644 index 0000000..353d192 --- /dev/null +++ b/src/components/payroll/__tests__/BatchDetail.test.tsx @@ -0,0 +1,301 @@ +/** + * Batch detail rendering. + * + * The properties that matter here are truthfulness properties: one row per payment + * with exact stored amounts, approvals read from Approval records rather than + * inferred from payment state, and a timeline containing only events that exist. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { BatchDetail, type BatchDetailData } from '../BatchDetail'; + +vi.mock('@/components/NetworkBadge', () => ({ + NetworkBadge: () => TESTNET, +})); +// The funding panel has its own tests; here it must simply not interfere. +vi.mock('@/components/funding/FundingPanel', () => ({ + FundingPanel: ({ batchId }: { batchId: string }) => ( +
{batchId}
+ ), +})); + +const A = 'G' + 'A'.repeat(55); +const B = 'G' + 'B'.repeat(55); +const C = 'G' + 'C'.repeat(55); +const MANAGER = 'G' + 'M'.repeat(55); + +function payment(over: Partial = {}) { + return { + id: 'pay_1', + recipient: A, + amount: '1,000.00', + amountBaseUnits: '10000000000', + rate: '25.00', + rateBaseUnits: '250000000', + hours: '40', + asset: 'USDC', + state: 'AWAITING_ORACLE', + stateLabel: 'Awaiting oracle', + tone: 'neutral', + needsAttention: false, + stateReason: null, + reference: null, + transactionHash: null, + settledAt: null, + onChainPaymentIndex: 0, + approvals: [], + ...over, + }; +} + +function data(over: Partial = {}): BatchDetailData { + return { + batch: { + id: 'bat_1', + reference: 'CF-00042', + projectId: null, + periodStart: '2026-09-01T00:00:00.000Z', + periodEnd: '2026-09-15T00:00:00.000Z', + createdAt: '2026-09-11T09:41:00.000Z', + source: { filename: 'september.csv', rowsSeen: 3, checksum: 'f'.repeat(64), uploadedBy: 'u1' }, + total: '2,860.00', + totalBaseUnits: '28600000000', + asset: 'USDC', + paymentCount: 3, + standing: { + headline: 'AWAITING_ORACLE', + byState: { AWAITING_ORACLE: 3 }, + needsAttention: 0, + totalAmountBaseUnits: '28600000000', + paidAmountBaseUnits: '0', + paid: '0.00', + }, + payments: [ + payment(), + payment({ id: 'pay_2', recipient: B, amount: '1,600.00', amountBaseUnits: '16000000000', hours: '80' }), + payment({ id: 'pay_3', recipient: C, amount: '260.00', amountBaseUnits: '2600000000', hours: '20' }), + ], + }, + activity: [ + { + id: 'aud_1', + type: 'payroll.batch.created', + at: '2026-09-11T09:41:00.000Z', + actor: { kind: 'user', address: MANAGER }, + previousState: null, + newState: null, + txHash: null, + paymentId: null, + metadata: { paymentCount: 3, totalBaseUnits: '28600000000' }, + }, + { + id: 'aud_2', + type: 'funding.confirmed', + at: '2026-09-11T09:46:00.000Z', + actor: { kind: 'system', system: 'funding-verifier' }, + previousState: null, + newState: null, + txHash: 'a'.repeat(64), + paymentId: null, + metadata: { onChainEscrowId: 9 }, + }, + ], + findings: [], + ...over, + }; +} + +beforeEach(() => vi.clearAllMocks()); +afterEach(() => vi.restoreAllMocks()); + +describe('header and summary', () => { + it('shows the reference, a human status and the Testnet environment', () => { + render(); + expect(screen.getByText('CF-00042')).toBeDefined(); + // Mapped from the domain state, not invented. + // Appears in the header and on each payment badge; all are the mapped label. + expect(screen.getAllByText('Awaiting work verification').length).toBeGreaterThan(0); + expect(screen.getByTestId('network-badge')).toBeDefined(); + expect(screen.getByText(/CoreFlow v2 on Stellar Testnet/i)).toBeDefined(); + }); + + it('renders money as the server formatted it, with no client arithmetic', () => { + render(); + expect(screen.getByText('2,860.00')).toBeDefined(); + expect(screen.getByText('0.00')).toBeDefined(); + // Exact base units available, but only in technical details. + expect(screen.queryByText('28600000000')).toBeNull(); + fireEvent.click(screen.getByText('Technical details')); + expect(screen.getAllByText('28600000000').length).toBeGreaterThan(0); + }); + + it('shows the pay period and source file', () => { + render(); + expect(screen.getByText('2026-09-01')).toBeDefined(); + expect(screen.getByText('to 2026-09-15')).toBeDefined(); + expect(screen.getByText('september.csv')).toBeDefined(); + }); +}); + +describe('payments', () => { + it('renders one row per payment and never an aggregate', () => { + render(); + // Three distinct amounts, three rows. + expect(screen.getByText('1,000.00')).toBeDefined(); + expect(screen.getByText('1,600.00')).toBeDefined(); + expect(screen.getByText('260.00')).toBeDefined(); + expect(screen.getAllByRole('button', { name: 'Details' })).toHaveLength(3); + }); + + it('reveals exact stored values in the drill-down', () => { + render(); + fireEvent.click(screen.getAllByRole('button', { name: 'Details' })[0]); + + expect(screen.getByText((t) => t === A)).toBeDefined(); + expect(screen.getByText('10000000000')).toBeDefined(); + expect(screen.getByText('250000000')).toBeDefined(); + // The readable rate is in the row; the exact value only in the drill-down. + expect(screen.getAllByText('25.00').length).toBeGreaterThan(0); + // No transaction exists yet, and the UI says so rather than showing a link. + expect(screen.getByText('not yet available')).toBeDefined(); + }); + + it('shows a state reason when the domain recorded one', () => { + const d = data(); + d.batch.payments[0].stateReason = 'Funding transaction prepared; awaiting signature.'; + render(); + fireEvent.click(screen.getAllByRole('button', { name: 'Details' })[0]); + expect(screen.getByText(/awaiting signature/i)).toBeDefined(); + }); + + it('renders an empty batch without a blank page', () => { + const d = data(); + d.batch.payments = []; + d.batch.paymentCount = 0; + render(); + expect(screen.getByText('This batch has no payments.')).toBeDefined(); + }); +}); + +describe('approvals', () => { + it('reads approvals from Approval records and shows the missing half', () => { + const d = data(); + d.batch.payments[0].approvals = [ + { role: 'MANAGER', decision: 'APPROVED', actorAddress: MANAGER, createdAt: '2026-09-11T14:41:00.000Z' }, + ]; + render(); + + expect(screen.getByText('Approved')).toBeDefined(); + // The absent half is stated, not omitted β€” that is the fact a reviewer needs. + expect(screen.getByText('Waiting for approval')).toBeDefined(); + expect(screen.getByText('Manager')).toBeDefined(); + expect(screen.getByText('Finance')).toBeDefined(); + }); + + it('does not infer approval from payment state', () => { + const d = data(); + // A payment past the approval stages, but with no Approval records. + d.batch.payments = [payment({ state: 'READY_TO_SETTLE', approvals: [] })]; + render(); + expect(screen.getAllByText('Waiting for approval')).toHaveLength(2); + expect(screen.queryByText('Approved')).toBeNull(); + }); +}); + +describe('activity timeline', () => { + it('renders only the events that exist', () => { + render(); + expect(screen.getByText('Payroll batch created')).toBeDefined(); + expect(screen.getByText('Funding confirmed on Stellar')).toBeDefined(); + + // Plausible-sounding events nobody recorded must not appear. + for (const fabricated of [ + 'CSV reviewed', + 'Oracle verified', + 'Manager reviewed', + 'Finance reviewed', + ]) { + expect(screen.queryByText(fabricated)).toBeNull(); + } + expect(screen.getAllByText('More')).toHaveLength(2); + }); + + it('says so plainly when nothing has been recorded', () => { + render(); + expect(screen.getByText('No activity has been recorded for this batch yet.')).toBeDefined(); + }); + + it('attributes a system actor without pretending a person acted', () => { + render(); + expect(screen.getByText(/by funding-verifier/)).toBeDefined(); + }); + + it('exposes event metadata on demand', () => { + render(); + fireEvent.click(screen.getAllByText('More')[1]); + expect(screen.getByText('funding.confirmed')).toBeDefined(); + expect(screen.getByText('9')).toBeDefined(); + }); +}); + +describe('reconciliation findings', () => { + it('warns without claiming the payment failed', () => { + const d = data({ + findings: [ + { + id: 'fnd_1', + kind: 'ASSET_MISMATCH', + severity: 'HIGH', + status: 'OPEN', + detail: 'The chain shows a different asset than the database records.', + paymentId: 'pay_1', + firstDetectedAt: '2026-09-11T10:00:00.000Z', + lastObservedAt: '2026-09-11T11:00:00.000Z', + observationCount: 2, + remediation: 'Compare the escrow token against the configured SAC.', + }, + ], + }); + render(); + + expect(screen.getByText('Payment verification needs attention')).toBeDefined(); + expect(screen.getByText('HIGH')).toBeDefined(); + expect(screen.getByText(/does not by\s+itself mean a payment failed/i)).toBeDefined(); + // Never restated as failure. + expect(screen.queryByText(/failed/i)).not.toBe(screen.getByText('HIGH')); + + // Surfaced against the payment it belongs to. + expect(screen.getAllByText(/needs attention/i).length).toBeGreaterThan(0); + }); +}); + +describe('funding', () => { + it('delegates the funding card to the funding panel for this batch', () => { + render(); + expect(screen.getByTestId('funding-panel').textContent).toBe('bat_1'); + }); +}); + +describe('accessibility', () => { + it('uses labelled sections and an accessible payments table', () => { + render(); + expect(screen.getByRole('heading', { level: 1 }).textContent).toBe('CF-00042'); + expect(screen.getByRole('heading', { name: 'Payments' })).toBeDefined(); + expect(screen.getByRole('heading', { name: 'Approvals' })).toBeDefined(); + expect(screen.getByRole('heading', { name: 'Activity' })).toBeDefined(); + expect(screen.getByRole('table')).toBeDefined(); + expect(screen.getAllByRole('columnheader').length).toBeGreaterThan(4); + }); + + it('marks expandable controls with their state', async () => { + render(); + const toggle = screen.getAllByRole('button', { name: 'Details' })[0]; + expect(toggle.getAttribute('aria-expanded')).toBe('false'); + fireEvent.click(toggle); + await waitFor(() => + expect( + screen.getAllByRole('button', { name: 'Hide' })[0].getAttribute('aria-expanded'), + ).toBe('true'), + ); + }); +}); diff --git a/src/components/reconciliation/ReconciliationPanel.tsx b/src/components/reconciliation/ReconciliationPanel.tsx new file mode 100644 index 0000000..8b127db --- /dev/null +++ b/src/components/reconciliation/ReconciliationPanel.tsx @@ -0,0 +1,386 @@ +'use client'; + +import { FindingSeverity, FindingStatus, RunStatus } from '@prisma/client'; + +/** + * Operational reconciliation view. + * + * ── Two audiences, two vocabularies ────────────────────────────────────────── + * An ordinary finance user needs to know whether they can trust a payment record. + * An operator needs the RPC condition, the finding kind and the transaction. The + * same screen serves both: plain language at the top, detail behind `detailed`. + * + * ── Why "no findings" is not automatically green ────────────────────────────── + * If the last run failed, or never happened, an empty findings list means nothing + * was checked β€” not that everything is correct. Silence is reported as DEGRADED, + * because a reconciler whose last run died is more dangerous than none: the + * absence of findings reads as health. + */ + +export interface RunSummaryView { + id: string; + correlationId: string; + status: RunStatus | string; + startedAt: string; + completedAt: string | null; + paymentsExamined: number; + agreed: number; + mismatched: number; + unreadable: number; + findingsOpened: number; + correctionsApplied: number; + errorMessage: string | null; +} + +export interface FindingView { + id: string; + kind: string; + status: FindingStatus | string; + severity: FindingSeverity | string; + detail: string | null; + remediation: string | null; + dbState: string | null; + chainState: string | null; + escrowOnChainId: number | null; + paymentIndex: number | null; + transaction: { hash: string; explorerUrl: string } | null; + payment: { + id: string; + recipient: string; + amount: string; + assetCode: string; + state: string; + batch: { id: string; reference: string } | null; + } | null; + detectedAt: string; + lastObservedAt: string; + observationCount: number; + acknowledgedBy: string | null; + resolvedBy: string | null; + resolution: string | null; +} + +export interface ReconciliationPanelProps { + health: { + lastRun: RunSummaryView | null; + openFindings: number; + criticalFindings: number; + oldestUnresolvedHours: number | null; + degraded: boolean; + degradedReason?: string; + }; + findings: readonly FindingView[]; + /** Operator mode: infrastructure terms, finding kinds, correlation ids. */ + detailed?: boolean; + onResolve?: (findingId: string) => void; + onAcknowledge?: (findingId: string) => void; + onRunNow?: () => void; + isRunning?: boolean; +} + +const SEVERITY_STYLE: Record = { + CRITICAL: 'border-rose-500/50 bg-rose-500/10 text-rose-300', + HIGH: 'border-orange-500/50 bg-orange-500/10 text-orange-300', + MEDIUM: 'border-amber-500/40 bg-amber-500/10 text-amber-300', + LOW: 'border-slate-600/40 bg-slate-500/10 text-slate-300', +}; + +/** + * Plain-language summaries for non-operators. + * + * Deliberately never reassuring about an unverified payment: a finance user seeing + * "Payment verification delayed" for a DB_PAID_CHAIN_NOT would be misled, so that + * one says plainly that the record cannot be relied on. + */ +const PLAIN_LANGUAGE: Record = { + DB_PAID_CHAIN_NOT: 'This payment is shown as paid but could not be confirmed. Do not rely on it yet.', + FAILED_TX_ACTUALLY_SUCCEEDED: 'This payment may already have gone through. Do not send it again.', + AMOUNT_MISMATCH: 'The amount paid does not match the amount recorded.', + RECIPIENT_MISMATCH: 'The payment reached a different account than recorded.', + ASSET_MISMATCH: 'The payment was made in a different currency than recorded.', + DUPLICATE_PAYMENT_EVENT: 'This worker may have been paid more than once.', + MISSING_PAYMENT_EVENT: 'We could not confirm this payment actually reached the recipient.', + CHAIN_PAID_DB_NOT: 'Payment confirmation is still catching up.', + MISSING_ON_CHAIN: 'This payment record has no matching on-chain payment.', + ORPHAN_ON_CHAIN: 'An on-chain payment is not yet shown in CoreFlow.', + UNKNOWN_ON_CHAIN_OBJECT: 'An escrow on Stellar is not linked to this workspace.', + CHAIN_UNREADABLE: 'Payment verification delayed β€” Stellar could not be reached.', + OTHER: 'This payment needs manual review.', +}; + +function ago(iso: string): string { + const mins = Math.floor((Date.now() - new Date(iso).getTime()) / 60_000); + if (mins < 1) return 'just now'; + if (mins < 60) return `${mins}m ago`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours}h ago`; + return `${Math.floor(hours / 24)}d ago`; +} + +export function ReconciliationPanel({ + health, findings, detailed = false, + onResolve, onAcknowledge, onRunNow, isRunning = false, +}: ReconciliationPanelProps) { + const { lastRun } = health; + const healthy = !health.degraded && health.criticalFindings === 0; + + return ( +
+ {/* ── Status ───────────────────────────────────────────────────── */} +
0 + ? 'border-orange-500/40 bg-orange-500/5' + : 'border-emerald-500/30 bg-emerald-500/5' + }`} + > +
+
+

+ Payment verification +

+

+ {health.degraded + ? // An empty findings list after a failed run means nothing was + // checked, which must never read as "all clear". + health.degradedReason ?? 'Verification is not running normally.' + : health.criticalFindings > 0 + ? `${health.criticalFindings} payment${health.criticalFindings === 1 ? '' : 's'} need urgent attention.` + : 'All payments verified against Stellar.'} +

+
+ + {onRunNow && ( + + )} +
+ + {lastRun ? ( +
+
+
Last check
+
+ {ago(lastRun.startedAt)} + {lastRun.status !== RunStatus.COMPLETED && ( + ({String(lastRun.status).toLowerCase()}) + )} +
+
+
+
Payments checked
+
{lastRun.paymentsExamined}
+
+
+
Verified
+
{lastRun.agreed}
+
+
+
Needs attention
+
{health.openFindings}
+
+ {detailed && ( + <> +
+
Could not check
+
{lastRun.unreadable}
+
+
+
Corrections applied
+
{lastRun.correctionsApplied}
+
+
+
Run id
+
+ {lastRun.correlationId} +
+
+ + )} +
+ ) : ( +

+ No verification has run yet for this workspace. +

+ )} + + {detailed && lastRun?.errorMessage && ( +

+ {lastRun.errorMessage} +

+ )} +
+ + {/* ── Findings ─────────────────────────────────────────────────── */} + {findings.length === 0 ? ( +
+

+ {health.degraded ? 'Nothing to show' : 'No issues found'} +

+

+ {health.degraded + ? 'Verification has not completed, so no payments were checked.' + : 'Every payment matches its on-chain settlement.'} +

+
+ ) : ( +
    + {findings.map((f) => ( +
  • +
    +
    +

    + {String(f.severity)} + {detailed && ( + + {f.kind} + + )} +

    +

    + {PLAIN_LANGUAGE[f.kind] ?? f.detail ?? 'Needs review.'} +

    +
    + + {String(f.status)} + +
    + + {f.payment && ( +
    +
    +
    Payment
    +
    + {f.payment.batch?.reference ?? f.payment.id.slice(0, 10)} +
    +
    +
    +
    Recipient
    +
    + {f.payment.recipient.slice(0, 6)}…{f.payment.recipient.slice(-4)} +
    +
    +
    +
    Amount
    +
    + {f.payment.amount} {f.payment.assetCode} +
    +
    +
    +
    CoreFlow says
    +
    {f.payment.state}
    +
    +
    + )} + + {detailed && ( +
    + {f.dbState && ( +
    +
    Database
    +
    {f.dbState}
    +
    + )} + {f.chainState && ( +
    +
    Chain
    +
    {f.chainState}
    +
    + )} + {f.escrowOnChainId !== null && ( +
    +
    Escrow
    +
    + #{f.escrowOnChainId} + {f.paymentIndex !== null && ` Β· slot ${f.paymentIndex}`} +
    +
    + )} + {f.transaction && ( + + )} +
    +
    First seen
    +
    + {ago(f.detectedAt)} + {f.observationCount > 1 && ( + + {' '}Β· still present after {f.observationCount} checks + + )} +
    +
    +
    + )} + + {f.remediation && ( +

    + What to do: + {f.remediation} +

    + )} + + {f.resolution && ( +

    + Resolved + {f.resolvedBy && ` by ${f.resolvedBy.slice(0, 6)}…`}: {f.resolution} +

    + )} + + {(onAcknowledge || onResolve) && f.status !== FindingStatus.RESOLVED && ( +
    + {onAcknowledge && f.status === FindingStatus.OPEN && ( + + )} + {onResolve && ( + + )} +
    + )} +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/components/reconciliation/__tests__/ReconciliationPanel.test.tsx b/src/components/reconciliation/__tests__/ReconciliationPanel.test.tsx new file mode 100644 index 0000000..a400ca3 --- /dev/null +++ b/src/components/reconciliation/__tests__/ReconciliationPanel.test.tsx @@ -0,0 +1,175 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { FindingSeverity, FindingStatus, RunStatus } from '@prisma/client'; +import { ReconciliationPanel, type FindingView } from '../ReconciliationPanel'; + +const run = (over: Record = {}) => ({ + id: 'r1', correlationId: 'rec_abc', status: RunStatus.COMPLETED, + startedAt: new Date(Date.now() - 5 * 60_000).toISOString(), + completedAt: new Date().toISOString(), + paymentsExamined: 146, agreed: 143, mismatched: 3, unreadable: 0, + findingsOpened: 3, correctionsApplied: 0, errorMessage: null, + ...over, +}); + +const finding = (over: Partial = {}): FindingView => ({ + id: 'f1', kind: 'DB_PAID_CHAIN_NOT', status: FindingStatus.OPEN, + severity: FindingSeverity.CRITICAL, + detail: 'db says paid, chain does not', + remediation: 'Do not rely on the payment record. Verify on the explorer.', + dbState: 'PAID', chainState: 'no confirmed settlement', + escrowOnChainId: 7, paymentIndex: 0, + transaction: { hash: 'abcdef1234567890', explorerUrl: 'https://explorer/tx/abcdef1234567890' }, + payment: { + id: 'pay1', recipient: 'G' + 'W'.repeat(55), amount: '1,000.00', + assetCode: 'USDC', state: 'PAID', + batch: { id: 'b1', reference: 'CF-00042' }, + }, + detectedAt: new Date(Date.now() - 3 * 3_600_000).toISOString(), + lastObservedAt: new Date().toISOString(), + observationCount: 4, + acknowledgedBy: null, resolvedBy: null, resolution: null, + ...over, +}); + +const health = (over: Record = {}) => ({ + lastRun: run(), openFindings: 3, criticalFindings: 1, + oldestUnresolvedHours: 3, degraded: false, + ...over, +}); + +describe('operational summary', () => { + it('shows the last check and the counts an operator asks for', () => { + render(); + expect(screen.getByText('146')).toBeInTheDocument(); + expect(screen.getByText('143')).toBeInTheDocument(); + expect(screen.getByText(/Last check/i)).toBeInTheDocument(); + }); + + it('reports DEGRADED when reconciliation has never run', () => { + render( + + ); + expect(screen.getByText(/never run/i)).toBeInTheDocument(); + // "No issues" must NOT read as all-clear when nothing was checked. + expect(screen.getByText(/has not completed, so no payments were checked/i)).toBeInTheDocument(); + expect(screen.queryByText(/All payments verified/i)).toBeNull(); + }); + + it('does not claim all-clear after a failed run', () => { + render( + + ); + expect(screen.getByText(/last reconciliation run failed/i)).toBeInTheDocument(); + // Surfaced twice on purpose: in the status line a finance user reads, and in + // the raw error block an operator needs. + expect(screen.getAllByText(/rpc timeout/i).length).toBeGreaterThanOrEqual(2); + expect(screen.queryByText(/All payments verified/i)).toBeNull(); + }); + + it('says all-clear only when healthy with no critical findings', () => { + render( + + ); + expect(screen.getByText(/All payments verified against Stellar/i)).toBeInTheDocument(); + }); +}); + +describe('two audiences', () => { + it('gives a finance user plain language, not infrastructure terms', () => { + render(); + expect(screen.getByText(/could not be confirmed. Do not rely on it yet/i)).toBeInTheDocument(); + // Operator detail is withheld. + expect(screen.queryByText('DB_PAID_CHAIN_NOT')).toBeNull(); + expect(screen.queryByText(/rec_abc/)).toBeNull(); + }); + + it('gives an operator the kind, both states, the escrow slot and the transaction', () => { + render(); + expect(screen.getByText('DB_PAID_CHAIN_NOT')).toBeInTheDocument(); + expect(screen.getByText('no confirmed settlement')).toBeInTheDocument(); + expect(screen.getByText(/#7 Β· slot 0/)).toBeInTheDocument(); + expect(screen.getByRole('link')).toHaveAttribute('href', 'https://explorer/tx/abcdef1234567890'); + expect(screen.getByText(/rec_abc/)).toBeInTheDocument(); + }); + + it('never reassures a finance user about an unverified payment', () => { + // "Verification delayed" for a DB_PAID_CHAIN_NOT would be misleading. + render(); + expect(screen.queryByText(/verification delayed/i)).toBeNull(); + }); + + it('does use softer language for a genuine infrastructure delay', () => { + render( + + ); + expect(screen.getByText(/verification delayed/i)).toBeInTheDocument(); + }); +}); + +describe('findings', () => { + it('always shows what to do', () => { + render(); + expect(screen.getByText(/What to do:/i)).toBeInTheDocument(); + expect(screen.getByText(/Verify on the explorer/i)).toBeInTheDocument(); + }); + + it('shows how long a finding has persisted', () => { + render(); + expect(screen.getByText(/still present after 4 checks/i)).toBeInTheDocument(); + }); + + it('renders the payment identity so the issue is actionable', () => { + render(); + expect(screen.getByText('CF-00042')).toBeInTheDocument(); + expect(screen.getByText(/1,000.00 USDC/)).toBeInTheDocument(); + }); + + it('offers acknowledge and resolve only on unresolved findings', () => { + const onResolve = vi.fn(); + const onAcknowledge = vi.fn(); + const { unmount } = render( + + ); + expect(screen.getByRole('button', { name: /Acknowledge/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Resolve/i })).toBeInTheDocument(); + unmount(); + + render( + + ); + expect(screen.queryByRole('button', { name: /Acknowledge/i })).toBeNull(); + expect(screen.getByText(/Confirmed settled/i)).toBeInTheDocument(); + }); + + it('does not hide a problem behind a generic banner', () => { + render(); + expect(screen.queryByText(/^Something went wrong$/i)).toBeNull(); + expect(screen.queryByText(/^An error occurred$/i)).toBeNull(); + expect(screen.getByText('CRITICAL')).toBeInTheDocument(); + }); +}); diff --git a/src/hooks/useDashboard.ts b/src/hooks/useDashboard.ts index 3b9cd6d..3705959 100644 --- a/src/hooks/useDashboard.ts +++ b/src/hooks/useDashboard.ts @@ -3,6 +3,7 @@ import { EscrowData } from '@/components/EscrowCard'; import { Transaction } from '@/components/TransactionFeed'; import { CoreFlowClient } from '@/lib/contracts'; import { STELLAR_CONFIG } from '@/lib/config'; +import { SAC_DECIMALS, formatAmountWithSeparators } from '@/lib/money'; interface UseDashboardProps { isAuthenticated: boolean; @@ -182,16 +183,13 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro const errorMsg = err instanceof Error ? err.message : 'Blockchain transaction failed'; console.error(`[Reconciliation] Blockchain transaction failed for Escrow #${escrowId}:`, err); - // Rollback DB status to ensure DB stays synchronized with chain - try { - await fetch(`/api/escrows/${escrowId}/status`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: originalStatus }), - }); - } catch (e) { - console.error('Rollback sync failed:', e); - } + // No DB rollback write. + // + // There is nothing to roll back: the client never advanced the stored state in + // the first place. And "the submission threw" does not establish what the chain + // did β€” an RPC timeout can accompany a transaction that landed. Reverting the + // record here could mark a settled payment as unsettled, which is a false + // statement about money. Reconciliation resolves it against chain state. // Report error for audit try { @@ -229,15 +227,14 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro return; } - try { - await fetch(`/api/escrows/${escrowId}/status`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ managerApproved: true, status: 'pending_finance' }), - }); - } catch (e) { - console.error('Off-chain sync failed:', e); - } + // No off-chain status write here, deliberately. + // + // The transaction above was submitted; whether the CHAIN accepted it is a + // separate question, answered by the indexer observing the contract's event + // log. Writing the expected status from the client would assert an outcome + // nobody has confirmed β€” the precise failure mode the payment state machine + // exists to prevent. The dashboard refreshes below and advances when the + // indexer catches up. setTransactions((prev) => [ { @@ -298,15 +295,14 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro return; } - try { - await fetch(`/api/escrows/${escrowId}/status`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ financeApproved: true, status: 'ready' }), - }); - } catch (e) { - console.error('Off-chain sync failed:', e); - } + // No off-chain status write here, deliberately. + // + // The transaction above was submitted; whether the CHAIN accepted it is a + // separate question, answered by the indexer observing the contract's event + // log. Writing the expected status from the client would assert an outcome + // nobody has confirmed β€” the precise failure mode the payment state machine + // exists to prevent. The dashboard refreshes below and advances when the + // indexer catches up. setTransactions((prev) => [ { @@ -369,15 +365,14 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro return; } - try { - await fetch(`/api/escrows/${escrowId}/status`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: 'paid' }), - }); - } catch (e) { - console.error('Off-chain sync failed:', e); - } + // No off-chain status write here, deliberately. + // + // The transaction above was submitted; whether the CHAIN accepted it is a + // separate question, answered by the indexer observing the contract's event + // log. Writing the expected status from the client would assert an outcome + // nobody has confirmed β€” the precise failure mode the payment state machine + // exists to prevent. The dashboard refreshes below and advances when the + // indexer catches up. setTransactions((prev) => [ { @@ -441,15 +436,14 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro return; } - try { - await fetch(`/api/escrows/${escrowId}/status`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: 'cancelled' }), - }); - } catch (e) { - console.error('Off-chain sync failed:', e); - } + // No off-chain status write here, deliberately. + // + // The transaction above was submitted; whether the CHAIN accepted it is a + // separate question, answered by the indexer observing the contract's event + // log. Writing the expected status from the client would assert an outcome + // nobody has confirmed β€” the precise failure mode the payment state machine + // exists to prevent. The dashboard refreshes below and advances when the + // indexer catches up. setTransactions((prev) => [ { @@ -526,7 +520,19 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro } }; - const handleCreateEscrow = async (workerPubKey: string, amountCents: number, rateCents: number) => { + /** + * Create and fund an escrow on-chain. + * + * Amounts arrive as base units (bigint) from the modal, NOT as + * dollars-times-100. Passing "cents" here used to under-fund every escrow by + * 100,000x, because Stellar assets carry seven decimals β€” see lib/money. + */ + const handleCreateEscrow = async ( + workerPubKey: string, + financeApprover: string, + amountUnits: bigint, + rateUnits: bigint + ) => { if (!isConnected) { setError('Please connect Freighter wallet first'); return; @@ -546,6 +552,22 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro `Unsupported address type: "${workerPubKey}". In live on-chain mode, the worker address must be a valid 56-character Stellar public key (starting with 'G') or Contract ID (starting with 'C').` ); } + if (!/^[GC][A-Z2-7]{55}$/.test(financeApprover)) { + throw new Error( + `Finance approver "${financeApprover}" is not a valid Stellar address.` + ); + } + // Separation of duties is the product's core claim, and the contract + // enforces it with SignersNotDistinct (#15). The dashboard previously + // passed the connected wallet as BOTH manager and finance approver, so + // this call trapped on every attempt and the dual-approval flow had no + // working path at all. + if (financeApprover === walletAddress) { + throw new Error( + 'The finance approver must be a different wallet from the manager. ' + + 'CoreFlow requires two distinct signers before funds can move.' + ); + } const tokenAddress = STELLAR_CONFIG.token.id; if (!tokenAddress) { @@ -560,10 +582,10 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro // Per-payee asset. This single-escrow path uses the configured // default SAC; the Bulk Pay CSV flow sets it per row. token: tokenAddress, - amount: BigInt(amountCents), + amount: amountUnits, start_date: Math.floor(Date.now() / 1000), end_date: Math.floor(Date.now() / 1000) + 86400 * 7, - rate_per_hour: BigInt(rateCents), + rate_per_hour: rateUnits, } ]; @@ -572,7 +594,12 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro throw new Error('Oracle is not configured; cannot create a verifiable escrow.'); } const { pubkey: oraclePubkey } = await pubkeyRes.json(); - const txResult = await client.submitInitializeEscrow(walletAddress, walletAddress, oraclePubkey, payload); + const txResult = await client.submitInitializeEscrow( + walletAddress, + financeApprover, + oraclePubkey, + payload + ); try { await fetch('/api/escrows', { @@ -581,8 +608,12 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro body: JSON.stringify({ onChainId: txResult.returnValue || 0, workerPubKey, - amountCents, - rateCents, + // Base units as a string: JSON has no bigint, and the value can + // exceed Number.MAX_SAFE_INTEGER for large batches. + amountBaseUnits: amountUnits.toString(), + rateBaseUnits: rateUnits.toString(), + assetDecimals: SAC_DECIMALS, + financeApprover, tokenAddress, }), }); @@ -609,7 +640,7 @@ export function useDashboard({ isAuthenticated, walletAddress }: UseDashboardPro const newEsc: EscrowData = { id: newId, worker: workerPubKey.length >= 10 ? workerPubKey.slice(0, 6) + '...' + workerPubKey.slice(-4) : workerPubKey, - amount: (amountCents / 100).toLocaleString(), + amount: formatAmountWithSeparators(amountUnits, SAC_DECIMALS), currency: 'USDC', hoursLogged: '0', status: 'pending_hours', diff --git a/src/lib/__tests__/money.test.ts b/src/lib/__tests__/money.test.ts new file mode 100644 index 0000000..f4148cb --- /dev/null +++ b/src/lib/__tests__/money.test.ts @@ -0,0 +1,115 @@ +// @vitest-environment node +/** + * Money conversion tests. + * + * These pin the fix for the cents/stroops defect: the dashboard collected + * dollars, multiplied by 100, and sent the result on-chain as if base units + * were cents. Stellar uses 7 decimals, so every amount settled 100,000Γ— short + * while the UI reported success. + */ +import { describe, it, expect } from 'vitest'; +import { + parseAmount, + formatAmount, + formatAmountWithSeparators, + sumAmounts, + hoursForAmount, + MoneyParseError, + SAC_DECIMALS, +} from '../money'; + +describe('parseAmount', () => { + it('converts dollars to Stellar base units, not to cents', () => { + // The regression itself: $250.50 must fund 2_505_000_000 base units. + // The old code produced 25_050 β€” 0.0025050 USDC. + expect(parseAmount('250.50', SAC_DECIMALS)).toBe(2_505_000_000n); + expect(parseAmount('250.50', SAC_DECIMALS)).not.toBe(25_050n); + }); + + it('handles whole numbers and the smallest representable unit', () => { + expect(parseAmount('1', SAC_DECIMALS)).toBe(10_000_000n); + expect(parseAmount('0.0000001', SAC_DECIMALS)).toBe(1n); + expect(parseAmount('0', SAC_DECIMALS)).toBe(0n); + }); + + it('is exact where floating point is not', () => { + // parseFloat('0.1') * 10 ** 7 is 1000000.0000000001 in IEEE-754. + expect(parseAmount('0.1', SAC_DECIMALS)).toBe(1_000_000n); + expect(parseAmount('8420.29', SAC_DECIMALS)).toBe(84_202_900_000n); + expect(parseAmount('0.07', SAC_DECIMALS)).toBe(700_000n); + }); + + it('carries amounts far past the old 32-bit ceiling', () => { + // The previous Int column overflowed at $21,474,836.47. + expect(parseAmount('100000000.00', SAC_DECIMALS)).toBe(1_000_000_000_000_000n); + }); + + it('accepts thousands separators from pasted input', () => { + expect(parseAmount('8,420.00', SAC_DECIMALS)).toBe(84_200_000_000n); + }); + + it('rejects more precision than the asset can represent', () => { + // Rounding here would quietly change a payroll figure. + expect(() => parseAmount('1.00000001', SAC_DECIMALS)).toThrow(MoneyParseError); + }); + + it.each(['', 'abc', '1.2.3', '1e5', '$250', ' '])( + 'rejects malformed input %j', + (bad) => { + expect(() => parseAmount(bad, SAC_DECIMALS)).toThrow(MoneyParseError); + } + ); + + it('round-trips through formatAmount', () => { + for (const v of ['0.50', '1.00', '250.50', '8420.29', '999999.99']) { + expect(formatAmount(parseAmount(v, SAC_DECIMALS), SAC_DECIMALS)).toBe(v); + } + }); +}); + +describe('formatAmount', () => { + it('renders base units exactly', () => { + expect(formatAmount(2_505_000_000n, SAC_DECIMALS)).toBe('250.50'); + expect(formatAmount(1n, SAC_DECIMALS)).toBe('0.0000001'); + expect(formatAmount(0n, SAC_DECIMALS)).toBe('0.00'); + }); + + it('groups thousands', () => { + expect(formatAmountWithSeparators(84_202_900_000n, SAC_DECIMALS)).toBe('8,420.29'); + expect(formatAmountWithSeparators(10_000_000_000_000n, SAC_DECIMALS)).toBe('1,000,000.00'); + }); + + it('handles negatives', () => { + expect(formatAmount(-2_505_000_000n, SAC_DECIMALS)).toBe('-250.50'); + }); +}); + +describe('sumAmounts', () => { + it('sums a batch without overflow', () => { + const batch = Array.from({ length: 12 }, () => parseAmount('8420.29', SAC_DECIMALS)); + expect(sumAmounts(batch)).toBe(84_202_900_000n * 12n); + }); + + it('returns zero for an empty batch', () => { + expect(sumAmounts([])).toBe(0n); + }); +}); + +describe('hoursForAmount', () => { + it('returns whole hours when the amount divides evenly', () => { + // 40 h at $25/h = $1000 + const rate = parseAmount('25', SAC_DECIMALS); + expect(hoursForAmount(parseAmount('1000', SAC_DECIMALS), rate)).toBe(40n); + }); + + it('returns null when no whole number of hours reaches the amount', () => { + // The contract would reject this with AmountHoursMismatch (#17), funding + // custody into an escrow that can never settle. + const rate = parseAmount('25', SAC_DECIMALS); + expect(hoursForAmount(parseAmount('1000.01', SAC_DECIMALS), rate)).toBeNull(); + }); + + it('returns null for a non-positive rate', () => { + expect(hoursForAmount(1000n, 0n)).toBeNull(); + }); +}); diff --git a/src/lib/api/errors.ts b/src/lib/api/errors.ts new file mode 100644 index 0000000..17f94e0 --- /dev/null +++ b/src/lib/api/errors.ts @@ -0,0 +1,184 @@ +/** + * One error shape for every route, and one place that decides what a caller is + * allowed to learn. + * + * Two rules drive the design: + * + * 1. NOTHING INTERNAL CROSSES THE BOUNDARY. A Prisma error names constraints, + * columns and ids; a stack trace names file paths; an RPC error can carry a + * URL with credentials. All of it is logged and none of it is returned. + * + * 2. A MISS DOES NOT CONFIRM EXISTENCE. A payment in another organization must be + * indistinguishable from one that never existed, or the 403/404 split becomes + * an enumeration oracle for a competitor's payroll. 403 is therefore only for + * resources the caller already demonstrably knows about β€” typically their own + * organization, where the question is permission rather than visibility. + */ + +import { NextResponse } from 'next/server'; + +/** + * Status taxonomy. Each has one meaning, so a client can branch on it. + * + * | Status | Meaning | + * |--------|----------------------------------------------------------------| + * | 400 | The request itself is malformed β€” bad JSON, wrong content type | + * | 401 | Not authenticated | + * | 403 | Authenticated, known resource, insufficient permission | + * | 404 | Not found OR not visible to this tenant. Deliberately the same | + * | 409 | Understood and well-formed, but conflicts with current state | + * | 422 | Well-formed request whose CONTENT fails domain validation | + * | 429 | Rate limited | + * | 500 | Unexpected server failure. Never carries detail | + * | 503 | A dependency (database, RPC) is unavailable | + */ +export type ApiErrorStatus = 400 | 401 | 403 | 404 | 409 | 422 | 429 | 500 | 503; + +export type ApiErrorCode = + // 400 + | 'MALFORMED_REQUEST' + | 'UNSUPPORTED_CONTENT_TYPE' + | 'PAYLOAD_TOO_LARGE' + // 401 / 403 / 404 + | 'UNAUTHENTICATED' + | 'FORBIDDEN' + | 'NOT_FOUND' + // 409 + | 'STATE_CONFLICT' + | 'REFERENCE_TAKEN' + | 'REFERENCE_EXHAUSTED' + | 'IDEMPOTENCY_KEY_REUSED' + | 'DUPLICATE_APPROVAL' + | 'APPROVER_NOT_DISTINCT' + // 422 + | 'VALIDATION_FAILED' + | 'CSV_INVALID' + | 'ASSET_NOT_SETTLEABLE' + | 'SETTLEMENT_ASSET_UNCONFIGURED' + // 429 / 500 / 503 + | 'RATE_LIMITED' + | 'INTERNAL_ERROR' + | 'DEPENDENCY_UNAVAILABLE'; + +export interface ApiErrorBody { + error: string; + code: ApiErrorCode; + /** + * Structured, caller-actionable specifics β€” field paths, row numbers. Only + * ever data the caller supplied, echoed back so they can fix it. Never server + * state, and never populated for 500. + */ + details?: unknown; +} + +/** + * A failure a route is deliberately reporting. + * + * Thrown rather than returned where it is raised deep in a helper; `toResponse` + * renders it. Anything NOT an ApiError reaching the boundary is, by definition, + * unanticipated, and becomes an opaque 500. + */ +export class ApiError extends Error { + constructor( + readonly status: ApiErrorStatus, + readonly code: ApiErrorCode, + message: string, + readonly details?: unknown, + ) { + super(message); + this.name = 'ApiError'; + } +} + +/** The JSON envelope. Shared with the existing `{ error, code }` routes. */ +export function errorResponse( + status: ApiErrorStatus, + code: ApiErrorCode, + message: string, + details?: unknown, +): NextResponse { + return NextResponse.json( + { error: message, code, ...(details === undefined ? {} : { details }) }, + { status }, + ); +} + +export function apiErrorResponse(e: ApiError): NextResponse { + return errorResponse(e.status, e.code, e.message, e.details); +} + +/** + * A resource that is absent, or that belongs to another tenant. + * + * One message for both, on purpose. "You may not access batch X" tells the caller + * batch X exists. + */ +export function notFound(what: string): NextResponse { + return errorResponse(404, 'NOT_FOUND', `${what} not found.`); +} + +/** + * The last line before the client. + * + * Logs the real failure server-side and returns an opaque 500. Deliberately has + * no "include details in development" switch: a conditional that reveals + * internals is one misconfigured environment variable away from revealing them + * in production. + */ +export function internalError(context: string, e: unknown): NextResponse { + const message = e instanceof Error ? e.message : String(e); + console.error(`[${context}] ${message}`); + return errorResponse( + 500, + 'INTERNAL_ERROR', + 'The request could not be completed. If this persists, contact support with the time of the request.', + ); +} + +/** + * Render any thrown value as a response. + * + * ApiError keeps its status and detail; everything else becomes an opaque 500. + * Unknown failure modes therefore fail CLOSED β€” silent about their cause β€” rather + * than leaking whatever a library happened to put in `.message`. + */ +export function handleRouteError(context: string, e: unknown): NextResponse { + if (e instanceof ApiError) return apiErrorResponse(e); + return internalError(context, e); +} + +/** + * Reject a body that is not JSON before trying to read it. + * + * A form post or an uploaded file reaching a JSON route is a client mistake worth + * naming, not something to coerce into `{}` and then report as a missing field. + */ +export function assertJsonContentType(request: Request): void { + const header = request.headers.get('content-type') ?? ''; + const type = header.split(';')[0].trim().toLowerCase(); + if (type !== 'application/json') { + throw new ApiError( + 400, + 'UNSUPPORTED_CONTENT_TYPE', + `This endpoint accepts application/json. Received ${type || '(none)'}.`, + ); + } +} + +/** + * Refuse an oversized body up front, using the declared length. + * + * Advisory only β€” Content-Length can lie or be absent β€” so the real limit is + * still enforced after parsing. This exists to reject the honest 50 MB upload + * before it is buffered, not to be a security boundary. + */ +export function assertDeclaredSizeWithin(request: Request, maxBytes: number): void { + const declared = Number(request.headers.get('content-length') ?? ''); + if (Number.isFinite(declared) && declared > maxBytes) { + throw new ApiError( + 400, + 'PAYLOAD_TOO_LARGE', + `Request body is ${Math.round(declared / 1024)} KB; the limit is ${Math.round(maxBytes / 1024)} KB.`, + ); + } +} diff --git a/src/lib/config.ts b/src/lib/config.ts index 5520583..e855fb2 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -7,6 +7,21 @@ import { isConnected, requestAccess, signTransaction, signMessage } from '@stellar/freighter-api'; +export type StellarNetwork = 'testnet' | 'public'; + +/** + * Resolve the configured network, refusing to guess. + * + * An empty or unrecognised value defaults to `testnet`: if the operator has + * not said which chain they mean, the safe reading is the one where nothing of + * value can move. Choosing mainnet requires saying so explicitly. + */ +function normalizeNetwork(raw: string | undefined): StellarNetwork { + const v = raw?.trim().toLowerCase(); + if (v === 'public' || v === 'mainnet') return 'public'; + return 'testnet'; +} + export const STELLAR_CONFIG = { // Network configuration network: { @@ -25,12 +40,22 @@ export const STELLAR_CONFIG = { }, // Smart contract configuration + // + // FAIL-CLOSED, DELIBERATELY. This used to default to a hard-coded MAINNET + // contract address when NEXT_PUBLIC_STELLAR_CONTRACT_ID was unset, while the + // network separately defaulted to 'testnet'. Two consequences, both bad: + // + // 1. Production shipped with both variables set to "" (falsy), so the live + // app aimed a mainnet contract ID at testnet RPC. Every call failed. + // 2. Any developer running locally without an env file was pointed at the + // real mainnet contract. + // + // An unset contract ID is now a loud error at the point of use rather than a + // silent guess. For a system that moves money, refusing to act beats acting + // on an assumption about which chain you meant. contract: { - // Replace with deployed contract ID - id: process.env.NEXT_PUBLIC_STELLAR_CONTRACT_ID || 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW', - - // Network selection - network: (process.env.NEXT_PUBLIC_STELLAR_NETWORK as 'testnet' | 'public') || 'testnet', + id: process.env.NEXT_PUBLIC_STELLAR_CONTRACT_ID?.trim() || '', + network: normalizeNetwork(process.env.NEXT_PUBLIC_STELLAR_NETWORK), }, // Settlement token (Stellar Asset Contract address, e.g. USDC SAC). @@ -58,6 +83,32 @@ export const STELLAR_CONFIG = { }, // RPC endpoint helpers + /** True when a contract address is configured for the selected network. */ + isConfigured: () => STELLAR_CONFIG.contract.id.length > 0, + + /** + * The contract address, or a clear failure. Never a fallback: a wrong + * address on the wrong network is worse than an unusable page. + */ + requireContractId: () => { + const id = STELLAR_CONFIG.contract.id; + if (!id) { + throw new Error( + 'NEXT_PUBLIC_STELLAR_CONTRACT_ID is not set. CoreFlow will not guess a ' + + 'contract address β€” set it to the CoreFlow contract deployed on ' + + `${STELLAR_CONFIG.contract.network === 'public' ? 'Mainnet' : 'Testnet'}.` + ); + } + return id; + }, + + /** Human label for the active network, shown in the UI. */ + networkLabel: () => + STELLAR_CONFIG.contract.network === 'public' ? 'Stellar Mainnet' : 'Stellar Testnet', + + /** True when the app is pointed at a network where funds are real. */ + isMainnet: () => STELLAR_CONFIG.contract.network === 'public', + getRpcUrl: () => { const network = STELLAR_CONFIG.contract.network; return STELLAR_CONFIG.network[network].rpcUrl; diff --git a/src/lib/contracts.ts b/src/lib/contracts.ts index 1dc5951..08a85d7 100644 --- a/src/lib/contracts.ts +++ b/src/lib/contracts.ts @@ -57,7 +57,8 @@ export class CoreFlowClient { private networkPassphrase: string; constructor() { - this.contractAddress = STELLAR_CONFIG.contract.id; + // Throws when unset rather than defaulting to a mainnet address. + this.contractAddress = STELLAR_CONFIG.requireContractId(); this.networkPassphrase = STELLAR_CONFIG.getNetworkPassphrase(); } @@ -87,7 +88,20 @@ export class CoreFlowClient { /** * Helper to build, simulate, sign via Freighter, and submit a transaction */ - private async submitTransaction(method: string, args: any[]): Promise { + /** + * Invoke a contract method: simulate, sign with Freighter, submit, then poll. + * + * `onSubmitted` fires the INSTANT the network accepts the transaction, before + * polling begins. That matters for funding: `initialize_multi_sig_escrow` moves + * custody and is not idempotent, so if polling then fails, the caller must still + * know the hash β€” otherwise the only record of a transaction that may already + * have moved money is lost, and the obvious recovery is to sign a second one. + */ + private async submitTransaction( + method: string, + args: any[], + onSubmitted?: (hash: string) => void | Promise + ): Promise { try { const sdk = await this.loadSDK(); const signingAddress = await this.getSigningAddress(); @@ -115,6 +129,17 @@ export class CoreFlowClient { ); if (response.status === 'PENDING') { + // Report the hash before waiting on confirmation. A failure inside the + // callback must not lose the hash either, so it is isolated. + if (onSubmitted) { + try { + await onSubmitted(response.hash); + } catch { + // The caller's bookkeeping failed; the transaction is still in flight + // and the hash is still returned below. + } + } + const resultStatus = await this.pollForResult(rpcClient, response.hash); // Fetch transaction details to parse return value if needed @@ -270,7 +295,9 @@ export class CoreFlowClient { managerAddress: string, financeAddress: string, oraclePubkeyHex: string, - payments: PaymentScheduleInput[] + payments: PaymentScheduleInput[], + /** Called as soon as the network accepts the transaction. See submitTransaction. */ + onSubmitted?: (hash: string) => void | Promise ): Promise { const sdk = await this.loadSDK(); @@ -300,12 +327,11 @@ export class CoreFlowClient { const oraclePubkeyScVal = sdk.nativeToScVal(oracleBytes, { type: 'bytes' }); const paymentsScVal = sdk.nativeToScVal(mappedPayments); - return this.submitTransaction('initialize_multi_sig_escrow', [ - managerScVal, - financeScVal, - oraclePubkeyScVal, - paymentsScVal, - ]); + return this.submitTransaction( + 'initialize_multi_sig_escrow', + [managerScVal, financeScVal, oraclePubkeyScVal, paymentsScVal], + onSubmitted + ); } /** @@ -391,6 +417,117 @@ export class CoreFlowClient { ]); } + /** + * Ask the contract for the exact bytes the oracle must sign (read-only). + * + * The contract is the single source of truth for the attestation preimage. + * A signer that reads it here cannot drift from the verifier, which is the + * failure mode every reimplementation of a message format eventually hits. + */ + async getProofPreimage( + escrowId: number, + paymentId: number, + hours: bigint, + nonce: bigint + ): Promise { + const sdk = await this.loadSDK(); + const readAddress = STELLAR_CONFIG.addresses.readAddress; + if (!readAddress) { + throw new Error('NEXT_PUBLIC_STELLAR_READ_ADDRESS not configured'); + } + + const rpcClient = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + const contract = new sdk.Contract(this.contractAddress); + const sourceAccount = await rpcClient.getAccount(readAddress); + + const transaction = new sdk.TransactionBuilder(sourceAccount, { + fee: sdk.BASE_FEE, + networkPassphrase: this.networkPassphrase, + }) + .addOperation( + contract.call( + 'proof_preimage', + sdk.nativeToScVal(escrowId, { type: 'u32' }), + sdk.nativeToScVal(paymentId, { type: 'u32' }), + sdk.nativeToScVal(hours, { type: 'i128' }), + sdk.nativeToScVal(nonce, { type: 'u64' }) + ) + ) + .setTimeout(300) + .build(); + + const simulated = await rpcClient.simulateTransaction(transaction); + if (sdk.rpc.Api.isSimulationError(simulated)) { + throw new Error(`Failed to read proof preimage: ${simulated.error}`); + } + if (!simulated.result?.retval) { + throw new Error('No preimage returned from simulation'); + } + return Buffer.from(sdk.scValToNative(simulated.result.retval)); + } + + /** + * Register an oracle signing key as platform-trusted (contract admin only). + * + * Escrows may only name a registered key, so a manager cannot install their + * own oracle and attest to their own work. + */ + async submitRegisterOracleKey(pubkeyHex: string): Promise { + const sdk = await this.loadSDK(); + const bytes = Buffer.from(pubkeyHex, 'hex'); + if (bytes.length !== 32) { + throw new Error(`Oracle public key must be exactly 32 bytes (got ${bytes.length})`); + } + return this.submitTransaction('register_oracle_key', [ + sdk.nativeToScVal(bytes, { type: 'bytes' }), + ]); + } + + /** Revoke a registered oracle key (contract admin only). */ + async submitRevokeOracleKey(pubkeyHex: string): Promise { + const sdk = await this.loadSDK(); + const bytes = Buffer.from(pubkeyHex, 'hex'); + if (bytes.length !== 32) { + throw new Error(`Oracle public key must be exactly 32 bytes (got ${bytes.length})`); + } + return this.submitTransaction('revoke_oracle_key', [ + sdk.nativeToScVal(bytes, { type: 'bytes' }), + ]); + } + + /** True if the contract admin has registered this oracle key (read-only). */ + async isOracleKeyRegistered(pubkeyHex: string): Promise { + const sdk = await this.loadSDK(); + const readAddress = STELLAR_CONFIG.addresses.readAddress; + if (!readAddress) { + throw new Error('NEXT_PUBLIC_STELLAR_READ_ADDRESS not configured'); + } + const bytes = Buffer.from(pubkeyHex, 'hex'); + if (bytes.length !== 32) { + throw new Error(`Oracle public key must be exactly 32 bytes (got ${bytes.length})`); + } + + const rpcClient = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + const contract = new sdk.Contract(this.contractAddress); + const sourceAccount = await rpcClient.getAccount(readAddress); + + const transaction = new sdk.TransactionBuilder(sourceAccount, { + fee: sdk.BASE_FEE, + networkPassphrase: this.networkPassphrase, + }) + .addOperation( + contract.call('is_oracle_key_registered', sdk.nativeToScVal(bytes, { type: 'bytes' })) + ) + .setTimeout(300) + .build(); + + const simulated = await rpcClient.simulateTransaction(transaction); + if (sdk.rpc.Api.isSimulationError(simulated)) { + throw new Error(`Failed to read oracle registry: ${simulated.error}`); + } + return Boolean(sdk.scValToNative(simulated.result!.retval)); + } + /** * Submit escrow cancellation */ diff --git a/src/lib/db/__tests__/constraints.integration.test.ts b/src/lib/db/__tests__/constraints.integration.test.ts new file mode 100644 index 0000000..5a0875e --- /dev/null +++ b/src/lib/db/__tests__/constraints.integration.test.ts @@ -0,0 +1,702 @@ +/** + * What PostgreSQL itself enforces. + * + * CoreFlow puts part of its security model in the database: composite tenant + * foreign keys, unique idempotency indexes, a partial unique index for run + * locking, bigint money columns. None of that is provable in TypeScript β€” a test + * double enforces whatever its author taught it, and the two drift silently. + * + * These tests therefore make the assertions that only a real database can settle, + * including direct reproductions of two defects that survived the unit suite. + */ + +import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest'; +import { PrismaClient, OrgRole, PaymentState, ApprovalDecision, RunStatus } from '@prisma/client'; +import { + assertLocalDatabase, + resetDatabase, + seedOrganization, + payeeWallet, + type SeededOrg, +} from './helpers'; + +assertLocalDatabase(); + +const prisma = new PrismaClient(); + +let orgA: SeededOrg; +let orgB: SeededOrg; + +beforeAll(async () => { + await prisma.$connect(); +}); + +afterAll(async () => { + await prisma.$disconnect(); +}); + +beforeEach(async () => { + await resetDatabase(prisma); + orgA = await seedOrganization(prisma, 'orga'); + orgB = await seedOrganization(prisma, 'orgb'); +}); + +/** A batch with one payment, in the given organization. */ +async function seedBatchWithPayment(org: SeededOrg, reference: string) { + const batch = await prisma.payrollBatch.create({ + data: { orgId: org.orgId, reference }, + select: { id: true }, + }); + const payment = await prisma.payment.create({ + data: { + orgId: org.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('payee' + reference), + amountBaseUnits: 10_000_000_000n, + rateBaseUnits: 250_000_000n, + hours: 40n, + }, + select: { id: true }, + }); + return { batchId: batch.id, paymentId: payment.id }; +} + +// --------------------------------------------------------------------------- +// Reproduction A β€” the defect the unit suite could not see +// --------------------------------------------------------------------------- + +describe('A. Approval requires orgId (the composite-FK defect)', () => { + it('rejects an approval created without orgId', async () => { + const { paymentId } = await seedBatchWithPayment(orgA, 'CF-A1'); + + // EXACTLY what src/lib/payments/actions.ts did before the fix. `db` is typed + // `any` there, so TypeScript could not object, and the in-memory double did + // not enforce required columns β€” so this shipped and passed. + // Prisma reports the missing RELATION, not the missing column: + // PrismaClientValidationError: Argument `org` is missing. + // Worth recording precisely, because "orgId" does not appear in the message β€” + // so anyone grepping logs for the column name would not find this failure. + await expect( + (prisma.approval.create as any)({ + data: { + paymentId, + role: OrgRole.MANAGER, + decision: ApprovalDecision.APPROVED, + actorAddress: orgA.members.MANAGER.wallet, + }, + }), + ).rejects.toThrow(/Argument `org` is missing/); + + expect(await prisma.approval.count()).toBe(0); + }); + + it('accepts the same approval once orgId is supplied', async () => { + const { paymentId } = await seedBatchWithPayment(orgA, 'CF-A2'); + const approval = await prisma.approval.create({ + data: { + orgId: orgA.orgId, + paymentId, + role: OrgRole.MANAGER, + decision: ApprovalDecision.APPROVED, + actorAddress: orgA.members.MANAGER.wallet, + }, + }); + expect(approval.orgId).toBe(orgA.orgId); + }); + + it('enforces one decision per role per payment', async () => { + const { paymentId } = await seedBatchWithPayment(orgA, 'CF-A3'); + const base = { + orgId: orgA.orgId, + paymentId, + role: OrgRole.MANAGER, + decision: ApprovalDecision.APPROVED, + actorAddress: orgA.members.MANAGER.wallet, + }; + await prisma.approval.create({ data: base }); + // A second manager approval is a duplicate, not a new fact. + await expect(prisma.approval.create({ data: base })).rejects.toMatchObject({ + code: 'P2002', + }); + + // The other half of the gate is a different row, and allowed. + await prisma.approval.create({ + data: { ...base, role: OrgRole.FINANCE, actorAddress: orgA.members.FINANCE.wallet }, + }); + expect(await prisma.approval.count()).toBe(2); + }); +}); + +// --------------------------------------------------------------------------- +// Reproduction B β€” cross-tenant relations +// --------------------------------------------------------------------------- + +describe('B. Composite foreign keys reject cross-tenant rows', () => { + it('refuses a payment in org A attached to a batch in org B', async () => { + const batchB = await prisma.payrollBatch.create({ + data: { orgId: orgB.orgId, reference: 'CF-B1' }, + select: { id: true }, + }); + + // Only the DATABASE can refuse this. With a plain `batchId` foreign key the + // row would be accepted, and tenant isolation would depend entirely on the + // application remembering to check. + await expect( + prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batchB.id, + recipientAddress: payeeWallet('x'), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }, + }), + ).rejects.toMatchObject({ code: 'P2003' }); + + expect(await prisma.payment.count()).toBe(0); + }); + + it('refuses an approval in org B against a payment in org A', async () => { + const { paymentId } = await seedBatchWithPayment(orgA, 'CF-B2'); + await expect( + prisma.approval.create({ + data: { + orgId: orgB.orgId, + paymentId, + role: OrgRole.MANAGER, + decision: ApprovalDecision.APPROVED, + actorAddress: orgB.members.MANAGER.wallet, + }, + }), + ).rejects.toMatchObject({ code: 'P2003' }); + }); + + it('refuses a payment pointing at another tenant project or worker', async () => { + const projectB = await prisma.project.create({ + data: { orgId: orgB.orgId, code: 'PB', name: 'B project' }, + select: { id: true }, + }); + const batchA = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-B3' }, + select: { id: true }, + }); + + await expect( + prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batchA.id, + projectId: projectB.id, + recipientAddress: payeeWallet('y'), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }, + }), + ).rejects.toMatchObject({ code: 'P2003' }); + }); + + it('refuses an audit event in org B referencing an org A batch', async () => { + const { batchId } = await seedBatchWithPayment(orgA, 'CF-B4'); + await expect( + prisma.auditEvent.create({ + data: { orgId: orgB.orgId, type: 'probe', batchId }, + }), + ).rejects.toMatchObject({ code: 'P2003' }); + }); + + it('allows the same wallet to be a worker in both organizations', async () => { + const address = payeeWallet('shared'); + await prisma.worker.create({ data: { orgId: orgA.orgId, walletAddress: address } }); + // A contractor working for two clients is normal. Uniqueness is per tenant. + await prisma.worker.create({ data: { orgId: orgB.orgId, walletAddress: address } }); + expect(await prisma.worker.count()).toBe(2); + + await expect( + prisma.worker.create({ data: { orgId: orgA.orgId, walletAddress: address } }), + ).rejects.toMatchObject({ code: 'P2002' }); + }); +}); + +// --------------------------------------------------------------------------- +// Unique and partial indexes +// --------------------------------------------------------------------------- + +describe('Unique indexes', () => { + it('allows many batches with a NULL idempotency key', async () => { + // Postgres treats NULLs as distinct in a unique index, which is what makes the + // key optional without forcing every unkeyed batch to collide. + for (let i = 0; i < 3; i++) { + await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: `CF-N${i}`, idempotencyKey: null }, + }); + } + expect(await prisma.payrollBatch.count()).toBe(3); + }); + + it('permits one batch per (orgId, idempotencyKey) and no more', async () => { + await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-K1', idempotencyKey: 'key-1' }, + }); + await expect( + prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-K2', idempotencyKey: 'key-1' }, + }), + ).rejects.toMatchObject({ code: 'P2002' }); + + // Scoped to the tenant: two organizations may reuse a key. + const other = await prisma.payrollBatch.create({ + data: { orgId: orgB.orgId, reference: 'CF-K1', idempotencyKey: 'key-1' }, + }); + expect(other.orgId).toBe(orgB.orgId); + }); + + it('scopes batch references per organization', async () => { + await prisma.payrollBatch.create({ data: { orgId: orgA.orgId, reference: 'CF-00001' } }); + await prisma.payrollBatch.create({ data: { orgId: orgB.orgId, reference: 'CF-00001' } }); + await expect( + prisma.payrollBatch.create({ data: { orgId: orgA.orgId, reference: 'CF-00001' } }), + ).rejects.toMatchObject({ code: 'P2002' }); + }); + + it('allows one payment per on-chain slot and no more', async () => { + const escrow = await prisma.escrow.create({ + data: { + orgId: orgA.orgId, + onChainId: 4242, + contractId: 'CTEST', + network: 'testnet', + managerAddress: orgA.members.MANAGER.wallet, + financeApproverAddress: orgA.members.FINANCE.wallet, + }, + select: { id: true }, + }); + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-S1' }, + select: { id: true }, + }); + const row = (index: number) => ({ + orgId: orgA.orgId, + batchId: batch.id, + escrowId: escrow.id, + onChainPaymentIndex: index, + recipientAddress: payeeWallet('slot' + index), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }); + + await prisma.payment.create({ data: row(0) }); + await prisma.payment.create({ data: row(1) }); + // This is the constraint that makes re-indexing a three-payee settlement + // idempotent however many times the events are replayed. + await expect(prisma.payment.create({ data: row(0) })).rejects.toMatchObject({ + code: 'P2002', + }); + expect(await prisma.payment.count()).toBe(2); + }); + + it('permits only one RUNNING reconciliation run per organization', async () => { + await prisma.reconciliationRun.create({ + data: { + orgId: orgA.orgId, + correlationId: 'rec_1', + status: RunStatus.RUNNING, + scope: 'organization', + }, + }); + + // A partial unique index, so the lock is the database's job rather than a + // check-then-insert in application code β€” which is a race by construction. + await expect( + prisma.reconciliationRun.create({ + data: { + orgId: orgA.orgId, + correlationId: 'rec_2', + status: RunStatus.RUNNING, + scope: 'organization', + }, + }), + ).rejects.toMatchObject({ code: 'P2002' }); + + // Another organization reconciles concurrently, unaffected. + await prisma.reconciliationRun.create({ + data: { + orgId: orgB.orgId, + correlationId: 'rec_3', + status: RunStatus.RUNNING, + scope: 'organization', + }, + }); + + // And once the first completes, the next run may start. + await prisma.reconciliationRun.updateMany({ + where: { correlationId: 'rec_1' }, + data: { status: RunStatus.COMPLETED, completedAt: new Date() }, + }); + const next = await prisma.reconciliationRun.create({ + data: { + orgId: orgA.orgId, + correlationId: 'rec_4', + status: RunStatus.RUNNING, + scope: 'organization', + }, + }); + expect(next.status).toBe(RunStatus.RUNNING); + }); +}); + +// --------------------------------------------------------------------------- +// Money +// --------------------------------------------------------------------------- + +describe('Exact money through PostgreSQL', () => { + it.each([ + ['250.50 USDC', 2_505_000_000n], + ['1000 USDC', 10_000_000_000n], + ['one base unit', 1n], + ['a large payroll', 9_223_372_036_854_775_807n], + ])('round-trips %s without loss', async (_label, units) => { + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: `CF-M${units}` }, + select: { id: true }, + }); + const created = await prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('money'), + amountBaseUnits: units, + rateBaseUnits: 1n, + hours: units, + }, + select: { id: true, amountBaseUnits: true }, + }); + + expect(created.amountBaseUnits).toBe(units); + expect(typeof created.amountBaseUnits).toBe('bigint'); + + // Re-read on a fresh query, not the create's return value. + const reread = await prisma.payment.findUniqueOrThrow({ + where: { id: created.id }, + select: { amountBaseUnits: true, hours: true }, + }); + expect(reread.amountBaseUnits).toBe(units); + expect(reread.hours).toBe(units); + + // And what the column actually holds, as text, bypassing the client entirely. + const raw = await prisma.$queryRawUnsafe<{ amount: string }[]>( + `SELECT "amountBaseUnits"::text AS amount FROM "Payment" WHERE id = $1`, + created.id, + ); + expect(raw[0].amount).toBe(units.toString()); + }); + + it('stores bigint columns as int8, so no value is silently a float', async () => { + const rows = await prisma.$queryRawUnsafe<{ column_name: string; data_type: string }[]>( + `SELECT column_name, data_type FROM information_schema.columns + WHERE table_name = 'Payment' + AND column_name IN ('amountBaseUnits','rateBaseUnits','hours')`, + ); + expect(rows).toHaveLength(3); + for (const r of rows) expect(r.data_type).toBe('bigint'); + }); + + it('refuses a value that would overflow, rather than wrapping it', async () => { + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-OVF' }, + select: { id: true }, + }); + await expect( + prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('ovf'), + amountBaseUnits: 9_223_372_036_854_775_808n, // int8 max + 1 + rateBaseUnits: 1n, + hours: 1n, + }, + }), + ).rejects.toThrow(); + }); +}); + +// --------------------------------------------------------------------------- +// Required columns, cascades, transactions +// --------------------------------------------------------------------------- + +describe('Nullability and required columns', () => { + it('refuses a payment without its tenant or batch', async () => { + await expect( + (prisma.payment.create as any)({ + data: { + recipientAddress: payeeWallet('z'), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }, + }), + ).rejects.toThrow(); + }); + + it('accepts the nullable provenance columns as absent', async () => { + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-NULL' }, + select: { sourceFilename: true, sourceChecksum: true, idempotencyFingerprint: true }, + }); + expect(batch.sourceFilename).toBeNull(); + expect(batch.sourceChecksum).toBeNull(); + expect(batch.idempotencyFingerprint).toBeNull(); + }); +}); + +describe('Cascades and restrictions', () => { + it('removes a batch payments when the batch is deleted', async () => { + const { batchId } = await seedBatchWithPayment(orgA, 'CF-C1'); + await prisma.payrollBatch.delete({ where: { id: batchId } }); + expect(await prisma.payment.count()).toBe(0); + }); + + it('removes an organization entire payroll when the organization is deleted', async () => { + await seedBatchWithPayment(orgA, 'CF-C2'); + await seedBatchWithPayment(orgB, 'CF-C3'); + + await prisma.organization.delete({ where: { id: orgA.orgId } }); + + // Org B is untouched. A cascade that reached across tenants would be a far + // worse failure than a foreign-key error. + expect(await prisma.payment.count()).toBe(1); + const survivor = await prisma.payment.findFirstOrThrow({ select: { orgId: true } }); + expect(survivor.orgId).toBe(orgB.orgId); + }); + + it('refuses to delete an escrow that payments still reference', async () => { + const escrow = await prisma.escrow.create({ + data: { + orgId: orgA.orgId, + onChainId: 77, + contractId: 'CTEST', + network: 'testnet', + managerAddress: orgA.members.MANAGER.wallet, + financeApproverAddress: orgA.members.FINANCE.wallet, + }, + select: { id: true }, + }); + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-C4' }, + select: { id: true }, + }); + const payment = await prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + escrowId: escrow.id, + recipientAddress: payeeWallet('det'), + amountBaseUnits: 5n, + rateBaseUnits: 5n, + hours: 1n, + }, + select: { id: true }, + }); + + // The relation was declared `onDelete: SetNull`, which CANNOT work on a + // composite FK whose first column is NOT NULL β€” the delete failed with a + // confusing "Null constraint violation on the fields: (orgId)". It is now + // NoAction, so the delete is refused for the real reason. + await expect(prisma.escrow.delete({ where: { id: escrow.id } })).rejects.toMatchObject({ + code: 'P2003', + }); + + // Refusing is how the record is preserved. Detaching a payment from its escrow + // would destroy the evidence of what the money was for. + const after = await prisma.payment.findUniqueOrThrow({ + where: { id: payment.id }, + select: { escrowId: true, amountBaseUnits: true }, + }); + expect(after.escrowId).toBe(escrow.id); + expect(after.amountBaseUnits).toBe(5n); + }); + + it('still cascades a whole organization away in one statement', async () => { + // NoAction is checked at the END of the statement, which is why this works + // where RESTRICT might not: Organization -> Escrow and Organization -> Payment + // are both Cascade, so parent and child disappear together. + const escrow = await prisma.escrow.create({ + data: { + orgId: orgA.orgId, + onChainId: 78, + contractId: 'CTEST', + network: 'testnet', + managerAddress: orgA.members.MANAGER.wallet, + financeApproverAddress: orgA.members.FINANCE.wallet, + }, + select: { id: true }, + }); + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-C5' }, + select: { id: true }, + }); + await prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + escrowId: escrow.id, + recipientAddress: payeeWallet('csc'), + amountBaseUnits: 9n, + rateBaseUnits: 9n, + hours: 1n, + }, + }); + + await prisma.organization.delete({ where: { id: orgA.orgId } }); + + expect(await prisma.payment.count({ where: { orgId: orgA.orgId } })).toBe(0); + expect(await prisma.escrow.count({ where: { orgId: orgA.orgId } })).toBe(0); + }); +}); + +describe('Transaction isolation and rollback', () => { + it('rolls back every write when a transaction throws', async () => { + await expect( + prisma.$transaction(async (tx) => { + const batch = await tx.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-T1' }, + select: { id: true }, + }); + await tx.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('t1'), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }, + }); + await tx.auditEvent.create({ + data: { orgId: orgA.orgId, type: 'payroll.batch.created', batchId: batch.id }, + }); + throw new Error('simulated failure after the last write'); + }), + ).rejects.toThrow('simulated failure'); + + // Zero partial financial records. A batch that looked complete while missing a + // payment would quietly underpay someone. + expect(await prisma.payrollBatch.count()).toBe(0); + expect(await prisma.payment.count()).toBe(0); + expect(await prisma.auditEvent.count()).toBe(0); + }); + + it('does not let one failing transaction undo another committed one', async () => { + // The in-memory double originally failed this: it snapshotted every table and + // restored the whole snapshot, so a rollback discarded a concurrent + // transaction's committed writes. Real Postgres isolates per connection. + await prisma.$transaction(async (tx) => { + await tx.payrollBatch.create({ data: { orgId: orgA.orgId, reference: 'CF-KEEP' } }); + }); + + await expect( + prisma.$transaction(async (tx) => { + await tx.payrollBatch.create({ data: { orgId: orgA.orgId, reference: 'CF-DROP' } }); + throw new Error('rollback'); + }), + ).rejects.toThrow('rollback'); + + const remaining = await prisma.payrollBatch.findMany({ select: { reference: true } }); + expect(remaining.map((b) => b.reference)).toEqual(['CF-KEEP']); + }); + + it('surfaces a constraint violation as a rollback, not a partial write', async () => { + await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-DUP', idempotencyKey: 'dup-key' }, + }); + + await expect( + prisma.$transaction(async (tx) => { + const batch = await tx.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-DUP2', idempotencyKey: 'dup-key' }, + select: { id: true }, + }); + await tx.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('dup'), + amountBaseUnits: 1n, + rateBaseUnits: 1n, + hours: 1n, + }, + }); + }), + ).rejects.toMatchObject({ code: 'P2002' }); + + expect(await prisma.payrollBatch.count()).toBe(1); + expect(await prisma.payment.count()).toBe(0); + }); +}); + +describe('Indexes supporting tenant-scoped queries', () => { + it('indexes the columns every tenant query filters on', async () => { + const rows = await prisma.$queryRawUnsafe<{ tablename: string; indexdef: string }[]>( + `SELECT tablename, indexdef FROM pg_indexes + WHERE schemaname = 'public' AND indexdef LIKE '%orgId%'`, + ); + const tables = new Set(rows.map((r) => r.tablename)); + // Every table a tenant query filters by organization. + for (const t of [ + 'Payment', + 'PayrollBatch', + 'Escrow', + 'Worker', + 'Project', + 'Approval', + 'AuditEvent', + 'ReconciliationFinding', + 'ReconciliationRun', + 'OrgMember', + ]) { + expect(tables).toContain(t); + } + }); + + it('has an index the planner can use for a tenant payment listing', async () => { + const batch = await prisma.payrollBatch.create({ + data: { orgId: orgA.orgId, reference: 'CF-IDX' }, + select: { id: true }, + }); + for (let i = 0; i < 50; i++) { + await prisma.payment.create({ + data: { + orgId: orgA.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('idx' + i), + amountBaseUnits: BigInt(i + 1), + rateBaseUnits: 1n, + hours: BigInt(i + 1), + state: i % 2 === 0 ? PaymentState.DRAFT : PaymentState.PAID, + }, + }); + } + await prisma.$executeRawUnsafe('ANALYZE "Payment"'); + + // Asserting that the planner CHOOSES an index would be asserting a cost + // decision: on a small table a sequential scan is genuinely cheaper, and + // Postgres is right to pick it. What matters is that a usable index EXISTS, so + // the query does not degrade to a full scan once a tenant has real volume. + // Discouraging seqscan makes the planner reveal whether it has one. + // Both statements must share one connection: SET LOCAL applies only inside a + // transaction, and issued on its own it is silently discarded β€” which is why + // the first attempt at this test still saw a sequential scan. + const plan = await prisma.$transaction(async (tx) => { + await tx.$executeRawUnsafe('SET LOCAL enable_seqscan = off'); + return tx.$queryRawUnsafe<{ 'QUERY PLAN': string }[]>( + `EXPLAIN SELECT * FROM "Payment" WHERE "orgId" = $1 AND state = 'DRAFT'`, + orgA.orgId, + ); + }); + const text = plan.map((r) => r['QUERY PLAN']).join('\n'); + expect(text).toMatch(/Index Scan|Bitmap Index Scan|Bitmap Heap Scan/); + }); +}); diff --git a/src/lib/db/__tests__/helpers.ts b/src/lib/db/__tests__/helpers.ts new file mode 100644 index 0000000..e4d0bac --- /dev/null +++ b/src/lib/db/__tests__/helpers.ts @@ -0,0 +1,150 @@ +/** + * Shared fixtures for INTEGRATION tests, which run against real PostgreSQL. + * + * Not exported from any production path. Nothing here may be imported by a unit + * test: the point of the split is that a unit suite can never be mistaken for + * database validation. + */ + +import { PrismaClient, OrgRole, MembershipStatus } from '@prisma/client'; + +/** Every table, child-first, so a TRUNCATE is unambiguous even without CASCADE. */ +const TABLES = [ + 'ReconciliationFinding', + 'ReconciliationRun', + 'AuditEvent', + 'BlockchainTransaction', + 'OracleAttestation', + 'Approval', + 'Payment', + 'PayrollBatch', + 'Escrow', + 'Worker', + 'Project', + 'Invitation', + 'OrgMember', + 'Organization', + 'ChainEvent', + 'IndexerCursor', + 'TimeLog', + 'AuditLog', + 'Session', + 'AuthChallenge', + 'User', +] as const; + +/** + * Empty every table. + * + * TRUNCATE rather than deleteMany: it is one statement, it resets nothing we rely + * on, and it will not silently leave rows behind because of a cascade rule a test + * author did not expect. + */ +export async function resetDatabase(prisma: PrismaClient): Promise { + const list = TABLES.map((t) => `"${t}"`).join(', '); + await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${list} RESTART IDENTITY CASCADE`); +} + +/** + * Refuse to run against anything but a local database. + * + * The npm script runs `check-env` first, but a test invoked directly bypasses it. + * These tests TRUNCATE every table, so this is the guard that matters most. + */ +export function assertLocalDatabase(): void { + const url = process.env.DATABASE_URL ?? ''; + let host: string; + try { + host = new URL(url).hostname; + } catch { + throw new Error('DATABASE_URL is unset or unparseable; refusing to run.'); + } + const LOCAL = new Set(['localhost', '127.0.0.1', '::1', 'host.docker.internal', 'postgres', 'db']); + if (!LOCAL.has(host)) { + throw new Error( + `Refusing to run integration tests against non-local host "${host}". ` + + 'These tests TRUNCATE every table. See docs/ENVIRONMENTS.md.', + ); + } +} + +export interface SeededOrg { + orgId: string; + slug: string; + members: Record; +} + +function wallet(tag: string): string { + return ('G' + tag.toUpperCase().replace(/[^A-Z2-7]/g, '')).padEnd(56, 'A'); +} + +/** An organization with one active member per role. */ +export async function seedOrganization( + prisma: PrismaClient, + slug: string, +): Promise { + const org = await prisma.organization.create({ + data: { name: slug, slug }, + select: { id: true }, + }); + + const roles: OrgRole[] = [ + OrgRole.OWNER, + OrgRole.ADMIN, + OrgRole.MANAGER, + OrgRole.FINANCE, + OrgRole.WORKER, + OrgRole.VIEWER, + ]; + + const members: SeededOrg['members'] = {}; + for (const role of roles) { + const tag = `${slug}${role}`; + const address = wallet(tag); + const user = await prisma.user.create({ + data: { walletAddress: address }, + select: { id: true }, + }); + await prisma.orgMember.create({ + data: { + orgId: org.id, + userId: user.id, + role, + status: MembershipStatus.ACTIVE, + }, + }); + members[role] = { userId: user.id, wallet: address, role }; + } + + return { orgId: org.id, slug, members }; +} + +/** A worker row, for testing the payee link. */ +export async function seedWorker( + prisma: PrismaClient, + orgId: string, + tag: string, +): Promise<{ id: string; walletAddress: string }> { + return prisma.worker.create({ + data: { orgId, walletAddress: wallet(tag), displayName: tag }, + select: { id: true, walletAddress: true }, + }); +} + +export function payeeWallet(tag: string): string { + return wallet(tag); +} + +/** A CSV whose rows satisfy the contract's hours x rate == amount invariant. */ +export function payrollCsv( + rows: { tag: string; amount: string; hours: number; rate: string }[], + period: { start: string; end: string } = { start: '2026-09-01', end: '2026-09-15' }, +): string { + return [ + 'recipient,amount,asset,hours,rate,period_start,period_end', + ...rows.map( + (r) => + `${wallet(r.tag)},${r.amount},USDC,${r.hours},${r.rate},${period.start},${period.end}`, + ), + ].join('\n'); +} diff --git a/src/lib/env.ts b/src/lib/env.ts index a882706..3301289 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -9,7 +9,10 @@ const envSchema = z.object({ ADMIN_WALLET_ADDRESS: z.string().optional().default(''), BOOTSTRAP_SECRET: z.string().optional().default(''), NEXT_PUBLIC_COMPANY_NAME: z.string().default('CoreFlow'), - AUTH_SECRET: z.string().min(32, 'AUTH_SECRET must be at least 32 characters').optional().default('default_super_secret_coreflow_jwt_key_32bytes'), + // No default. A fallback here would be a publicly-known JWT signing key that + // still satisfies min(32), letting anyone forge an ADMIN session on any + // deployment that forgot to set the variable. Missing => startup failure. + AUTH_SECRET: z.string().min(32, 'AUTH_SECRET must be at least 32 characters'), NEXT_PUBLIC_SENTRY_DSN: z.string().optional().default(''), }); diff --git a/src/lib/explorer.ts b/src/lib/explorer.ts new file mode 100644 index 0000000..23f5121 --- /dev/null +++ b/src/lib/explorer.ts @@ -0,0 +1,56 @@ +/** + * Stellar Expert explorer links, derived from the ACTIVE network. + * + * ── Why this is centralized ────────────────────────────────────────────────── + * Several call sites hard-coded `/explorer/public/` (Mainnet) regardless of + * which network the app was actually pointed at. A Testnet transaction linked + * to the Mainnet explorer resolves to nothing β€” and, worse, presents Testnet + * activity as though it happened on Mainnet. For a product whose credibility + * rests on verifiable settlement, a link that misstates the network is a + * correctness bug, not a cosmetic one. + * + * ── CoreFlow v1 vs v2 ──────────────────────────────────────────────────────── + * CoreFlow v1 is deployed on Mainnet. CoreFlow v2 β€” the hardened contract with + * domain-separated attestations, an admin-managed oracle registry, and the + * work/amount invariant β€” is deployed on TESTNET ONLY. These are different + * contracts with different security properties. `V1_MAINNET` exists so the one + * place that deliberately references the historical deployment can do so + * explicitly, rather than by a Mainnet default leaking through. + */ + +import { STELLAR_CONFIG } from './config'; + +const BASE = 'https://stellar.expert/explorer'; + +/** The historical v1 contract on Mainnet. Not v2, and not security-hardened. */ +export const V1_MAINNET = { + contractId: 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW', + network: 'public' as const, + label: 'CoreFlow v1 Β· Stellar Mainnet', + url: `${BASE}/public/contract/CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW`, +}; + +/** `public` or `testnet`, matching the network the app is configured for. */ +function segment(): 'public' | 'testnet' { + return STELLAR_CONFIG.isMainnet() ? 'public' : 'testnet'; +} + +export function txUrl(hash: string): string { + return `${BASE}/${segment()}/tx/${hash}`; +} + +export function contractUrl(contractId: string): string { + return `${BASE}/${segment()}/contract/${contractId}`; +} + +export function accountUrl(address: string): string { + return `${BASE}/${segment()}/account/${address}`; +} + +/** + * Human label for the active network, for use next to an explorer link so a + * reader never has to infer which chain a hash belongs to. + */ +export function explorerNetworkLabel(): string { + return STELLAR_CONFIG.isMainnet() ? 'Mainnet' : 'Testnet'; +} diff --git a/src/lib/funding/__tests__/eligibility.test.ts b/src/lib/funding/__tests__/eligibility.test.ts new file mode 100644 index 0000000..a3174cd --- /dev/null +++ b/src/lib/funding/__tests__/eligibility.test.ts @@ -0,0 +1,272 @@ +import { describe, it, expect } from 'vitest'; +import { OrgRole, PaymentState } from '@prisma/client'; +import { + assessFundingEligibility, + MAX_FUNDABLE_PAYMENTS, + type EligibilityInput, + type FundingPayment, +} from '../eligibility'; + +const ASSET = { code: 'USDC', contractId: 'CUSDC', decimals: 7 }; + +function wallet(tag: string): string { + return ('G' + tag.toUpperCase().replace(/[^A-Z2-7]/g, '')).padEnd(56, 'A'); +} + +function payment(over: Partial = {}): FundingPayment { + return { + id: 'pay_1', + recipientAddress: wallet('alice'), + assetCode: 'USDC', + assetContractId: 'CUSDC', + assetDecimals: 7, + amountBaseUnits: 10_000_000_000n, + rateBaseUnits: 250_000_000n, + hours: 40n, + periodStart: new Date('2026-09-01T00:00:00Z'), + periodEnd: new Date('2026-09-15T00:00:00Z'), + state: PaymentState.DRAFT, + escrowId: null, + onChainPaymentIndex: null, + ...over, + }; +} + +function input(over: Partial = {}): EligibilityInput { + return { + payments: [payment()], + asset: ASSET, + funder: { walletAddress: wallet('manager'), role: OrgRole.MANAGER }, + financeApproverAddress: wallet('finance'), + attempt: null, + oraclePublicKey: 'ab'.repeat(32), + ...over, + }; +} + +const codes = (r: ReturnType) => r.blockers.map((b) => b.code); + +describe('a fundable batch', () => { + it('is eligible and reports the exact total', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ id: 'a' }), payment({ id: 'b', amountBaseUnits: 2_600_000_000n, rateBaseUnits: 130_000_000n, hours: 20n })] }), + ); + expect(result.eligible).toBe(true); + expect(result.blockers).toEqual([]); + expect(result.totalBaseUnits).toBe(12_600_000_000n); + expect(result.paymentCount).toBe(2); + }); + + it('accepts a payment already moved to VALIDATING by an earlier attempt', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ state: PaymentState.VALIDATING })] }), + ); + expect(result.eligible).toBe(true); + }); + + it.each([[OrgRole.OWNER], [OrgRole.ADMIN], [OrgRole.MANAGER]])( + 'allows %s to fund', + (role) => { + const result = assessFundingEligibility(input({ funder: { walletAddress: wallet('m'), role } })); + expect(result.eligible).toBe(true); + }, + ); +}); + +describe('who may fund', () => { + it.each([[OrgRole.FINANCE], [OrgRole.WORKER], [OrgRole.VIEWER]])( + 'refuses %s', + (role) => { + const result = assessFundingEligibility(input({ funder: { walletAddress: wallet('x'), role } })); + expect(codes(result)).toContain('ROLE_NOT_PERMITTED'); + }, + ); +}); + +describe('double-funding', () => { + it('refuses a batch already funded', () => { + const result = assessFundingEligibility( + input({ + attempt: { id: 't1', status: 'CONFIRMED', hash: 'f'.repeat(64), createdAt: new Date() }, + }), + ); + expect(codes(result)).toContain('ALREADY_FUNDED'); + // The reason matters: a second escrow would move the money a second time. + expect(result.blockers[0].message).toContain('second time'); + }); + + it.each([['PREPARING'], ['SIMULATING'], ['AWAITING_SIGNATURE'], ['SUBMITTED']] as const)( + 'refuses while an attempt is %s', + (status) => { + const result = assessFundingEligibility( + input({ attempt: { id: 't1', status, hash: null, createdAt: new Date() } }), + ); + expect(codes(result)).toContain('FUNDING_IN_FLIGHT'); + }, + ); + + it.each([['FAILED'], ['CANCELLED'], ['EXPIRED']] as const)( + 'allows a retry after a %s attempt', + (status) => { + const result = assessFundingEligibility( + input({ attempt: { id: 't1', status, hash: null, createdAt: new Date() } }), + ); + expect(result.eligible).toBe(true); + }, + ); + + it('refuses a payment already attached to an escrow', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ escrowId: 'esc_1', onChainPaymentIndex: 0 })] }), + ); + expect(codes(result)).toContain('PAYMENT_ALREADY_ON_CHAIN'); + }); +}); + +describe('the contract preconditions', () => { + it('refuses a payment with no pay period, and will not invent one', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ periodStart: null, periodEnd: null })] }), + ); + const blocker = result.blockers.find((b) => b.code === 'PERIOD_REQUIRED'); + expect(blocker).toBeDefined(); + // The period is a signed field of the oracle proof, so assuming it would mean + // attesting to a pay period nobody stated. + expect(blocker!.message).toContain('part of what the oracle signs'); + }); + + it('refuses a period that does not end after it starts', () => { + const result = assessFundingEligibility( + input({ + payments: [ + payment({ + periodStart: new Date('2026-09-15T00:00:00Z'), + periodEnd: new Date('2026-09-01T00:00:00Z'), + }), + ], + }), + ); + expect(codes(result)).toContain('PERIOD_INVALID'); + }); + + it('refuses amount that is not hours x rate', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ amountBaseUnits: 9_999_999_999n })] }), + ); + const blocker = result.blockers.find((b) => b.code === 'HOURS_RATE_MISMATCH'); + expect(blocker).toBeDefined(); + expect(blocker!.position).toBe(1); + }); + + it.each([ + ['zero amount', { amountBaseUnits: 0n }], + ['zero hours', { hours: 0n }], + ['zero rate', { rateBaseUnits: 0n }], + ])('refuses %s', (_label, over) => { + const result = assessFundingEligibility(input({ payments: [payment(over)] })); + expect(codes(result)).toContain('AMOUNT_NOT_POSITIVE'); + }); + + it('refuses more payments than the contract accepts in one escrow', () => { + const payments = Array.from({ length: MAX_FUNDABLE_PAYMENTS + 1 }, (_, i) => + payment({ id: `p${i}` }), + ); + const result = assessFundingEligibility(input({ payments })); + expect(codes(result)).toContain('TOO_MANY_PAYMENTS'); + }); + + it('accepts exactly the contract limit', () => { + const payments = Array.from({ length: MAX_FUNDABLE_PAYMENTS }, (_, i) => payment({ id: `p${i}` })); + expect(assessFundingEligibility(input({ payments })).eligible).toBe(true); + }); +}); + +describe('dual control, checked before a wallet opens', () => { + it('refuses when the organization has no second approver', () => { + const result = assessFundingEligibility(input({ financeApproverAddress: null })); + expect(codes(result)).toContain('NO_DISTINCT_FINANCE_APPROVER'); + }); + + it('refuses when the funder would also be the finance approver', () => { + const same = wallet('manager'); + const result = assessFundingEligibility( + input({ funder: { walletAddress: same, role: OrgRole.MANAGER }, financeApproverAddress: same }), + ); + const blocker = result.blockers.find((b) => b.code === 'NO_DISTINCT_FINANCE_APPROVER'); + // The contract refuses this too; catching it here gives a readable reason + // instead of a trapped transaction after the money has been committed. + expect(blocker!.message).toContain('SignersNotDistinct'); + }); +}); + +describe('the settlement asset', () => { + it('refuses when no SAC is configured', () => { + const result = assessFundingEligibility(input({ asset: { ...ASSET, contractId: null } })); + const blocker = result.blockers.find((b) => b.code === 'SETTLEMENT_ASSET_UNCONFIGURED'); + expect(blocker!.message).toContain('will not infer'); + }); + + it('refuses a payment denominated in another asset', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ assetCode: 'EURC' })] }), + ); + expect(codes(result)).toContain('ASSET_MISMATCH'); + }); + + it('refuses a batch mixing assets, because one escrow holds one asset', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ id: 'a' }), payment({ id: 'b', assetCode: 'XLM' })] }), + ); + expect(codes(result)).toContain('MIXED_ASSETS'); + }); +}); + +describe('the oracle', () => { + it('refuses when no oracle key is available', () => { + const result = assessFundingEligibility(input({ oraclePublicKey: null })); + const blocker = result.blockers.find((b) => b.code === 'ORACLE_KEY_UNAVAILABLE'); + // Funding an escrow whose work can never be verified creates custody that can + // never be released. + expect(blocker!.message).toContain('never settle'); + }); +}); + +describe('reporting', () => { + it('reports every blocker at once, not just the first', () => { + const result = assessFundingEligibility( + input({ + payments: [ + payment({ id: 'a', periodStart: null, periodEnd: null }), + payment({ id: 'b', amountBaseUnits: 1n }), + payment({ id: 'c', state: PaymentState.PAID }), + ], + financeApproverAddress: null, + oraclePublicKey: null, + }), + ); + expect(result.eligible).toBe(false); + const found = new Set(codes(result)); + for (const expected of [ + 'PERIOD_REQUIRED', + 'HOURS_RATE_MISMATCH', + 'PAYMENT_NOT_FUNDABLE', + 'NO_DISTINCT_FINANCE_APPROVER', + 'ORACLE_KEY_UNAVAILABLE', + ]) { + expect(found).toContain(expected); + } + }); + + it('names the row a payment blocker came from', () => { + const result = assessFundingEligibility( + input({ payments: [payment({ id: 'a' }), payment({ id: 'b', hours: 0n })] }), + ); + const blocker = result.blockers.find((b) => b.code === 'AMOUNT_NOT_POSITIVE'); + expect(blocker!.position).toBe(2); + expect(blocker!.paymentId).toBe('b'); + }); + + it('refuses an empty batch', () => { + expect(codes(assessFundingEligibility(input({ payments: [] })))).toContain('NO_PAYMENTS'); + }); +}); diff --git a/src/lib/funding/__tests__/live-funding.test.ts b/src/lib/funding/__tests__/live-funding.test.ts new file mode 100644 index 0000000..70e6b68 --- /dev/null +++ b/src/lib/funding/__tests__/live-funding.test.ts @@ -0,0 +1,498 @@ +// @vitest-environment node +/** + * LIVE Testnet funding validation. + * + * OPT-IN. Skipped unless COREFLOW_LIVE_TESTNET=1, because it submits a REAL + * transaction to Stellar Testnet that moves REAL test USDC into escrow custody. + * + * COREFLOW_LIVE_TESTNET=1 npx vitest run --config vitest.integration.config.ts \ + * src/lib/funding/__tests__/live-funding.test.ts + * + * What it proves, and why it cannot be proved any other way: that the frozen plan, + * the transaction the contract actually executed, the custody transfer the token + * contract actually emitted, the indexer's projection and the reconciler's + * independent verification all agree β€” on real infrastructure, in one run. + * + * Signing is done by the Stellar CLI using the project's own `coreflow-v2-manager` + * identity. No secret is read, printed, or passed through this process. + * + * `initialize_multi_sig_escrow` is NOT idempotent: it creates an escrow and moves + * custody atomically. So this test submits exactly one funding transaction and, if + * anything becomes uncertain, asserts on the recovery path rather than submitting + * another. + */ + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import { mkdirSync, writeFileSync } from 'node:fs'; +import { OrgRole, MembershipStatus, PaymentState, TxStatus } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { assertLocalDatabase } from '@/lib/db/__tests__/helpers'; +import { STELLAR_CONFIG } from '@/lib/config'; +import { createBatch } from '@/lib/payroll/api'; +import { approveBatch } from '@/lib/payroll/api'; +import { + openFundingIntent, + recordFundingSubmitted, + confirmFunding, + getFundingState, + readStoredPlan, + planDigest, +} from '../service'; +import { createRpcVerifier } from '@/lib/reconciliation/chain-verifier'; +import { runIndexerFromRpc } from '@/lib/indexer/run'; +import { runReconciliation } from '@/lib/reconciliation/scheduler'; +import type { TenantContext } from '@/lib/tenancy/resolve'; + +const LIVE = process.env.COREFLOW_LIVE_TESTNET === '1'; + +/** The project's own Testnet identities. Names only β€” never secrets. */ +const MANAGER_KEY = 'coreflow-v2-manager'; +const NETWORK = 'testnet'; + +/** Three payments, deliberately tiny: 1.0 + 1.5 + 0.5 = 3.0 test USDC. */ +const ROWS = [ + { key: 'coreflow-v2-worker', amount: '1', hours: 2, rate: '0.5' }, + { key: 'coreflow-v2-worker2', amount: '1.5', hours: 3, rate: '0.5' }, + { key: 'coreflow-v2-worker3', amount: '0.5', hours: 1, rate: '0.5' }, +]; +const EXPECTED_TOTAL = 30_000_000n; // 3.0 USDC at 7 decimals +const PERIOD = { start: '2026-09-01', end: '2026-09-15' }; + +function cli(args: string[]): string { + const command = `stellar ${args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(' ')} 2>&1`; + try { + return execFileSync('bash', ['-c', command], { + encoding: 'utf8', + maxBuffer: 10 * 1024 * 1024, + }); + } catch (e: any) { + // The CLI writes the contract's own error to stdout/stderr. Swallowing it turns + // a diagnosable refusal into "Command failed", which is useless here. + const output = `${e.stdout ?? ''}${e.stderr ?? ''}`.trim(); + throw new Error(`stellar CLI failed.\n${output}`); + } +} + +function addressOf(identity: string): string { + return cli(['keys', 'address', identity]).trim(); +} + +const evidence: Record = { version: 'v2', network: NETWORK, steps: [] }; +function step(name: string, detail: Record) { + (evidence.steps as unknown[]).push({ step: name, ...detail, at: new Date().toISOString() }); +} + +let ctx: TenantContext; +let financeCtx: TenantContext; +let orgId: string; +let batchId: string; +let managerAddress: string; +let financeAddress: string; + +beforeAll(async () => { + if (!LIVE) return; + // These tests TRUNCATE nothing, but they do write real records and spend real + // test funds. Refuse outright unless the database is local. + assertLocalDatabase(); + // A guard that refuses must say what it saw, or the operator is left guessing + // which of several env sources put them on the wrong chain. + const seen = { + envNetwork: process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? null, + envContract: process.env.NEXT_PUBLIC_STELLAR_CONTRACT_ID ?? null, + configNetwork: STELLAR_CONFIG.contract.network, + configContract: STELLAR_CONFIG.contract.id || null, + isMainnet: STELLAR_CONFIG.isMainnet(), + }; + if (seen.isMainnet || seen.configNetwork !== 'testnet') { + throw new Error(`refusing to run: not on Testnet. ${JSON.stringify(seen)}`); + } + if (!seen.envContract) { + throw new Error( + 'refusing to run: NEXT_PUBLIC_STELLAR_CONTRACT_ID is not visible to this ' + + `process, so the contract cannot be confirmed. ${JSON.stringify(seen)}`, + ); + } + + await prisma.$connect(); + + managerAddress = addressOf(MANAGER_KEY); + financeAddress = addressOf('coreflow-v2-finance'); + expect(managerAddress).toMatch(/^G[A-Z2-7]{55}$/); + expect(financeAddress).not.toBe(managerAddress); + + // A fresh organization per run, so this never disturbs earlier evidence. + const slug = `live-funding-${Date.now()}`; + const org = await prisma.organization.create({ + data: { name: 'Live Funding Run', slug }, + select: { id: true }, + }); + orgId = org.id; + + const manager = await prisma.user.upsert({ + where: { walletAddress: managerAddress }, + update: {}, + create: { walletAddress: managerAddress }, + select: { id: true }, + }); + const finance = await prisma.user.upsert({ + where: { walletAddress: financeAddress }, + update: {}, + create: { walletAddress: financeAddress }, + select: { id: true }, + }); + await prisma.orgMember.createMany({ + data: [ + { orgId, userId: manager.id, role: OrgRole.MANAGER, status: MembershipStatus.ACTIVE }, + { orgId, userId: finance.id, role: OrgRole.FINANCE, status: MembershipStatus.ACTIVE }, + ], + }); + + ctx = { + orgId, + orgName: 'Live Funding Run', + orgSlug: slug, + userId: manager.id, + walletAddress: managerAddress, + role: OrgRole.MANAGER, + }; + financeCtx = { ...ctx, userId: finance.id, walletAddress: financeAddress, role: OrgRole.FINANCE }; + + step('environment', { + contractId: STELLAR_CONFIG.requireContractId(), + assetContract: process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID, + orgId, + managerAddress, + financeAddress, + }); +}, 120_000); + +afterAll(async () => { + if (!LIVE) return; + mkdirSync('docs/evidence', { recursive: true }); + writeFileSync( + 'docs/evidence/testnet-v2-live-funding.json', + JSON.stringify(evidence, null, 2) + '\n', + ); + await prisma.$disconnect(); +}); + +describe.skipIf(!LIVE)('live Testnet funding', () => { + it('creates a real payroll of three payments from a CSV', async () => { + const csv = [ + 'recipient,amount,asset,hours,rate,period_start,period_end', + ...ROWS.map( + (r) => + `${addressOf(r.key)},${r.amount},USDC,${r.hours},${r.rate},${PERIOD.start},${PERIOD.end}`, + ), + ].join('\n'); + + const outcome = await createBatch(prisma, ctx, { + csv, + filename: 'live-funding-run.csv', + idempotencyKey: `live-${Date.now()}`, + }); + batchId = outcome.batch.id; + + expect(outcome.created).toBe(true); + expect(outcome.batch.paymentCount).toBe(3); + expect(outcome.batch.totalBaseUnits).toBe(EXPECTED_TOTAL.toString()); + + const payments = await prisma.payment.findMany({ + where: { orgId, batchId }, + orderBy: [{ createdAt: 'asc' }], + }); + expect(payments).toHaveLength(3); + for (const [i, p] of payments.entries()) { + expect(p.state).toBe(PaymentState.DRAFT); + expect(p.assetCode).toBe('USDC'); + expect(p.hours * p.rateBaseUnits).toBe(p.amountBaseUnits); + expect(p.periodStart?.toISOString().slice(0, 10)).toBe(PERIOD.start); + expect(p.recipientAddress).toBe(addressOf(ROWS[i].key)); + } + + step('payroll.created', { + batchId, + reference: outcome.batch.reference, + paymentIds: payments.map((p) => p.id), + totalBaseUnits: outcome.batch.totalBaseUnits, + }); + }, 120_000); + + it('records both halves of the approval gate as real Approval rows', async () => { + const payments = await prisma.payment.findMany({ + where: { orgId, batchId }, + select: { id: true, state: true }, + }); + + const asManager = await approveBatch(prisma, ctx, { id: batchId, payments }); + const asFinance = await approveBatch(prisma, financeCtx, { id: batchId, payments }); + + expect(asManager.recorded).toBe(3); + expect(asFinance.recorded).toBe(3); + + const approvals = await prisma.approval.findMany({ where: { orgId } }); + expect(approvals).toHaveLength(6); + // Two distinct wallets, never one standing in for both. + expect(new Set(approvals.map((a) => a.actorAddress)).size).toBe(2); + + step('approvals.recorded', { + manager: asManager.approvalRole, + finance: asFinance.approvalRole, + approvalCount: approvals.length, + }); + }, 120_000); + + it('freezes a funding plan that matches the payroll exactly', async () => { + const result = await openFundingIntent(prisma, ctx, { + id: batchId, + reference: (await prisma.payrollBatch.findUniqueOrThrow({ + where: { id: batchId }, + select: { reference: true }, + })).reference, + }); + + expect(result.created).toBe(true); + expect(result.plan.schedule).toHaveLength(3); + expect(result.plan.totalBaseUnits).toBe(EXPECTED_TOTAL.toString()); + expect(result.plan.manager).toBe(managerAddress); + expect(result.plan.financeApprover).toBe(financeAddress); + + const record = await prisma.blockchainTransaction.findFirstOrThrow({ + where: { orgId, batchId }, + }); + expect(record.status).toBe(TxStatus.AWAITING_SIGNATURE); + expect(record.planDigest).toMatch(/^[0-9a-f]{64}$/); + + // The stored plan is intact and is what confirmation will compare against. + const stored = readStoredPlan({ plan: record.plan, planDigest: record.planDigest }); + expect(planDigest(stored)).toBe(record.planDigest); + expect(stored.rows.map((r) => r.amountBaseUnits)).toEqual(['10000000', '15000000', '5000000']); + + step('funding.intent', { + attemptId: record.id, + planDigest: record.planDigest, + paymentCount: stored.rows.length, + }); + }, 120_000); + + it('submits ONE real transaction that creates and funds the escrow', async () => { + const record = await prisma.blockchainTransaction.findFirstOrThrow({ + where: { orgId, batchId }, + }); + const plan = readStoredPlan({ plan: record.plan, planDigest: record.planDigest }); + + // Built from the STORED plan, in order, with no adjustment. + const payments = plan.rows.map((r, i) => ({ + id: i + 1, + worker: r.worker, + token: r.token, + amount: r.amountBaseUnits, + start_date: r.startDate, + end_date: r.endDate, + hours_logged: '0', + rate_per_hour: r.rateBaseUnits, + proof_verified: false, + status: 0, + })); + + const out = cli([ + 'contract', 'invoke', '--id', plan.contractId, '--source', MANAGER_KEY, + '--network', NETWORK, '--', + 'initialize_multi_sig_escrow', + '--manager', plan.manager, + '--finance_approver', plan.financeApprover, + '--oracle_pubkey', plan.oraclePublicKey, + '--payments', JSON.stringify(payments), + ]); + + const hash = out.match(/explorer\/testnet\/tx\/([0-9a-f]{64})/)?.[1] ?? null; + const escrowId = Number((out.trim().split('\n').pop() || '').replace(/[^0-9]/g, '')); + + expect(hash).toMatch(/^[0-9a-f]{64}$/); + expect(Number.isInteger(escrowId) && escrowId > 0).toBe(true); + + // Persist the hash before any confirmation is attempted. + const attempt = await recordFundingSubmitted(prisma, ctx, { + attemptId: record.id, + transactionHash: hash!, + batchId, + }); + expect(attempt.status).toBe(TxStatus.SUBMITTED); + expect(attempt.hash).toBe(hash); + + evidence.transactionHash = hash; + evidence.escrowId = escrowId; + step('funding.submitted', { transactionHash: hash, escrowId }); + }, 300_000); + + it('independently verifies the SAC custody transfer', async () => { + const verifier = createRpcVerifier(); + const assetContract = process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID!; + const transfers = await verifier.readTransfers(assetContract, {}); + expect(transfers.ok).toBe(true); + if (!transfers.ok) return; + + // The TOKEN contract's own event, not CoreFlow's claim about it. + const custody = transfers.value.find( + (t) => t.txHash === evidence.transactionHash && t.from === managerAddress, + ); + expect(custody).toBeDefined(); + expect(custody!.to).toBe(STELLAR_CONFIG.requireContractId()); + expect(custody!.amountBaseUnits).toBe(EXPECTED_TOTAL); + expect(custody!.assetContractId).toBe(assetContract); + + step('custody.verified', { + from: custody!.from, + to: custody!.to, + amountBaseUnits: custody!.amountBaseUnits.toString(), + assetContractId: custody!.assetContractId, + ledger: custody!.ledger, + }); + }, 180_000); + + it('confirms funding against the frozen plan', async () => { + const record = await prisma.blockchainTransaction.findFirstOrThrow({ + where: { orgId, batchId }, + }); + + const result = await confirmFunding(prisma, ctx, createRpcVerifier(), { + attemptId: record.id, + // Supplied so the server cross-checks it against what the transaction + // actually created, rather than trusting it. + onChainEscrowId: evidence.escrowId as number, + batchId, + }); + + expect(result.outcome).toBe('CONFIRMED'); + if (result.outcome !== 'CONFIRMED') { + // Never submit another transaction to "fix" this. Record and stop. + step('funding.not_confirmed', { outcome: result.outcome, result }); + return; + } + + const escrow = await prisma.escrow.findFirstOrThrow({ + where: { orgId, onChainId: result.escrow.onChainId }, + }); + expect(escrow.managerAddress).toBe(managerAddress); + expect(escrow.financeApproverAddress).toBe(financeAddress); + expect(escrow.totalAmountBaseUnits).toBe(EXPECTED_TOTAL); + + const payments = await prisma.payment.findMany({ + where: { orgId, batchId }, + orderBy: [{ onChainPaymentIndex: 'asc' }], + }); + expect(payments.map((p) => p.onChainPaymentIndex)).toEqual([0, 1, 2]); + expect(payments.every((p) => p.escrowId === escrow.id)).toBe(true); + + step('funding.confirmed', { + escrowDbId: escrow.id, + onChainId: escrow.onChainId, + totalAmountBaseUnits: escrow.totalAmountBaseUnits.toString(), + }); + }, 300_000); + + it('projects the escrow and its three payments through the real indexer', async () => { + const result = await runIndexerFromRpc(); + + const payments = await prisma.payment.findMany({ where: { orgId, batchId } }); + // Three payments, still three. The indexer recognised our rows rather than + // inventing a parallel set. + expect(payments).toHaveLength(3); + expect(payments.every((p) => p.state === PaymentState.AWAITING_ORACLE)).toBe(true); + + const events = await prisma.chainEvent.findMany({ + where: { txHash: evidence.transactionHash as string }, + }); + expect(events.some((e) => e.type === 'created')).toBe(true); + expect(events.filter((e) => e.type === 'payment_added')).toHaveLength(3); + + step('indexer', { + created: events.filter((e) => e.type === 'created').length, + paymentAdded: events.filter((e) => e.type === 'payment_added').length, + paymentsInDb: payments.length, + states: [...new Set(payments.map((p) => p.state))], + result, + }); + }, 300_000); + + it('is accepted by independent reconciliation with zero findings', async () => { + const summary = await runReconciliation(prisma, orgId, {}); + expect('skipped' in summary).toBe(false); + if ('skipped' in summary) return; + + expect(summary.mismatched).toBe(0); + expect(summary.findingsOpened).toBe(0); + + const findings = await prisma.reconciliationFinding.findMany({ where: { orgId } }); + expect(findings).toHaveLength(0); + + step('reconciliation', { + escrowsExamined: summary.escrowsExamined, + paymentsExamined: summary.paymentsExamined, + agreed: summary.agreed, + mismatched: summary.mismatched, + findingsOpened: summary.findingsOpened, + }); + }, 300_000); + + it('refuses a second funding attempt, without submitting anything', async () => { + const batch = await prisma.payrollBatch.findUniqueOrThrow({ + where: { id: batchId }, + select: { id: true, reference: true }, + }); + + // The whole point of the off-chain boundary: the contract would happily create + // a second escrow and move the money again. + await expect(openFundingIntent(prisma, ctx, batch)).rejects.toMatchObject({ status: 409 }); + + const state = await getFundingState(prisma, ctx, batch); + expect(state.assessment.eligible).toBe(false); + expect(state.assessment.blockers.some((b) => b.code === 'ALREADY_FUNDED')).toBe(true); + expect(state.plan).toBeNull(); + + const attempts = await prisma.blockchainTransaction.findMany({ where: { orgId, batchId } }); + expect(attempts).toHaveLength(1); + expect(attempts[0].status).toBe(TxStatus.CONFIRMED); + + step('duplicate.refused', { + attempts: attempts.length, + blocker: 'ALREADY_FUNDED', + }); + }, 120_000); + + it('is idempotent under re-confirmation and indexer replay', async () => { + const record = await prisma.blockchainTransaction.findFirstOrThrow({ + where: { orgId, batchId }, + }); + + const again = await confirmFunding(prisma, ctx, createRpcVerifier(), { + attemptId: record.id, + batchId, + }); + expect(again.outcome).toBe('CONFIRMED'); + + // Replay the indexer over the same ledger range. + await runIndexerFromRpc(); + + expect(await prisma.escrow.count({ where: { orgId } })).toBe(1); + expect(await prisma.payment.count({ where: { orgId, batchId } })).toBe(3); + expect(await prisma.blockchainTransaction.count({ where: { orgId, batchId } })).toBe(1); + expect( + await prisma.auditEvent.count({ where: { orgId, type: 'funding.confirmed' } }), + ).toBe(1); + + const summary = await runReconciliation(prisma, orgId, {}); + if (!('skipped' in summary)) { + expect(summary.mismatched).toBe(0); + expect(summary.findingsOpened).toBe(0); + } + + step('idempotency', { + escrows: 1, + payments: 3, + attempts: 1, + fundingConfirmedEvents: 1, + }); + }, 300_000); +}); diff --git a/src/lib/funding/__tests__/service.test.ts b/src/lib/funding/__tests__/service.test.ts new file mode 100644 index 0000000..16e5ba3 --- /dev/null +++ b/src/lib/funding/__tests__/service.test.ts @@ -0,0 +1,986 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { OrgRole, PaymentState, TxStatus, TxKind } from '@prisma/client'; + +// STELLAR_CONFIG reads process.env at module load, so it is stubbed rather than +// configured after the fact. +// +// The address is written out INSIDE the factory: vi.mock is hoisted above every +// declaration in this file, so a factory closing over a module constant throws +// "Cannot access 'CONTRACT' before initialization". +vi.mock('@/lib/config', () => { + const id = 'CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4'; + return { + STELLAR_CONFIG: { + contract: { id, network: 'testnet' }, + requireContractId: () => id, + networkLabel: () => 'Stellar Testnet', + isMainnet: () => false, + }, + }; +}); + +const CONTRACT = 'CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4'; +const TOKEN = 'CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M'; +vi.mock('@/lib/oracle', () => ({ getOraclePublicKeyHex: () => 'ab'.repeat(32) })); + +import { createFakeDb, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { + planDigest, + readStoredPlan, + openFundingIntent, + recordFundingSubmitted, + failFundingIntent, + confirmFunding, + getFundingState, + selectFinanceApprover, + fundingIdempotencyKey, +} from '../service'; +import type { ChainVerifier } from '@/lib/reconciliation/chain-verifier'; +import type { TenantContext } from '@/lib/tenancy/resolve'; + +const ORG = 'orgA'; +const BATCH = { id: 'bat_1', reference: 'CF-00001' }; + +function wallet(tag: string): string { + return ('G' + tag.toUpperCase().replace(/[^A-Z2-7]/g, '')).padEnd(56, 'A'); +} + +const MANAGER = wallet('manager'); +const FINANCE = wallet('finance'); +const HASH = 'a'.repeat(64); + +function ctxFor(role: OrgRole = OrgRole.MANAGER, address = MANAGER): TenantContext { + return { + orgId: ORG, + orgName: 'Org A', + orgSlug: 'org-a', + userId: 'usr_manager', + walletAddress: address, + role, + }; +} + +let db: FakeDb; + +/** Three payments whose rows satisfy hours x rate == amount. */ +const ROWS = [ + { id: 'pay_1', recipient: wallet('alice'), amount: 10_000_000_000n, rate: 250_000_000n, hours: 40n }, + { id: 'pay_2', recipient: wallet('bob'), amount: 16_000_000_000n, rate: 200_000_000n, hours: 80n }, + { id: 'pay_3', recipient: wallet('carol'), amount: 2_600_000_000n, rate: 130_000_000n, hours: 20n }, +]; +const TOTAL = 28_600_000_000n; + +function seed() { + db.__tables.organization.rows.push({ id: ORG, name: 'Org A', slug: 'org-a' }); + for (const [role, address, userId] of [ + [OrgRole.MANAGER, MANAGER, 'usr_manager'], + [OrgRole.FINANCE, FINANCE, 'usr_finance'], + ] as const) { + db.__tables.user.rows.push({ id: userId, walletAddress: address }); + db.__tables.orgMember.rows.push({ + id: `ogm_${userId}`, + orgId: ORG, + userId, + role, + status: 'ACTIVE', + createdAt: new Date('2026-01-01'), + }); + } + db.__tables.payrollBatch.rows.push({ + id: BATCH.id, + orgId: ORG, + reference: BATCH.reference, + createdAt: new Date(), + }); + for (const [i, r] of ROWS.entries()) { + db.__tables.payment.rows.push({ + id: r.id, + orgId: ORG, + batchId: BATCH.id, + recipientAddress: r.recipient, + assetCode: 'USDC', + assetContractId: TOKEN, + assetDecimals: 7, + amountBaseUnits: r.amount, + rateBaseUnits: r.rate, + hours: r.hours, + periodStart: new Date('2026-09-01T00:00:00Z'), + periodEnd: new Date('2026-09-15T00:00:00Z'), + state: PaymentState.DRAFT, + escrowId: null, + onChainPaymentIndex: null, + createdAt: new Date(Date.now() + i), + }); + } +} + +/** A verifier that agrees with the plan. */ +function agreeingVerifier(over: Partial = {}): ChainVerifier { + return { + readTransactionSucceeded: async () => ({ ok: true, value: true }), + // The recovery anchor: which escrow did this transaction create? + findEscrowsCreatedByTransaction: async () => ({ ok: true, value: [9] }), + readEscrow: async (onChainId: number) => ({ + ok: true, + value: { + onChainId, + manager: MANAGER, + financeApprover: FINANCE, + managerApproved: false, + financeApproved: false, + cancelled: false, + payments: ROWS.map((r, i) => ({ + index: i, + worker: r.recipient, + token: TOKEN, + amountBaseUnits: r.amount, + hours: 0n, + proofVerified: false, + status: 0, + })), + }, + }), + readTransfers: async () => ({ + ok: true, + value: [ + { from: MANAGER, to: CONTRACT, assetContractId: TOKEN, amountBaseUnits: TOTAL, ledger: 100, txHash: HASH }, + ], + }), + latestLedger: async () => ({ ok: true, value: 100 }), + ...over, + }; +} + +beforeEach(() => { + db = createFakeDb(); + seed(); + process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID = TOKEN; + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'USDC'; +}); + +// --------------------------------------------------------------------------- + +describe('selectFinanceApprover', () => { + it('picks a wallet distinct from the funder, preferring FINANCE', async () => { + expect(await selectFinanceApprover(db, ORG, MANAGER)).toBe(FINANCE); + }); + + it('returns null when the only candidate is the funder', async () => { + expect(await selectFinanceApprover(db, ORG, FINANCE)).toBeNull(); + }); +}); + +describe('getFundingState', () => { + it('produces a plan carrying exactly what will be signed', async () => { + const state = await getFundingState(db, ctxFor(), BATCH); + + expect(state.assessment.eligible).toBe(true); + expect(state.assessment.totalBaseUnits).toBe(TOTAL.toString()); + expect(state.assessment.total).toBe('2,860.00'); + + const plan = state.plan!; + expect(plan.manager).toBe(MANAGER); + expect(plan.financeApprover).toBe(FINANCE); + expect(plan.asset).toEqual({ code: 'USDC', contractId: TOKEN, decimals: 7 }); + expect(plan.contractId).toBe(CONTRACT); + // Custody is the contract's own address. + expect(plan.custodyDestination).toBe(CONTRACT); + expect(plan.network).toEqual({ id: 'testnet', label: 'Stellar Testnet', isMainnet: false }); + expect(plan.schedule).toHaveLength(3); + expect(plan.schedule.map((r) => r.worker)).toEqual(ROWS.map((r) => r.recipient)); + // Decimal strings on the wire: a plan crosses into JSON, which has no bigint. + expect(plan.schedule.map((r) => r.amountBaseUnits)).toEqual( + ROWS.map((r) => r.amount.toString()), + ); + expect(plan.schedule.every((r) => typeof r.amountBaseUnits === 'string')).toBe(true); + // Periods as unix seconds, from the stored dates. + expect(plan.schedule[0].startDate).toBe(Math.floor(Date.UTC(2026, 8, 1) / 1000)); + expect(plan.schedule[0].endDate).toBe(Math.floor(Date.UTC(2026, 8, 15) / 1000)); + }); + + it('withholds the plan when the batch is not fundable', async () => { + db.__tables.payment.rows[0].periodStart = null; + const state = await getFundingState(db, ctxFor(), BATCH); + expect(state.assessment.eligible).toBe(false); + // No plan for an unfundable batch: there is nothing safe to sign. + expect(state.plan).toBeNull(); + }); + + it('writes nothing', async () => { + await getFundingState(db, ctxFor(), BATCH); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(0); + expect(db.__tables.auditEvent.rows).toHaveLength(0); + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + }); +}); + +describe('openFundingIntent', () => { + it('opens one attempt, moves payments to VALIDATING, and records it', async () => { + const result = await openFundingIntent(db, ctxFor(), BATCH); + + expect(result.created).toBe(true); + expect(result.attempt.status).toBe(TxStatus.AWAITING_SIGNATURE); + expect(result.attempt.attempt).toBe(1); + + const txs = db.__tables.blockchainTransaction.rows; + expect(txs).toHaveLength(1); + expect(txs[0].kind).toBe(TxKind.INITIALIZE_ESCROW); + expect(txs[0].batchId).toBe(BATCH.id); + expect(txs[0].idempotencyKey).toBe(fundingIdempotencyKey(BATCH.id, 1)); + expect(txs[0].hash).toBeUndefined(); + + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.VALIDATING)).toBe(true); + + const audit = db.__tables.auditEvent.rows.filter((e) => e.type === 'funding.intent.opened'); + expect(audit).toHaveLength(1); + expect(audit[0].metadata.totalBaseUnits).toBe(TOTAL.toString()); + expect(audit[0].metadata.financeApprover).toBe(FINANCE); + }); + + it('returns the SAME attempt on a second call, rather than funding twice', async () => { + const first = await openFundingIntent(db, ctxFor(), BATCH); + const second = await openFundingIntent(db, ctxFor(), BATCH); + + expect(first.created).toBe(true); + expect(second.created).toBe(false); + expect(second.attempt.id).toBe(first.attempt.id); + // One attempt, so one escrow, so one custody transfer. + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + }); + + it('converges on one attempt under concurrent calls', async () => { + const results = await Promise.all([ + openFundingIntent(db, ctxFor(), BATCH), + openFundingIntent(db, ctxFor(), BATCH), + openFundingIntent(db, ctxFor(), BATCH), + ]); + expect(new Set(results.map((r) => r.attempt.id)).size).toBe(1); + expect(results.filter((r) => r.created)).toHaveLength(1); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + }); + + it('refuses an ineligible batch and explains why', async () => { + db.__tables.payment.rows[1].periodEnd = null; + await expect(openFundingIntent(db, ctxFor(), BATCH)).rejects.toMatchObject({ + status: 409, + code: 'STATE_CONFLICT', + }); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(0); + // Nothing moved out of DRAFT. + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + }); + + it('refuses a role that cannot fund', async () => { + await expect( + openFundingIntent(db, ctxFor(OrgRole.VIEWER, wallet('viewer')), BATCH), + ).rejects.toMatchObject({ status: 409 }); + }); +}); + +describe('submission and abandonment', () => { + it('records a submitted hash without claiming success', async () => { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + const attempt = await recordFundingSubmitted(db, ctxFor(), { + attemptId: opened.attempt.id, + transactionHash: HASH, + }); + + expect(attempt.status).toBe(TxStatus.SUBMITTED); + expect(attempt.hash).toBe(HASH); + expect(attempt.confirmedAt).toBeNull(); + // Submitted is not funded: no escrow, and payments have not advanced. + expect(db.__tables.escrow.rows).toHaveLength(0); + expect(db.__tables.payment.rows.every((p) => p.escrowId === null)).toBe(true); + }); + + it('returns payments to DRAFT when the signature is declined', async () => { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + const attempt = await failFundingIntent(db, ctxFor(), { + attemptId: opened.attempt.id, + reason: 'User declined the signature in Freighter', + userRejected: true, + batchId: BATCH.id, + }); + + expect(attempt.status).toBe(TxStatus.CANCELLED); + // Safe only because nothing was submitted. + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + expect(db.__tables.auditEvent.rows.some((e) => e.type === 'funding.declined')).toBe(true); + }); + + it('does NOT rewind payments when a transaction was already submitted', async () => { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await recordFundingSubmitted(db, ctxFor(), { attemptId: opened.attempt.id, transactionHash: HASH, batchId: BATCH.id }); + await failFundingIntent(db, ctxFor(), { + attemptId: opened.attempt.id, + reason: 'RPC timed out while polling', + batchId: BATCH.id, + }); + + // The money may have moved. Quietly marking the batch editable again would + // invite a second funding of an escrow that already exists. + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.VALIDATING)).toBe(true); + }); + + it('allows a fresh attempt after a declined one, with a new idempotency key', async () => { + const first = await openFundingIntent(db, ctxFor(), BATCH); + await failFundingIntent(db, ctxFor(), { + attemptId: first.attempt.id, + reason: 'declined', + userRejected: true, + batchId: BATCH.id, + }); + const second = await openFundingIntent(db, ctxFor(), BATCH); + + expect(second.created).toBe(true); + expect(second.attempt.attempt).toBe(2); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(2); + const keys = db.__tables.blockchainTransaction.rows.map((t) => t.idempotencyKey); + expect(new Set(keys).size).toBe(2); + }); + + it('refuses to abandon a confirmed attempt', async () => { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await recordFundingSubmitted(db, ctxFor(), { attemptId: opened.attempt.id, transactionHash: HASH, batchId: BATCH.id }); + await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId: opened.attempt.id, + onChainEscrowId: 9, + batchId: BATCH.id, + }); + + await expect( + failFundingIntent(db, ctxFor(), { + attemptId: opened.attempt.id, + reason: 'changed my mind', + batchId: BATCH.id, + }), + ).rejects.toMatchObject({ status: 409 }); + }); +}); + +describe('confirmFunding', () => { + async function submitted() { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await recordFundingSubmitted(db, ctxFor(), { attemptId: opened.attempt.id, transactionHash: HASH, batchId: BATCH.id }); + return opened.attempt.id; + } + + it('records funding only after the chain agrees', async () => { + const attemptId = await submitted(); + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + + expect(result.outcome).toBe('CONFIRMED'); + if (result.outcome !== 'CONFIRMED') return; + expect(result.escrow.onChainId).toBe(9); + + const escrow = db.__tables.escrow.rows[0]; + expect(escrow.onChainId).toBe(9); + expect(escrow.managerAddress).toBe(MANAGER); + expect(escrow.financeApproverAddress).toBe(FINANCE); + expect(escrow.tokenAddress).toBe(TOKEN); + expect(escrow.totalAmountBaseUnits).toBe(TOTAL); + + // Every payment is linked to its on-chain slot, in submitted order. This is + // what stops the indexer creating a duplicate set of rows. + const payments = db.__tables.payment.rows; + expect(payments.map((p) => p.onChainPaymentIndex)).toEqual([0, 1, 2]); + expect(payments.every((p) => p.escrowId === escrow.id)).toBe(true); + + const tx = db.__tables.blockchainTransaction.rows[0]; + expect(tx.status).toBe(TxStatus.CONFIRMED); + expect(tx.escrowId).toBe(escrow.id); + + const audit = db.__tables.auditEvent.rows.filter((e) => e.type === 'funding.confirmed'); + expect(audit).toHaveLength(1); + // Attributed to the verifier: this record exists because the chain was read. + expect(audit[0].actorSystem).toBe('funding-verifier'); + expect(audit[0].metadata.custodyDestination).toBe(CONTRACT); + expect(audit[0].metadata.totalBaseUnits).toBe(TOTAL.toString()); + }); + + it('leaves payments in VALIDATING for the indexer to advance', async () => { + const attemptId = await submitted(); + await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9 }); + // Funding links the records; the chain-observing indexer owns the transition to + // AWAITING_ORACLE when it sees payment/add. + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.VALIDATING)).toBe(true); + }); + + it('replays a confirmation instead of recording it twice', async () => { + const attemptId = await submitted(); + await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9 }); + const again = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + + expect(again.outcome).toBe('CONFIRMED'); + expect(db.__tables.escrow.rows).toHaveLength(1); + expect( + db.__tables.auditEvent.rows.filter((e) => e.type === 'funding.confirmed'), + ).toHaveLength(1); + }); + + it('reports FAILED when the chain says the transaction failed', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ readTransactionSucceeded: async () => ({ ok: true, value: false }) }), + { attemptId, onChainEscrowId: 9 }, + ); + + expect(result.outcome).toBe('FAILED'); + expect(db.__tables.escrow.rows).toHaveLength(0); + // Nothing moved, so the batch becomes preparable again. + expect(db.__tables.payment.rows.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + }); + + it('reports UNVERIFIABLE when the chain cannot be read, and records nothing', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + readTransactionSucceeded: async () => ({ + ok: false, + error: { kind: 'UNREADABLE', reason: 'rpc timeout' }, + }), + }), + { attemptId, onChainEscrowId: 9 }, + ); + + expect(result.outcome).toBe('UNVERIFIABLE'); + // Emphatically not FAILED: marking a funded escrow failed because RPC timed out + // would be worse than waiting. + expect(db.__tables.blockchainTransaction.rows[0].status).toBe(TxStatus.SUBMITTED); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('reports MISMATCH and records nothing when the escrow is not the one planned', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + readEscrow: async (onChainId: number) => ({ + ok: true, + value: { + onChainId, + manager: MANAGER, + financeApprover: FINANCE, + managerApproved: false, + financeApproved: false, + cancelled: false, + // Someone else's escrow: a different payee and a different amount. + payments: [ + { index: 0, worker: wallet('attacker'), token: TOKEN, amountBaseUnits: 1n, hours: 0n, proofVerified: false, status: 0 }, + ], + }, + }), + }), + { attemptId, onChainEscrowId: 9 }, + ); + + expect(result.outcome).toBe('MISMATCH'); + if (result.outcome !== 'MISMATCH') return; + expect(result.differences.length).toBeGreaterThan(0); + // The escrow is NOT adopted as this batch's: it is not the escrow we asked for. + expect(db.__tables.escrow.rows).toHaveLength(0); + expect(db.__tables.payment.rows.every((p) => p.escrowId === null)).toBe(true); + expect(db.__tables.auditEvent.rows.some((e) => e.type === 'funding.mismatch')).toBe(true); + }); + + it('reports MISMATCH when the on-chain escrow is cancelled', async () => { + const attemptId = await submitted(); + const base = agreeingVerifier(); + const result = await confirmFunding( + db, + ctxFor(), + { + ...base, + readEscrow: async (id: number) => { + const r = await base.readEscrow(id); + if (!r.ok) return r; + return { ok: true, value: { ...r.value, cancelled: true } }; + }, + }, + { attemptId, onChainEscrowId: 9 }, + ); + expect(result.outcome).toBe('MISMATCH'); + }); + + it('does not confirm when no custody transfer is found in that transaction', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + // The escrow exists, but no transfer is observable for this hash. + readTransfers: async () => ({ ok: true, value: [] }), + }), + { attemptId, onChainEscrowId: 9 }, + ); + + expect(result.outcome).toBe('UNVERIFIABLE'); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('does not confirm when the transfer amount differs from the plan', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + readTransfers: async () => ({ + ok: true, + value: [ + { from: MANAGER, to: CONTRACT, assetContractId: TOKEN, amountBaseUnits: 1n, ledger: 100, txHash: HASH }, + ], + }), + }), + { attemptId, onChainEscrowId: 9 }, + ); + expect(result.outcome).toBe('UNVERIFIABLE'); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('refuses to verify an attempt with no transaction hash', async () => { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await expect( + confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId: opened.attempt.id, + onChainEscrowId: 9, + }), + ).rejects.toMatchObject({ status: 409 }); + }); + + it('links to an escrow the indexer already created, rather than duplicating it', async () => { + const attemptId = await submitted(); + db.__tables.escrow.rows.push({ + id: 'esc_indexer', + orgId: ORG, + onChainId: 9, + contractId: CONTRACT, + network: 'testnet', + managerAddress: MANAGER, + financeApproverAddress: FINANCE, + assetDecimals: 7, + totalAmountBaseUnits: 0n, + createdAt: new Date(), + }); + + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + + expect(result.outcome).toBe('CONFIRMED'); + // onChainId is unique: whoever got there first wins and the other links to it. + expect(db.__tables.escrow.rows).toHaveLength(1); + expect(db.__tables.payment.rows.every((p) => p.escrowId === 'esc_indexer')).toBe(true); + }); +}); + +describe('the stored plan is the authority', () => { + async function submitted() { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await recordFundingSubmitted(db, ctxFor(), { attemptId: opened.attempt.id, transactionHash: HASH, batchId: BATCH.id }); + return opened.attempt.id; + } + + it('persists the plan and its digest when the intent is opened', async () => { + await openFundingIntent(db, ctxFor(), BATCH); + const tx = db.__tables.blockchainTransaction.rows[0]; + + expect(tx.plan).toBeDefined(); + expect(tx.planDigest).toMatch(/^[0-9a-f]{64}$/); + expect(planDigest(tx.plan)).toBe(tx.planDigest); + + // Money inside the JSON is a decimal string, never a Number. + expect(tx.plan.totalBaseUnits).toBe(TOTAL.toString()); + expect(tx.plan.rows).toHaveLength(3); + for (const row of tx.plan.rows) { + expect(typeof row.amountBaseUnits).toBe('string'); + expect(typeof row.rateBaseUnits).toBe('string'); + } + expect(tx.plan.manager).toBe(MANAGER); + expect(tx.plan.financeApprover).toBe(FINANCE); + expect(tx.plan.custodyDestination).toBe(CONTRACT); + expect(tx.plan.network).toBe('testnet'); + }); + + it('refuses a plan whose digest no longer matches it', async () => { + const attemptId = await submitted(); + const tx = db.__tables.blockchainTransaction.rows[0]; + // Somebody edited the stored JSON to pay a different wallet. + tx.plan = { ...tx.plan, rows: [{ ...tx.plan.rows[0], worker: wallet('attacker') }, ...tx.plan.rows.slice(1)] }; + + await expect( + confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9 }), + ).rejects.toMatchObject({ status: 409 }); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('refuses to verify an attempt that stored no plan', async () => { + const attemptId = await submitted(); + const tx = db.__tables.blockchainTransaction.rows[0]; + tx.plan = null; + tx.planDigest = null; + + await expect( + confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9 }), + ).rejects.toMatchObject({ status: 409 }); + }); + + it('detects an escrow paying a recipient the plan never named', async () => { + const attemptId = await submitted(); + const base = agreeingVerifier(); + const result = await confirmFunding( + db, + ctxFor(), + { + ...base, + readEscrow: async (id: number) => { + const r = await base.readEscrow(id); + if (!r.ok) return r; + const payments = [...r.value.payments]; + payments[1] = { ...payments[1], worker: wallet('attacker') }; + return { ok: true, value: { ...r.value, payments } }; + }, + }, + { attemptId, onChainEscrowId: 9 }, + ); + + expect(result.outcome).toBe('MISMATCH'); + if (result.outcome !== 'MISMATCH') return; + expect(result.differences.some((d) => d.includes('the plan says'))).toBe(true); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('detects an escrow whose finance approver is not the planned one', async () => { + const attemptId = await submitted(); + const base = agreeingVerifier(); + const result = await confirmFunding( + db, + ctxFor(), + { + ...base, + readEscrow: async (id: number) => { + const r = await base.readEscrow(id); + if (!r.ok) return r; + return { ok: true, value: { ...r.value, financeApprover: wallet('someoneelse') } }; + }, + }, + { attemptId, onChainEscrowId: 9 }, + ); + expect(result.outcome).toBe('MISMATCH'); + }); + + it('detects an escrow whose manager and finance approver are the same key', async () => { + const attemptId = await submitted(); + const base = agreeingVerifier(); + const result = await confirmFunding( + db, + ctxFor(), + { + ...base, + readEscrow: async (id: number) => { + const r = await base.readEscrow(id); + if (!r.ok) return r; + return { ok: true, value: { ...r.value, financeApprover: MANAGER } }; + }, + }, + { attemptId, onChainEscrowId: 9 }, + ); + // Dual control is vacuous if one key holds both halves; the contract refuses it + // at creation, and an escrow that somehow had it must not be adopted. + expect(result.outcome).toBe('MISMATCH'); + }); + + it('detects a payment edited after the plan was frozen', async () => { + const attemptId = await submitted(); + // The chain still matches the plan, but the database no longer does. + db.__tables.payment.rows[0].amountBaseUnits = 99_999_999_999n; + + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + + expect(result.outcome).toBe('MISMATCH'); + if (result.outcome !== 'MISMATCH') return; + expect(result.differences.some((d) => d.includes('altered since the plan'))).toBe(true); + // Adopting it would attach an escrow to payments nobody authorised. + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('detects a payment removed from the batch after the plan was frozen', async () => { + const attemptId = await submitted(); + db.__tables.payment.rows.splice(2, 1); + + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + expect(result.outcome).toBe('MISMATCH'); + }); + + it('opens a CRITICAL finding on mismatch, preserving the evidence', async () => { + const attemptId = await submitted(); + const base = agreeingVerifier(); + await confirmFunding( + db, + ctxFor(), + { + ...base, + readEscrow: async (id: number) => { + const r = await base.readEscrow(id); + if (!r.ok) return r; + return { ok: true, value: { ...r.value, payments: [] } }; + }, + }, + { attemptId, onChainEscrowId: 9 }, + ); + + const findings = db.__tables.reconciliationFinding.rows; + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('CRITICAL'); + expect(findings[0].detail).toContain('was NOT adopted'); + expect(findings[0].chainState).toBe('escrow:9'); + }); + + it('verifies custody against the plan asset, not current configuration', async () => { + const attemptId = await submitted(); + // The operator switches the settlement asset while the transaction is pending. + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'EURC'; + try { + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { attemptId, onChainEscrowId: 9, batchId: BATCH.id }); + // The plan said USDC, the chain says USDC, so this confirms β€” a recomputed + // plan would have disagreed with both. + expect(result.outcome).toBe('CONFIRMED'); + expect(db.__tables.escrow.rows[0].tokenAddress).toBe(TOKEN); + } finally { + process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE = 'USDC'; + } + }); + + it('readStoredPlan round-trips a plan it considers valid', async () => { + await openFundingIntent(db, ctxFor(), BATCH); + const tx = db.__tables.blockchainTransaction.rows[0]; + const plan = readStoredPlan({ plan: tx.plan, planDigest: tx.planDigest }); + expect(plan.rows.map((r) => r.worker)).toEqual(ROWS.map((r) => r.recipient)); + expect(BigInt(plan.totalBaseUnits)).toBe(TOTAL); + }); +}); + +describe('an attempt is scoped to its batch', () => { + it('is not actionable through a different batch', async () => { + db.__tables.payrollBatch.rows.push({ + id: 'bat_other', + orgId: ORG, + reference: 'CF-00002', + createdAt: new Date(), + }); + const opened = await openFundingIntent(db, ctxFor(), BATCH); + + // The attempt exists and belongs to this organization, but not to this batch. + // Without the batch filter, naming its id on another batch's route would act on + // it β€” and the tests would not have noticed, because tsconfig excludes test + // files from typechecking and `where: { batchId: undefined }` means "no filter". + await expect( + recordFundingSubmitted(db, ctxFor(), { + attemptId: opened.attempt.id, + transactionHash: HASH, + batchId: 'bat_other', + }), + ).rejects.toMatchObject({ status: 404 }); + + await expect( + failFundingIntent(db, ctxFor(), { + attemptId: opened.attempt.id, + reason: 'wrong batch', + batchId: 'bat_other', + }), + ).rejects.toMatchObject({ status: 404 }); + + await expect( + confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId: opened.attempt.id, + onChainEscrowId: 9, + batchId: 'bat_other', + }), + ).rejects.toMatchObject({ status: 404 }); + + // Untouched. + expect(db.__tables.blockchainTransaction.rows[0].status).toBe(TxStatus.AWAITING_SIGNATURE); + }); +}); + +describe('the funding state is JSON-serializable', () => { + it('survives JSON.stringify, as the GET route requires', async () => { + const state = await getFundingState(db, ctxFor(), BATCH); + // NextResponse.json throws "Do not know how to serialize a BigInt", so a bigint + // anywhere in this payload is a 500 at runtime and nothing catches it earlier. + expect(() => JSON.stringify(state)).not.toThrow(); + + const round = JSON.parse(JSON.stringify(state)); + expect(round.assessment.totalBaseUnits).toBe(TOTAL.toString()); + expect(round.plan.schedule).toHaveLength(3); + for (const row of round.plan.schedule) { + expect(typeof row.amountBaseUnits).toBe('string'); + expect(typeof row.rateBaseUnits).toBe('string'); + } + }); + + it('survives stringify when the batch is not fundable', async () => { + db.__tables.payment.rows[0].periodStart = null; + const state = await getFundingState(db, ctxFor(), BATCH); + expect(() => JSON.stringify(state)).not.toThrow(); + expect(JSON.parse(JSON.stringify(state)).plan).toBeNull(); + }); +}); + +describe('recovering the escrow from the transaction hash', () => { + async function submitted() { + const opened = await openFundingIntent(db, ctxFor(), BATCH); + await recordFundingSubmitted(db, ctxFor(), { + attemptId: opened.attempt.id, + transactionHash: HASH, + batchId: BATCH.id, + }); + return opened.attempt.id; + } + + /** + * The mandatory case: the transaction was accepted, the return value could not be + * read, and the user must NOT have to sign again. The hash is the durable anchor. + */ + it('confirms with no escrow id supplied, resolving it from the transaction', async () => { + const attemptId = await submitted(); + + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId, + batchId: BATCH.id, + // Deliberately absent, as when `scValToNative` on the return value fails. + }); + + expect(result.outcome).toBe('CONFIRMED'); + if (result.outcome !== 'CONFIRMED') return; + expect(result.escrow.onChainId).toBe(9); + + // One escrow, one attempt, one transaction record. Recovery created nothing new. + expect(db.__tables.escrow.rows).toHaveLength(1); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + expect(db.__tables.payment.rows.map((p) => p.onChainPaymentIndex)).toEqual([0, 1, 2]); + + const audit = db.__tables.auditEvent.rows.find((e) => e.type === 'funding.confirmed'); + expect(audit.metadata.escrowResolvedFrom).toBe('chain-event'); + }); + + it('prefers the indexer record over RPC, and works when RPC is unreadable', async () => { + const attemptId = await submitted(); + db.__tables.chainEvent.rows.push({ + id: 'cev_1', + txHash: HASH, + type: 'created', + contractId: CONTRACT, + network: 'testnet', + escrowOnChainId: 9, + ledger: 100, + }); + + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + // RPC event history has aged out; the indexed record still answers. + findEscrowsCreatedByTransaction: async () => ({ + ok: false, + error: { kind: 'UNREADABLE', reason: 'event retention exceeded' }, + }), + }), + { attemptId, batchId: BATCH.id }, + ); + + expect(result.outcome).toBe('CONFIRMED'); + const audit = db.__tables.auditEvent.rows.find((e) => e.type === 'funding.confirmed'); + expect(audit.metadata.escrowResolvedFrom).toBe('indexed-event'); + }); + + it('is UNVERIFIABLE, not FAILED, while no escrow/created event is visible yet', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ findEscrowsCreatedByTransaction: async () => ({ ok: true, value: [] }) }), + { attemptId, batchId: BATCH.id }, + ); + + expect(result.outcome).toBe('UNVERIFIABLE'); + // The attempt keeps blocking a second one, which is the point. + expect(db.__tables.blockchainTransaction.rows[0].status).toBe(TxStatus.SUBMITTED); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('is repeatable: recovery twice yields one escrow and one attempt', async () => { + const attemptId = await submitted(); + const first = await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId, + batchId: BATCH.id, + }); + const second = await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId, + batchId: BATCH.id, + }); + + expect(first.outcome).toBe('CONFIRMED'); + expect(second.outcome).toBe('CONFIRMED'); + expect(db.__tables.escrow.rows).toHaveLength(1); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + expect( + db.__tables.auditEvent.rows.filter((e) => e.type === 'funding.confirmed'), + ).toHaveLength(1); + }); + + it('refuses a client-supplied escrow id the transaction did not create', async () => { + const attemptId = await submitted(); + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId, + batchId: BATCH.id, + // Transaction A pointed at an escrow from transaction B. + onChainEscrowId: 777, + }); + + expect(result.outcome).toBe('MISMATCH'); + if (result.outcome !== 'MISMATCH') return; + expect(result.differences[0]).toContain('777'); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('refuses ambiguity rather than choosing an escrow', async () => { + const attemptId = await submitted(); + const result = await confirmFunding( + db, + ctxFor(), + agreeingVerifier({ + findEscrowsCreatedByTransaction: async () => ({ ok: true, value: [9, 10] }), + }), + { attemptId, batchId: BATCH.id }, + ); + + expect(result.outcome).toBe('MISMATCH'); + expect(db.__tables.escrow.rows).toHaveLength(0); + }); + + it('ignores an indexed event from another contract or network', async () => { + const attemptId = await submitted(); + db.__tables.chainEvent.rows.push({ + id: 'cev_other', + txHash: HASH, + type: 'created', + contractId: 'C' + 'Z'.repeat(55), + network: 'testnet', + escrowOnChainId: 4242, + ledger: 100, + }); + + const result = await confirmFunding(db, ctxFor(), agreeingVerifier(), { + attemptId, + batchId: BATCH.id, + }); + + // Falls through to the chain, which names escrow 9 β€” not the foreign 4242. + expect(result.outcome).toBe('CONFIRMED'); + if (result.outcome !== 'CONFIRMED') return; + expect(result.escrow.onChainId).toBe(9); + }); +}); diff --git a/src/lib/funding/client.ts b/src/lib/funding/client.ts new file mode 100644 index 0000000..7e282ae --- /dev/null +++ b/src/lib/funding/client.ts @@ -0,0 +1,201 @@ +/** + * Browser-side funding client. + * + * The one rule this file exists to enforce: **the browser displays the + * server-issued plan and signs it. It never constructs a financial intent of its + * own.** Nothing here computes a total, converts a decimal, derives an amount, or + * decides what an escrow should contain. Every figure shown and every value signed + * comes from the plan the server froze when the intent was opened. + * + * Money crosses the wire as decimal STRINGS and is converted to `bigint` only at + * the point of building the contract arguments. A `Number` anywhere here would + * reintroduce the rounding the rest of the system refuses. + */ + +'use client'; + +import type { FundingStateView, FundingPlan, FundingAttemptView } from './service'; + +export type { FundingStateView, FundingPlan, FundingAttemptView }; + +/** Every outcome the confirm endpoint can report. Never collapsed in the UI. */ +export type ConfirmOutcome = 'CONFIRMED' | 'FAILED' | 'UNVERIFIABLE' | 'MISMATCH'; + +export interface ConfirmResult { + outcome: ConfirmOutcome; + attempt: FundingAttemptView; + escrow?: { id: string; onChainId: number }; + reason?: string; + differences?: string[]; +} + +export class FundingRequestError extends Error { + constructor( + readonly status: number, + message: string, + readonly code?: string, + readonly details?: unknown, + ) { + super(message); + this.name = 'FundingRequestError'; + } +} + +async function request(url: string, init?: RequestInit): Promise { + const response = await fetch(url, { + ...init, + headers: { + ...(init?.body ? { 'content-type': 'application/json' } : {}), + ...init?.headers, + }, + }); + + const text = await response.text(); + let body: any = null; + try { + body = text ? JSON.parse(text) : null; + } catch { + // A non-JSON body from a proxy or gateway. The status is still meaningful. + } + + if (!response.ok) { + throw new FundingRequestError( + response.status, + body?.error ?? `The request failed (${response.status}).`, + body?.code, + body?.details, + ); + } + return body as T; +} + +const base = (batchId: string) => + `/api/payroll/batches/${encodeURIComponent(batchId)}/funding`; + +/** + * Current funding state: blockers, the plan, and any attempt already in flight. + * + * Called on mount, so a reload during submission recovers the existing intent + * instead of starting a second one. + */ +export function getFundingState(batchId: string, orgId?: string): Promise { + const query = orgId ? `?orgId=${encodeURIComponent(orgId)}` : ''; + return request(`${base(batchId)}${query}`, { method: 'GET' }); +} + +export interface OpenIntentResponse { + created: boolean; + attempt: FundingAttemptView; + plan: FundingPlan; +} + +/** Open or recover the funding intent. Idempotent: a second call returns the first. */ +export function openFundingIntent( + batchId: string, + orgId?: string, +): Promise { + return request(`${base(batchId)}/intent`, { + method: 'POST', + body: JSON.stringify(orgId ? { orgId } : {}), + }); +} + +/** Record a hash the network accepted. Deliberately does not claim funding. */ +export function recordSubmitted( + batchId: string, + input: { attemptId: string; transactionHash: string; orgId?: string }, +): Promise<{ attempt: FundingAttemptView }> { + return request(`${base(batchId)}/submitted`, { + method: 'POST', + body: JSON.stringify({ + attemptId: input.attemptId, + transactionHash: input.transactionHash, + ...(input.orgId ? { orgId: input.orgId } : {}), + }), + }); +} + +/** Ask the server to verify the transaction against the frozen plan. */ +export function confirmFunding( + batchId: string, + input: { attemptId: string; onChainEscrowId?: number; orgId?: string }, +): Promise { + return request(`${base(batchId)}/confirm`, { + method: 'POST', + body: JSON.stringify({ + attemptId: input.attemptId, + // Omitted when the client could not read it. The server resolves the escrow + // from the transaction hash, so a missing id is a recoverable gap, not a + // reason to sign again. + ...(input.onChainEscrowId !== undefined + ? { onChainEscrowId: input.onChainEscrowId } + : {}), + ...(input.orgId ? { orgId: input.orgId } : {}), + }), + }); +} + +/** Close an attempt that never reached the network. */ +export function abandonFunding( + batchId: string, + input: { attemptId: string; reason: string; userRejected?: boolean; orgId?: string }, +): Promise<{ attempt: FundingAttemptView }> { + return request(`${base(batchId)}/abandon`, { + method: 'POST', + body: JSON.stringify({ + attemptId: input.attemptId, + reason: input.reason, + ...(input.userRejected ? { userRejected: true } : {}), + ...(input.orgId ? { orgId: input.orgId } : {}), + }), + }); +} + +/** + * Turn the server's plan into contract arguments, verbatim. + * + * A 1:1 mapping with no arithmetic and no reordering. Order is load-bearing: + * verification compares the on-chain payment vector against the plan BY INDEX, so + * reordering here would read as a mismatch β€” correctly, because it would mean the + * signed transaction was not the reviewed one. + */ +export function planToContractArguments(plan: FundingPlan): { + manager: string; + financeApprover: string; + oraclePublicKeyHex: string; + payments: { + worker: string; + token: string; + amount: bigint; + rate_per_hour: bigint; + start_date: number; + end_date: number; + }[]; +} { + return { + manager: plan.manager, + financeApprover: plan.financeApprover, + oraclePublicKeyHex: plan.oraclePublicKey, + payments: plan.schedule.map((row) => ({ + worker: row.worker, + token: row.token, + // Strings to bigint. The only conversion in this file, and it is exact. + amount: BigInt(row.amountBaseUnits), + rate_per_hour: BigInt(row.rateBaseUnits), + start_date: row.startDate, + end_date: row.endDate, + })), + }; +} + +/** + * A short, human-quotable reference for the frozen plan. + * + * Exists so a finance user can say which plan they signed without reading 64 hex + * characters aloud. The full digest remains available in technical details; this is + * a label, never an identifier the server trusts. + */ +export function planReference(planDigest: string | null | undefined): string | null { + if (!planDigest || planDigest.length < 8) return null; + return `CF-PLAN-${planDigest.slice(0, 8).toUpperCase()}`; +} diff --git a/src/lib/funding/eligibility.ts b/src/lib/funding/eligibility.ts new file mode 100644 index 0000000..1fbaae4 --- /dev/null +++ b/src/lib/funding/eligibility.ts @@ -0,0 +1,311 @@ +/** + * When may a batch be funded? + * + * ── The fact that shapes this whole module ────────────────────────────────── + * `initialize_multi_sig_escrow` creates the escrow AND pulls custody in ONE + * atomic invocation. There is no separate `fund()` in the contract, and + * `CoreFlowEscrow` has no `funded` flag: an escrow EXISTS if and only if its + * custody was transferred. + * + * Two consequences: + * + * 1. "Created but not funded" is not a representable state. There is one + * signature, not two. + * 2. Submitting it twice creates a SECOND escrow and moves the money AGAIN. The + * contract offers no idempotency, so everything that stops a manager being + * charged twice lives off-chain β€” which is why eligibility is assessed before a + * wallet ever opens, and why an in-flight attempt blocks a second one. + * + * Deliberately pure: it takes already-loaded records and returns a verdict. The + * question "may this money move?" should be answerable in a test without a + * database, an RPC endpoint or a wallet. + */ + +import { PaymentState, OrgRole } from '@prisma/client'; +import { formatAmount } from '@/lib/money'; +import type { SettlementAsset } from '@/lib/payroll/assets'; + +/** The contract's MAX_BATCH_SIZE. A larger batch cannot be created at all. */ +export const MAX_FUNDABLE_PAYMENTS = 100; + +/** States from which a payment may enter funding. */ +const FUNDABLE_STATES: readonly PaymentState[] = [ + PaymentState.DRAFT, + // A retry after a failed attempt: the previous intent already moved these. + PaymentState.VALIDATING, +]; + +/** Roles permitted to fund, mirroring `escrow:create`. */ +export const FUNDING_ROLES: readonly OrgRole[] = [OrgRole.OWNER, OrgRole.ADMIN, OrgRole.MANAGER]; + +export type FundingBlockerCode = + | 'NO_PAYMENTS' + | 'TOO_MANY_PAYMENTS' + | 'ALREADY_FUNDED' + | 'FUNDING_IN_FLIGHT' + | 'PAYMENT_NOT_FUNDABLE' + | 'PAYMENT_ALREADY_ON_CHAIN' + | 'SETTLEMENT_ASSET_UNCONFIGURED' + | 'ASSET_MISMATCH' + | 'MIXED_ASSETS' + | 'AMOUNT_NOT_POSITIVE' + | 'HOURS_RATE_MISMATCH' + | 'PERIOD_REQUIRED' + | 'PERIOD_INVALID' + | 'NO_DISTINCT_FINANCE_APPROVER' + | 'ORACLE_KEY_UNAVAILABLE' + | 'ROLE_NOT_PERMITTED'; + +export interface FundingBlocker { + code: FundingBlockerCode; + /** What to do about it, in the operator's terms. */ + message: string; + paymentId?: string; + /** 1-based position within the batch, for a human scanning a table. */ + position?: number; +} + +/** The payment fields funding reads. Typed, never `any`: `any * any` is `number`. */ +export interface FundingPayment { + id: string; + recipientAddress: string; + assetCode: string; + assetContractId: string | null; + assetDecimals: number; + amountBaseUnits: bigint; + rateBaseUnits: bigint; + hours: bigint; + periodStart: Date | null; + periodEnd: Date | null; + state: PaymentState; + escrowId: string | null; + onChainPaymentIndex: number | null; +} + +export interface ExistingFundingAttempt { + id: string; + status: 'PREPARING' | 'SIMULATING' | 'AWAITING_SIGNATURE' | 'SUBMITTED' | 'CONFIRMED' | 'FAILED' | 'EXPIRED' | 'CANCELLED'; + hash: string | null; + createdAt: Date; +} + +export interface EligibilityInput { + payments: readonly FundingPayment[]; + asset: SettlementAsset; + /** The wallet that will sign, and the role it holds. */ + funder: { walletAddress: string; role: OrgRole }; + /** A second, distinct wallet for the finance half of the gate. */ + financeApproverAddress: string | null; + /** The most recent funding attempt for this batch, if any. */ + attempt: ExistingFundingAttempt | null; + oraclePublicKey: string | null; +} + +export interface FundingAssessment { + eligible: boolean; + blockers: FundingBlocker[]; + /** Exact total to be pulled into custody, in base units. */ + totalBaseUnits: bigint; + paymentCount: number; +} + +/** Statuses in which an attempt is neither finished nor abandoned. */ +const IN_FLIGHT = new Set(['PREPARING', 'SIMULATING', 'AWAITING_SIGNATURE', 'SUBMITTED']); + +function short(address: string): string { + return `${address.slice(0, 6)}…${address.slice(-4)}`; +} + +/** + * Decide whether this batch may be funded, and say exactly why not if it may not. + * + * Every blocker is returned, not just the first: an operator fixing one problem at + * a time across a twelve-row payroll cannot work, and the wallet prompt is the + * worst possible place to discover the thirteenth. + */ +export function assessFundingEligibility(input: EligibilityInput): FundingAssessment { + const blockers: FundingBlocker[] = []; + const { payments, asset, funder, attempt } = input; + + // ── Who is asking ── + if (!FUNDING_ROLES.includes(funder.role)) { + blockers.push({ + code: 'ROLE_NOT_PERMITTED', + message: + `Your role (${funder.role}) cannot fund a payroll. Funding moves money into ` + + 'escrow custody and is performed by the escrow manager.', + }); + } + + // ── Has this already happened, or is it happening now ── + if (attempt && attempt.status === 'CONFIRMED') { + blockers.push({ + code: 'ALREADY_FUNDED', + message: + 'This batch has already been funded on-chain' + + (attempt.hash ? ` (transaction ${attempt.hash.slice(0, 12)}…)` : '') + + '. Funding it again would create a second escrow and move the money a ' + + 'second time.', + }); + } else if (attempt && IN_FLIGHT.has(attempt.status)) { + blockers.push({ + code: 'FUNDING_IN_FLIGHT', + message: + `A funding attempt started ${attempt.createdAt.toISOString()} has not finished ` + + `(${attempt.status}). Wait for it to confirm or fail before starting another β€” ` + + 'two attempts would fund two escrows.', + }); + } + + // ── Is there anything to fund ── + if (payments.length === 0) { + blockers.push({ code: 'NO_PAYMENTS', message: 'This batch has no payments.' }); + } + if (payments.length > MAX_FUNDABLE_PAYMENTS) { + blockers.push({ + code: 'TOO_MANY_PAYMENTS', + message: + `${payments.length} payments exceeds the ${MAX_FUNDABLE_PAYMENTS} the contract ` + + 'accepts in one escrow. Split the payroll.', + }); + } + + // ── The settlement asset ── + if (asset.contractId === null) { + blockers.push({ + code: 'SETTLEMENT_ASSET_UNCONFIGURED', + message: + `No Stellar Asset Contract is configured for ${asset.code}, so there is no ` + + 'address to move funds to. CoreFlow will not infer one from an asset symbol.', + }); + } + + const assetCodes = new Set(payments.map((p) => p.assetCode)); + if (assetCodes.size > 1) { + blockers.push({ + code: 'MIXED_ASSETS', + message: + `This batch mixes ${[...assetCodes].join(' and ')}. One escrow holds one ` + + 'asset, so a mixed batch needs one escrow per asset.', + }); + } + + // ── Each payment ── + let total = 0n; + for (const [i, p] of payments.entries()) { + const position = i + 1; + const where = `${short(p.recipientAddress)} (row ${position})`; + + if (!FUNDABLE_STATES.includes(p.state)) { + blockers.push({ + code: 'PAYMENT_NOT_FUNDABLE', + paymentId: p.id, + position, + message: `${where} is ${p.state} and is not awaiting funding.`, + }); + } + + // Already attached to an escrow: funding again would pay twice. + if (p.escrowId !== null || p.onChainPaymentIndex !== null) { + blockers.push({ + code: 'PAYMENT_ALREADY_ON_CHAIN', + paymentId: p.id, + position, + message: `${where} is already attached to an on-chain escrow.`, + }); + } + + if (p.assetCode !== asset.code) { + blockers.push({ + code: 'ASSET_MISMATCH', + paymentId: p.id, + position, + message: + `${where} is denominated in ${p.assetCode}, but this deployment settles ` + + `${asset.code}.`, + }); + } + + if (p.amountBaseUnits <= 0n || p.rateBaseUnits <= 0n || p.hours <= 0n) { + blockers.push({ + code: 'AMOUNT_NOT_POSITIVE', + paymentId: p.id, + position, + message: `${where} has a non-positive amount, rate or hours.`, + }); + } else if (p.hours * p.rateBaseUnits !== p.amountBaseUnits) { + // The contract rejects this (AmountHoursMismatch) and `submit_hours_proof` + // could never be satisfied, so the escrow would be funded and unsettleable. + blockers.push({ + code: 'HOURS_RATE_MISMATCH', + paymentId: p.id, + position, + message: + `${where} has an amount of ${formatAmount(p.amountBaseUnits, p.assetDecimals)} ` + + `but ${p.hours} hours at ${formatAmount(p.rateBaseUnits, p.assetDecimals)} is ` + + `${formatAmount(p.hours * p.rateBaseUnits, p.assetDecimals)}.`, + }); + } + + // The contract requires end_date > start_date (InvalidPeriod), and the period is + // a SIGNED field of the oracle proof. A missing period cannot be filled in here: + // attesting to a pay period nobody stated is exactly the kind of invented + // financial data this system refuses. + if (p.periodStart === null || p.periodEnd === null) { + blockers.push({ + code: 'PERIOD_REQUIRED', + paymentId: p.id, + position, + message: + `${where} has no pay period. The contract requires one, and the period is ` + + 'part of what the oracle signs β€” it cannot be assumed. Add period_start ' + + 'and period_end to the payroll file.', + }); + } else if (p.periodEnd.getTime() <= p.periodStart.getTime()) { + blockers.push({ + code: 'PERIOD_INVALID', + paymentId: p.id, + position, + message: `${where} has a pay period that does not end after it starts.`, + }); + } + + total += p.amountBaseUnits; + } + + // ── Dual control, before a wallet opens ── + if (input.financeApproverAddress === null) { + blockers.push({ + code: 'NO_DISTINCT_FINANCE_APPROVER', + message: + 'This organization has no second wallet to act as the finance approver. ' + + 'CoreFlow requires two distinct approvers, and the contract refuses an ' + + 'escrow whose manager and finance approver are the same key.', + }); + } else if (input.financeApproverAddress === funder.walletAddress) { + blockers.push({ + code: 'NO_DISTINCT_FINANCE_APPROVER', + message: + 'You would be both the manager and the finance approver on this escrow. ' + + 'The contract rejects that (SignersNotDistinct): invite or assign a second ' + + 'approver first.', + }); + } + + if (!input.oraclePublicKey) { + blockers.push({ + code: 'ORACLE_KEY_UNAVAILABLE', + message: + 'The oracle signing key is unavailable, so work could never be verified for ' + + 'this escrow. Funding is refused rather than creating an escrow that can ' + + 'never settle.', + }); + } + + return { + eligible: blockers.length === 0, + blockers, + totalBaseUnits: total, + paymentCount: payments.length, + }; +} diff --git a/src/lib/funding/service.ts b/src/lib/funding/service.ts new file mode 100644 index 0000000..9df6009 --- /dev/null +++ b/src/lib/funding/service.ts @@ -0,0 +1,1289 @@ +/** + * The funding bridge: an approved draft becomes a funded on-chain escrow. + * + * ── One signature, not two ────────────────────────────────────────────────── + * `initialize_multi_sig_escrow` creates the escrow AND pulls custody atomically. + * There is no `fund()` and no `funded` flag β€” an escrow exists iff its custody + * moved. So the lifecycle has ONE wallet interaction, and "created but unfunded" + * is not a state this product can display, because it is not a state the contract + * can be in. + * + * ── Why an intent record exists ───────────────────────────────────────────── + * Because the contract is not idempotent. A second submission creates a second + * escrow and moves the money again. Nothing on-chain prevents that, so a + * BlockchainTransaction row is opened BEFORE the wallet opens, keyed + * `fund:batch:`, and its UNIQUE index is what makes a double-click, a refresh + * or a second tab impossible to turn into a double payment. A disabled button is + * not a control. + * + * ── Why the chain is re-read afterwards ───────────────────────────────────── + * Freighter returning is not evidence. The client builds and signs the + * transaction, so it could submit something other than the plan. Funding is + * recorded only after reading back: the transaction succeeded, the escrow exists + * with the planned manager, finance approver and payments, and a transfer of the + * exact total reached the contract's own address in that transaction. + */ + +import { createHash } from 'node:crypto'; +import { PaymentState, TxKind, TxStatus, OrgRole, MembershipStatus } from '@prisma/client'; +import { ApiError } from '@/lib/api/errors'; +import { formatAmountWithSeparators } from '@/lib/money'; +import { recordAuditEvent } from '@/lib/payments/service'; +import { settlementAsset, type SettlementAsset } from '@/lib/payroll/assets'; +import { getOraclePublicKeyHex } from '@/lib/oracle'; +import { STELLAR_CONFIG } from '@/lib/config'; +import type { ChainVerifier } from '@/lib/reconciliation/chain-verifier'; +import type { TenantContext } from '@/lib/tenancy/resolve'; +import { + assessFundingEligibility, + FUNDING_ROLES, + type FundingAssessment, + type FundingPayment, +} from './eligibility'; + +/** Statuses in which an attempt is neither finished nor abandoned. */ +const IN_FLIGHT: readonly TxStatus[] = [ + TxStatus.PREPARING, + TxStatus.SIMULATING, + TxStatus.AWAITING_SIGNATURE, + TxStatus.SUBMITTED, +]; + +/** The idempotency key for a batch's funding. One per batch, per attempt number. */ +export function fundingIdempotencyKey(batchId: string, attempt: number): string { + return attempt <= 1 ? `fund:batch:${batchId}` : `fund:batch:${batchId}:retry:${attempt}`; +} + +/** + * A single row as the contract will receive it. + * + * Money is a decimal STRING, not a bigint: a plan is a transport object that + * crosses into JSON, and `NextResponse.json` cannot serialize a bigint β€” it throws. + * The authoritative bigint values live in the database and in the stored plan; the + * browser converts back exactly once, when building contract arguments. + */ +export interface FundingScheduleRow { + paymentId: string; + worker: string; + /** SAC address. Never inferred from an asset symbol. */ + token: string; + amountBaseUnits: string; + rateBaseUnits: string; + /** Unix seconds, as the contract's u64 fields. */ + startDate: number; + endDate: number; +} + +/** + * Everything the signer is entitled to know before a wallet opens. + * + * Assembled server-side from persisted Payment rows. The original CSV is never + * re-read: the database records are the authoritative payment intent, and + * re-deriving money from a file at signing time would let the two disagree. + */ +export interface FundingPlan { + batch: { id: string; reference: string; paymentCount: number }; + total: string; + totalBaseUnits: string; + asset: { code: string; contractId: string; decimals: number }; + network: { id: string; label: string; isMainnet: boolean }; + /** The CoreFlow contract being invoked. */ + contractId: string; + /** Where the funds will be held: the contract's own address. */ + custodyDestination: string; + manager: string; + financeApprover: string; + oraclePublicKey: string; + schedule: FundingScheduleRow[]; +} + +/** JSON-safe form of a plan, money as decimal strings. */ +export interface StoredFundingPlan { + batchId: string; + reference: string; + orgId: string; + projectId: string | null; + contractId: string; + custodyDestination: string; + network: string; + manager: string; + financeApprover: string; + oraclePublicKey: string; + assetCode: string; + assetContractId: string; + assetDecimals: number; + totalBaseUnits: string; + paymentCount: number; + createdAt: string; + rows: { + paymentId: string; + worker: string; + token: string; + amountBaseUnits: string; + rateBaseUnits: string; + startDate: number; + endDate: number; + }[]; +} + +export function serializePlan( + plan: FundingPlan, + meta: { orgId: string; projectId: string | null; createdAt: Date }, +): StoredFundingPlan { + return { + batchId: plan.batch.id, + reference: plan.batch.reference, + orgId: meta.orgId, + projectId: meta.projectId, + contractId: plan.contractId, + custodyDestination: plan.custodyDestination, + network: plan.network.id, + manager: plan.manager, + financeApprover: plan.financeApprover, + oraclePublicKey: plan.oraclePublicKey, + assetCode: plan.asset.code, + assetContractId: plan.asset.contractId, + assetDecimals: plan.asset.decimals, + totalBaseUnits: plan.totalBaseUnits, + paymentCount: plan.batch.paymentCount, + createdAt: meta.createdAt.toISOString(), + rows: plan.schedule.map((r) => ({ + paymentId: r.paymentId, + worker: r.worker, + token: r.token, + // Strings: JSON has no bigint, and a Number would be the rounding this + // codebase refuses everywhere else. + amountBaseUnits: r.amountBaseUnits, + rateBaseUnits: r.rateBaseUnits, + startDate: r.startDate, + endDate: r.endDate, + })), + }; +} + +/** + * SHA-256 over a canonical rendering of the plan. + * + * Keys are emitted in a fixed order so the digest depends on the plan's CONTENT + * rather than on how a JSON serializer happened to order it. + */ +export function planDigest(plan: StoredFundingPlan): string { + const canonical = JSON.stringify([ + plan.batchId, + plan.orgId, + plan.projectId, + plan.contractId, + plan.custodyDestination, + plan.network, + plan.manager, + plan.financeApprover, + plan.oraclePublicKey, + plan.assetCode, + plan.assetContractId, + plan.assetDecimals, + plan.totalBaseUnits, + plan.paymentCount, + plan.rows.map((r) => [ + r.paymentId, + r.worker, + r.token, + r.amountBaseUnits, + r.rateBaseUnits, + r.startDate, + r.endDate, + ]), + ]); + return createHash('sha256').update(canonical, 'utf8').digest('hex'); +} + +/** + * Read back a stored plan, refusing a tampered one. + * + * A plan whose digest does not match its content cannot be used to decide whether + * chain evidence is acceptable β€” it is no longer evidence of what was intended. + */ +export function readStoredPlan(record: { + plan: unknown; + planDigest: string | null; +}): StoredFundingPlan { + if (!record.plan || typeof record.plan !== 'object') { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'This funding attempt has no stored plan, so there is nothing to verify against.', + ); + } + const plan = record.plan as StoredFundingPlan; + if (!record.planDigest || planDigest(plan) !== record.planDigest) { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'The stored funding plan does not match its digest and cannot be trusted. ' + + 'Funding will not be confirmed against an altered plan.', + ); + } + return plan; +} + +export interface FundingAttemptView { + id: string; + status: TxStatus; + /** Digest of the frozen plan, so the UI can show a quotable plan reference. */ + planDigest: string | null; + attempt: number; + hash: string | null; + errorMessage: string | null; + createdAt: string; + submittedAt: string | null; + confirmedAt: string | null; +} + +export interface FundingStateView { + batch: { id: string; reference: string }; + assessment: { + eligible: boolean; + blockers: FundingAssessment['blockers']; + paymentCount: number; + total: string; + totalBaseUnits: string; + }; + /** Present only when the batch is eligible. No plan for an unfundable batch. */ + plan: FundingPlan | null; + attempt: FundingAttemptView | null; + escrow: { id: string; onChainId: number | null } | null; +} + +// --------------------------------------------------------------------------- +// Loading +// --------------------------------------------------------------------------- + +const PAYMENT_SELECT = { + id: true, + recipientAddress: true, + assetCode: true, + assetContractId: true, + assetDecimals: true, + amountBaseUnits: true, + rateBaseUnits: true, + hours: true, + periodStart: true, + periodEnd: true, + state: true, + escrowId: true, + onChainPaymentIndex: true, +} as const; + +/** + * A second wallet for the finance half of the gate. + * + * Chosen SERVER-SIDE and never taken from the request: letting a caller nominate + * the finance approver would let them nominate themselves, which is exactly what + * the contract refuses with SignersNotDistinct. FINANCE first, then an + * administrative role, then deterministically by join order so the same batch + * yields the same plan on every call. + */ +export async function selectFinanceApprover( + db: any, + orgId: string, + excludeWalletAddress: string, +): Promise { + const members = await db.orgMember.findMany({ + where: { + orgId, + status: MembershipStatus.ACTIVE, + role: { in: [OrgRole.FINANCE, OrgRole.OWNER, OrgRole.ADMIN] }, + }, + orderBy: [{ createdAt: 'asc' }], + include: { user: { select: { walletAddress: true } } }, + }); + + const priority: Record = { + [OrgRole.FINANCE]: 0, + [OrgRole.OWNER]: 1, + [OrgRole.ADMIN]: 2, + }; + + const candidates = members + .filter((m: any) => m.user?.walletAddress && m.user.walletAddress !== excludeWalletAddress) + .sort( + (a: any, b: any) => + (priority[a.role] ?? 9) - (priority[b.role] ?? 9) || + new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(), + ); + + return candidates[0]?.user.walletAddress ?? null; +} + +/** The batch's project, recorded on the plan so the intent names its full scope. */ +async function projectIdForBatch(db: any, orgId: string, batchId: string): Promise { + const row = await db.payrollBatch.findFirst({ + where: { orgId, id: batchId }, + select: { projectId: true }, + }); + return row?.projectId ?? null; +} + +/** The most recent funding attempt for a batch. */ +async function latestAttempt(db: any, orgId: string, batchId: string) { + return db.blockchainTransaction.findFirst({ + where: { orgId, batchId, kind: TxKind.INITIALIZE_ESCROW }, + orderBy: [{ attempt: 'desc' }], + }); +} + +function oraclePublicKeyOrNull(): string | null { + try { + return getOraclePublicKeyHex(); + } catch { + // Unconfigured is a blocker, not a crash: the operator needs to be told which + // thing is missing, not handed a 500. + return null; + } +} + +/** Seconds, as the contract's u64 period fields. */ +function unixSeconds(d: Date): number { + return Math.floor(d.getTime() / 1000); +} + +// --------------------------------------------------------------------------- +// Assessment + plan +// --------------------------------------------------------------------------- + +/** + * Assess a batch and, if it is fundable, produce the exact funding plan. + * + * Read-only. Safe to call as often as a screen needs. + */ +export async function getFundingState( + db: any, + ctx: TenantContext, + batch: { id: string; reference: string }, +): Promise { + const payments: FundingPayment[] = await db.payment.findMany({ + where: { orgId: ctx.orgId, batchId: batch.id }, + orderBy: [{ createdAt: 'asc' }, { id: 'asc' }], + select: PAYMENT_SELECT, + }); + + const asset = settlementAsset(); + const financeApproverAddress = await selectFinanceApprover(db, ctx.orgId, ctx.walletAddress); + const attempt = await latestAttempt(db, ctx.orgId, batch.id); + const oraclePublicKey = oraclePublicKeyOrNull(); + + const assessment = assessFundingEligibility({ + payments, + asset, + funder: { walletAddress: ctx.walletAddress, role: ctx.role }, + financeApproverAddress, + attempt: attempt + ? { + id: attempt.id, + status: attempt.status, + hash: attempt.hash, + createdAt: attempt.createdAt, + } + : null, + oraclePublicKey, + }); + + const decimals = payments[0]?.assetDecimals ?? asset.decimals; + + // An escrow already linked to this batch, via its payments. + const linked = payments.find((p) => p.escrowId !== null); + const escrow = linked + ? await db.escrow.findFirst({ + where: { orgId: ctx.orgId, id: linked.escrowId! }, + select: { id: true, onChainId: true }, + }) + : null; + + return { + batch: { id: batch.id, reference: batch.reference }, + assessment: { + eligible: assessment.eligible, + blockers: assessment.blockers, + paymentCount: assessment.paymentCount, + total: formatAmountWithSeparators(assessment.totalBaseUnits, decimals), + totalBaseUnits: assessment.totalBaseUnits.toString(), + }, + plan: + // A plan is produced when the BATCH is fundable. An attempt already in flight + // is not a reason to withhold it: it is the same plan, and the signer may + // need it again after a dropped wallet prompt. ALREADY_FUNDED does suppress + // it β€” there is nothing left to sign. + planReady(assessment) && asset.contractId && financeApproverAddress && oraclePublicKey + ? buildFundingPlan({ + batch, + payments, + asset: { ...asset, contractId: asset.contractId }, + manager: ctx.walletAddress, + financeApprover: financeApproverAddress, + oraclePublicKey, + totalBaseUnits: assessment.totalBaseUnits, + }) + : null, + attempt: attempt ? viewAttempt(attempt) : null, + escrow, + }; +} + +/** + * Is the batch itself fundable, ignoring an attempt already in flight? + * + * Separated from `eligible` because the two questions differ: "may this batch be + * funded?" and "may a NEW attempt be opened right now?". Conflating them left + * `openFundingIntent` unable to return an existing attempt, because the attempt's + * own existence made the batch look ineligible. + */ +function planReady(assessment: FundingAssessment): boolean { + return assessment.blockers.every((b) => b.code === 'FUNDING_IN_FLIGHT'); +} + +function viewAttempt(tx: any): FundingAttemptView { + return { + id: tx.id, + status: tx.status, + planDigest: tx.planDigest ?? null, + attempt: tx.attempt, + hash: tx.hash ?? null, + errorMessage: tx.errorMessage ?? null, + createdAt: tx.createdAt.toISOString(), + submittedAt: tx.submittedAt?.toISOString() ?? null, + confirmedAt: tx.confirmedAt?.toISOString() ?? null, + }; +} + +export function buildFundingPlan(input: { + batch: { id: string; reference: string }; + payments: readonly FundingPayment[]; + asset: SettlementAsset & { contractId: string }; + manager: string; + financeApprover: string; + oraclePublicKey: string; + totalBaseUnits: bigint; +}): FundingPlan { + const { asset } = input; + + const schedule: FundingScheduleRow[] = input.payments.map((p) => ({ + paymentId: p.id, + worker: p.recipientAddress, + // The CONFIGURED SAC, not whatever was stored on the row: a payment row's + // assetContractId can be null for a draft created before the asset was wired. + token: asset.contractId, + amountBaseUnits: p.amountBaseUnits.toString(), + rateBaseUnits: p.rateBaseUnits.toString(), + // Eligibility has already refused a payment without a period, so these are + // present. Asserted rather than defaulted: a fabricated pay period would be + // signed by the oracle as though someone had stated it. + startDate: unixSeconds(p.periodStart!), + endDate: unixSeconds(p.periodEnd!), + })); + + return { + batch: { + id: input.batch.id, + reference: input.batch.reference, + paymentCount: input.payments.length, + }, + total: formatAmountWithSeparators(input.totalBaseUnits, asset.decimals), + totalBaseUnits: input.totalBaseUnits.toString(), + asset: { code: asset.code, contractId: asset.contractId, decimals: asset.decimals }, + network: { + id: STELLAR_CONFIG.contract.network, + label: STELLAR_CONFIG.networkLabel(), + isMainnet: STELLAR_CONFIG.isMainnet(), + }, + contractId: STELLAR_CONFIG.requireContractId(), + // Custody is the contract's own address: `initialize_multi_sig_escrow` + // transfers to `env.current_contract_address()`. + custodyDestination: STELLAR_CONFIG.requireContractId(), + manager: input.manager, + financeApprover: input.financeApprover, + oraclePublicKey: input.oraclePublicKey, + schedule, + }; +} + +// --------------------------------------------------------------------------- +// Intent lifecycle +// --------------------------------------------------------------------------- + +export interface OpenIntentResult { + /** False when an equivalent intent was already open. */ + created: boolean; + attempt: FundingAttemptView; + plan: FundingPlan; +} + +/** + * Open a funding intent, then hand back the plan to sign. + * + * The unique index on `idempotencyKey` is the control. A second call while an + * attempt is open returns THAT attempt rather than creating another, so a + * double-click, a refresh and a second tab all converge on one escrow. + * + * Payments move DRAFT β†’ VALIDATING here, which is what makes the intent visible in + * the payment records themselves rather than only in a transaction row. + */ +export async function openFundingIntent( + db: any, + ctx: TenantContext, + batch: { id: string; reference: string }, +): Promise { + const state = await getFundingState(db, ctx, batch); + + // An attempt already open for this batch IS the answer β€” checked BEFORE the + // eligibility gate, because the attempt's own existence is reported as a blocker + // and would otherwise reject the very request it should satisfy. This is what + // makes a double-click, a refresh and a second tab converge rather than fail. + if (state.attempt && IN_FLIGHT.includes(state.attempt.status) && state.plan !== null) { + return { created: false, attempt: state.attempt, plan: state.plan }; + } + + if (!state.assessment.eligible || state.plan === null) { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'This batch cannot be funded yet.', + { blockers: state.assessment.blockers }, + ); + } + + const nextAttempt = (state.attempt?.attempt ?? 0) + 1; + const key = fundingIdempotencyKey(batch.id, nextAttempt); + + const projectId = await projectIdForBatch(db, ctx.orgId, batch.id); + const stored = serializePlan(state.plan, { + orgId: ctx.orgId, + projectId, + createdAt: new Date(), + }); + const digest = planDigest(stored); + + try { + const created = await db.$transaction(async (tx: any) => { + const record = await tx.blockchainTransaction.create({ + data: { + orgId: ctx.orgId, + batchId: batch.id, + kind: TxKind.INITIALIZE_ESCROW, + status: TxStatus.AWAITING_SIGNATURE, + idempotencyKey: key, + attempt: nextAttempt, + contractId: state.plan!.contractId, + network: state.plan!.network.id, + // Frozen here, before any wallet is shown, and never rewritten. + plan: stored as unknown as object, + planDigest: digest, + }, + }); + + // Each payment moves to VALIDATING under the state machine's own rules, so + // the batch visibly leaves the editable stage the moment a wallet is opened. + for (const row of state.plan!.schedule) { + await tx.payment.updateMany({ + where: { id: row.paymentId, orgId: ctx.orgId, state: PaymentState.DRAFT }, + data: { + state: PaymentState.VALIDATING, + stateUpdatedAt: new Date(), + stateReason: 'Funding transaction prepared; awaiting signature.', + }, + }); + } + + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: 'funding.intent.opened', + actor: { kind: 'user', role: ctx.role, address: ctx.walletAddress }, + batchId: batch.id, + metadata: { + attempt: nextAttempt, + idempotencyKey: key, + paymentCount: state.plan!.schedule.length, + totalBaseUnits: state.assessment.totalBaseUnits, + asset: state.plan!.asset.code, + assetContractId: state.plan!.asset.contractId, + contractId: state.plan!.contractId, + network: state.plan!.network.id, + manager: state.plan!.manager, + financeApprover: state.plan!.financeApprover, + planDigest: digest, + }, + }); + + return record; + }); + + return { created: true, attempt: viewAttempt(created), plan: state.plan }; + } catch (e: any) { + // Lost the race: a concurrent request opened the intent first. Its attempt is + // the answer β€” the caller asked for an intent to exist, and one does. + if (e?.code === 'P2002') { + const existing = await latestAttempt(db, ctx.orgId, batch.id); + if (existing) { + return { created: false, attempt: viewAttempt(existing), plan: state.plan }; + } + } + throw e; + } +} + +/** Record that a signed transaction reached the network. Submitted is not settled. */ +export async function recordFundingSubmitted( + db: any, + ctx: TenantContext, + input: { attemptId: string; transactionHash: string; batchId: string }, +): Promise { + const updated = await db.blockchainTransaction.updateMany({ + where: { + id: input.attemptId, + orgId: ctx.orgId, + // Scoped to the batch in the URL: an attempt belonging to another batch is + // not reachable by naming its id here. + batchId: input.batchId, + status: { in: [TxStatus.AWAITING_SIGNATURE, TxStatus.PREPARING, TxStatus.SIMULATING] }, + }, + data: { + status: TxStatus.SUBMITTED, + hash: input.transactionHash, + submittedAt: new Date(), + }, + }); + + const record = await db.blockchainTransaction.findFirst({ + where: { id: input.attemptId, orgId: ctx.orgId, batchId: input.batchId }, + }); + if (!record) throw new ApiError(404, 'NOT_FOUND', 'Funding attempt not found.'); + + if (updated.count === 1) { + await recordAuditEvent(db, { + orgId: ctx.orgId, + type: 'funding.submitted', + actor: { kind: 'user', role: ctx.role, address: ctx.walletAddress }, + batchId: record.batchId ?? undefined, + txHash: input.transactionHash, + metadata: { attemptId: input.attemptId, attempt: record.attempt }, + }); + } + + return viewAttempt(record); +} + +/** + * Abandon an attempt, so a rejected signature does not block the batch forever. + * + * Payments return to DRAFT only when nothing was submitted. Once a transaction has + * a hash, the money may have moved and the record must not be quietly rewound β€” + * that case needs chain evidence, not a status change. + */ +export async function failFundingIntent( + db: any, + ctx: TenantContext, + input: { attemptId: string; reason: string; userRejected?: boolean; batchId: string }, +): Promise { + const record = await db.blockchainTransaction.findFirst({ + where: { + id: input.attemptId, + orgId: ctx.orgId, + batchId: input.batchId, + kind: TxKind.INITIALIZE_ESCROW, + }, + }); + if (!record) throw new ApiError(404, 'NOT_FOUND', 'Funding attempt not found.'); + + if (record.status === TxStatus.CONFIRMED) { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'This funding transaction is already confirmed on-chain and cannot be abandoned.', + ); + } + + // `== null` deliberately: the question is "was anything submitted?", and an + // absent column reads as null from Prisma but as undefined from a row that never + // set it. Either answer means no transaction reached the network. + const nothingSubmitted = record.hash == null; + + await db.$transaction(async (tx: any) => { + await tx.blockchainTransaction.updateMany({ + where: { id: record.id, orgId: ctx.orgId }, + data: { + status: input.userRejected ? TxStatus.CANCELLED : TxStatus.FAILED, + errorMessage: input.reason.slice(0, 500), + }, + }); + + if (nothingSubmitted && record.batchId) { + // Safe precisely because nothing reached the network. + await tx.payment.updateMany({ + where: { + orgId: ctx.orgId, + batchId: record.batchId, + state: PaymentState.VALIDATING, + }, + data: { + state: PaymentState.DRAFT, + stateUpdatedAt: new Date(), + stateReason: input.userRejected + ? 'Funding signature declined; the batch is editable again.' + : `Funding failed before submission: ${input.reason.slice(0, 200)}`, + }, + }); + } + + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: input.userRejected ? 'funding.declined' : 'funding.failed', + actor: { kind: 'user', role: ctx.role, address: ctx.walletAddress }, + batchId: record.batchId ?? undefined, + txHash: record.hash ?? undefined, + metadata: { + attemptId: record.id, + attempt: record.attempt, + reason: input.reason.slice(0, 500), + paymentsReturnedToDraft: nothingSubmitted, + }, + }); + }); + + const after = await db.blockchainTransaction.findFirst({ + where: { id: record.id, orgId: ctx.orgId }, + }); + return viewAttempt(after); +} + +// --------------------------------------------------------------------------- +// Chain verification +// --------------------------------------------------------------------------- + +export type EscrowResolution = + | { ok: true; onChainEscrowId: number; source: 'indexed-event' | 'chain-event' } + | { ok: false; reason: 'PENDING'; detail: string } + | { ok: false; reason: 'AMBIGUOUS'; detail: string }; + +/** + * Which escrow did this transaction create? + * + * The transaction hash is the durable anchor. A client that submitted + * `initialize_multi_sig_escrow` and then failed to parse the return value β€” a + * dropped connection, an RPC hiccup, a reload β€” still knows the hash, and the hash + * is enough. Nobody should have to sign a second funding transaction, moving the + * money again, just to learn the id of the escrow the first one already created. + * + * Two sources, in order of durability: + * + * 1. `ChainEvent` β€” the indexer's own record. Survives RPC event retention. + * 2. Soroban RPC β€” authoritative but bounded by retention, used while the + * indexer has not caught up. + * + * Ambiguity is refused rather than resolved. One transaction creating two escrows + * is not a situation to guess about. + */ +export async function resolveEscrowFromTransaction( + db: any, + verifier: ChainVerifier, + input: { txHash: string; contractId: string; network: string }, +): Promise { + // 1. The indexer's record, scoped to the contract and network the attempt names. + const indexed = await db.chainEvent.findMany({ + where: { + txHash: input.txHash, + type: 'created', + contractId: input.contractId, + network: input.network, + }, + select: { escrowOnChainId: true }, + }); + const fromIndex = Array.from( + new Set( + indexed + .map((e: any) => e.escrowOnChainId) + .filter((id: unknown): id is number => typeof id === 'number' && id > 0), + ), + ) as number[]; + + if (fromIndex.length === 1) { + return { ok: true, onChainEscrowId: fromIndex[0], source: 'indexed-event' }; + } + if (fromIndex.length > 1) { + return { + ok: false, + reason: 'AMBIGUOUS', + detail: + `Transaction ${input.txHash} is recorded as creating more than one escrow ` + + `(${fromIndex.join(', ')}). Refusing to choose.`, + }; + } + + // 2. The chain itself, for a transaction the indexer has not reached yet. + const onChain = await verifier.findEscrowsCreatedByTransaction(input.txHash, {}); + if (!onChain.ok) { + return { + ok: false, + reason: 'PENDING', + detail: + `The escrow created by ${input.txHash} could not be read yet ` + + `(${onChain.error.reason}).`, + }; + } + if (onChain.value.length === 1) { + return { ok: true, onChainEscrowId: onChain.value[0], source: 'chain-event' }; + } + if (onChain.value.length > 1) { + return { + ok: false, + reason: 'AMBIGUOUS', + detail: + `Transaction ${input.txHash} created more than one escrow ` + + `(${onChain.value.join(', ')}). Refusing to choose.`, + }; + } + return { + ok: false, + reason: 'PENDING', + detail: + `No escrow/created event has been observed for ${input.txHash} yet. ` + + 'It may still be confirming, or the indexer may not have caught up.', + }; +} + +export type FundingConfirmation = + | { outcome: 'CONFIRMED'; attempt: FundingAttemptView; escrow: { id: string; onChainId: number } } + | { outcome: 'UNVERIFIABLE'; attempt: FundingAttemptView; reason: string } + | { outcome: 'FAILED'; attempt: FundingAttemptView; reason: string } + | { outcome: 'MISMATCH'; attempt: FundingAttemptView; differences: string[] }; + +/** + * Confirm funding from chain evidence, and only then record it. + * + * Three outcomes that are NOT success, each distinct on purpose: + * + * FAILED the chain says the transaction failed. Nothing moved. + * UNVERIFIABLE the chain could not be read. NOT a failure β€” retry later. Marking + * a funded escrow as failed because RPC timed out would be worse + * than waiting. + * MISMATCH the chain disagrees with the plan. The escrow is NOT recorded as + * this batch's, because it is not the escrow we asked for. + */ +export async function confirmFunding( + db: any, + ctx: TenantContext, + verifier: ChainVerifier, + input: { + attemptId: string; + /** + * What the client believes the contract returned. ADVISORY ONLY. + * + * The server resolves the escrow from the transaction hash itself and uses that. + * If this disagrees, it is a mismatch β€” a client must not be able to point a + * funded batch at an escrow from some other transaction. + */ + onChainEscrowId?: number; + batchId: string; + }, +): Promise { + const record = await db.blockchainTransaction.findFirst({ + where: { + id: input.attemptId, + orgId: ctx.orgId, + batchId: input.batchId, + kind: TxKind.INITIALIZE_ESCROW, + }, + }); + if (!record) throw new ApiError(404, 'NOT_FOUND', 'Funding attempt not found.'); + if (!record.hash) { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'This attempt has no transaction hash, so there is nothing to verify.', + ); + } + if (!record.batchId) { + throw new ApiError(409, 'STATE_CONFLICT', 'This attempt is not linked to a batch.'); + } + + // Already done. Replay the answer rather than re-recording. + if (record.status === TxStatus.CONFIRMED && record.escrowId) { + const escrow = await db.escrow.findFirst({ + where: { orgId: ctx.orgId, id: record.escrowId }, + select: { id: true, onChainId: true }, + }); + if (escrow?.onChainId != null) { + return { outcome: 'CONFIRMED', attempt: viewAttempt(record), escrow: { id: escrow.id, onChainId: escrow.onChainId } }; + } + } + + // ── 1. Did the transaction succeed? ── + const succeeded = await verifier.readTransactionSucceeded(record.hash); + if (!succeeded.ok) { + return { + outcome: 'UNVERIFIABLE', + attempt: viewAttempt(record), + reason: `The transaction could not be read (${succeeded.error.reason}). It may still be confirming.`, + }; + } + if (!succeeded.value) { + const after = await markFailed(db, ctx, record, 'The network reported this transaction as failed.'); + return { outcome: 'FAILED', attempt: after, reason: 'The transaction failed on-chain. No funds moved.' }; + } + + // ── 1b. Which escrow did THIS transaction create? ── + // + // Resolved from the hash, never taken from the caller. A client-supplied id is + // only cross-checked: tx A must not be able to adopt an escrow from tx B. + const resolution = await resolveEscrowFromTransaction(db, verifier, { + txHash: record.hash, + contractId: record.contractId ?? '', + network: record.network, + }); + + if (!resolution.ok) { + if (resolution.reason === 'AMBIGUOUS') { + await recordAuditEvent(db, { + orgId: ctx.orgId, + type: 'funding.mismatch', + actor: { kind: 'reconciler', system: 'funding-verifier' }, + batchId: record.batchId, + txHash: record.hash, + metadata: { reason: resolution.reason, detail: resolution.detail }, + }); + return { + outcome: 'MISMATCH', + attempt: viewAttempt(record), + differences: [resolution.detail], + }; + } + // PENDING: not a failure. The transaction may be fine and simply not yet + // observable, and asserting failure here would be a claim about money. + return { outcome: 'UNVERIFIABLE', attempt: viewAttempt(record), reason: resolution.detail }; + } + + const onChainEscrowId = resolution.onChainEscrowId; + + if (input.onChainEscrowId !== undefined && input.onChainEscrowId !== onChainEscrowId) { + await recordAuditEvent(db, { + orgId: ctx.orgId, + type: 'funding.mismatch', + actor: { kind: 'reconciler', system: 'funding-verifier' }, + batchId: record.batchId, + txHash: record.hash, + metadata: { + claimedEscrowId: input.onChainEscrowId, + resolvedEscrowId: onChainEscrowId, + }, + }); + return { + outcome: 'MISMATCH', + attempt: viewAttempt(record), + differences: [ + `the escrow reported by the client (${input.onChainEscrowId}) is not the escrow ` + + `transaction ${record.hash} created (${onChainEscrowId})`, + ], + }; + } + + // ── 2. Does the escrow match what we PLANNED β€” not what we would plan now? ── + // + // The comparison is against the stored plan. Configuration can move under a + // pending transaction: the settlement asset could be switched, a different + // finance approver could become the first candidate, a payment could be edited. + // A recomputed plan would quietly agree with whatever the chain contained, which + // is precisely the agreement that must not be manufactured. + const plan = readStoredPlan(record); + + const onChain = await verifier.readEscrow(onChainEscrowId); + if (!onChain.ok) { + return { + outcome: 'UNVERIFIABLE', + attempt: viewAttempt(record), + reason: `Escrow ${onChainEscrowId} could not be read (${onChain.error.reason}).`, + }; + } + + const expectedTotal = BigInt(plan.totalBaseUnits); + const differences: string[] = []; + const facts = onChain.value; + + // The environment the transaction was prepared for. + if (record.network !== plan.network) { + differences.push(`the attempt records network ${record.network}, the plan says ${plan.network}`); + } + if (record.contractId && record.contractId !== plan.contractId) { + differences.push( + `the attempt records contract ${record.contractId}, the plan says ${plan.contractId}`, + ); + } + + if (facts.cancelled) differences.push('the on-chain escrow is cancelled'); + if (facts.manager !== plan.manager) { + differences.push(`the escrow manager is ${facts.manager}, the plan says ${plan.manager}`); + } + if (facts.financeApprover !== plan.financeApprover) { + differences.push( + `the escrow finance approver is ${facts.financeApprover}, the plan says ` + + `${plan.financeApprover}`, + ); + } + if (facts.manager === facts.financeApprover) { + differences.push('the escrow has the same address as manager and finance approver'); + } + + if (facts.payments.length !== plan.rows.length) { + differences.push( + `the escrow holds ${facts.payments.length} payments, the plan has ${plan.rows.length}`, + ); + } else { + for (const [i, expected] of plan.rows.entries()) { + const actual = facts.payments[i]; + if (actual.worker !== expected.worker) { + differences.push(`payment ${i} pays ${actual.worker}, the plan says ${expected.worker}`); + } + if (actual.amountBaseUnits !== BigInt(expected.amountBaseUnits)) { + differences.push( + `payment ${i} is for ${actual.amountBaseUnits} base units, the plan says ` + + `${expected.amountBaseUnits}`, + ); + } + if (actual.token !== expected.token) { + differences.push(`payment ${i} uses asset ${actual.token}, the plan says ${expected.token}`); + } + } + } + + // Has the payment set itself been altered since the plan was frozen? The chain + // may agree with the plan while the database no longer does, and adopting the + // escrow would then attach it to payments nobody authorised. + const payments: FundingPayment[] = await db.payment.findMany({ + where: { orgId: ctx.orgId, batchId: record.batchId }, + orderBy: [{ createdAt: 'asc' }, { id: 'asc' }], + select: PAYMENT_SELECT, + }); + const byId = new Map(payments.map((p) => [p.id, p])); + for (const row of plan.rows) { + const current = byId.get(row.paymentId); + if (!current) { + differences.push(`payment ${row.paymentId} named in the plan no longer exists`); + continue; + } + if ( + current.recipientAddress !== row.worker || + current.amountBaseUnits !== BigInt(row.amountBaseUnits) || + current.rateBaseUnits !== BigInt(row.rateBaseUnits) + ) { + differences.push( + `payment ${row.paymentId} has been altered since the plan was prepared`, + ); + } + } + if (payments.length !== plan.rows.length) { + differences.push( + `this batch now has ${payments.length} payments, the plan was prepared for ${plan.rows.length}`, + ); + } + + if (differences.length > 0) { + // Do NOT record this escrow as the batch's. Recording a mismatched escrow + // would make the product assert something about money that is not true. + await db.blockchainTransaction.updateMany({ + where: { id: record.id, orgId: ctx.orgId }, + data: { + errorMessage: `Chain disagrees with the funding plan: ${differences.join('; ')}`.slice(0, 500), + }, + }); + await recordAuditEvent(db, { + orgId: ctx.orgId, + type: 'funding.mismatch', + actor: { kind: 'reconciler', system: 'funding-verifier' }, + batchId: record.batchId, + txHash: record.hash, + metadata: { + onChainEscrowId, + differences, + planDigest: record.planDigest, + }, + }); + // Evidence is preserved as a finding, not only as a log line: a mismatch means + // somebody funded an escrow this batch did not describe. + await db.reconciliationFinding.create({ + data: { + orgId: ctx.orgId, + kind: 'UNKNOWN_ON_CHAIN_OBJECT', + severity: 'CRITICAL', + detail: + `Funding transaction ${record.hash} produced escrow ${onChainEscrowId}, ` + + `which does not match the plan prepared for batch ${plan.reference}. ` + + 'The escrow was NOT adopted. ' + + differences.join('; '), + dbState: `plan:${record.planDigest}`, + chainState: `escrow:${onChainEscrowId}`, + metadata: { differences, txHash: record.hash } as any, + }, + }); + return { outcome: 'MISMATCH', attempt: viewAttempt(record), differences }; + } + + // ── 3. Did custody actually move, in THIS transaction? ── + const custody = plan.custodyDestination; + { + const transfers = await verifier.readTransfers(plan.assetContractId, {}); + if (!transfers.ok) { + return { + outcome: 'UNVERIFIABLE', + attempt: viewAttempt(record), + reason: + `Custody could not be verified: asset transfers are unreadable ` + + `(${transfers.error.reason}).`, + }; + } + const funding = transfers.value.find( + (t) => + t.txHash === record.hash && + t.to === custody && + // The PLAN's manager, not the caller: verification must not depend on who + // happens to be asking. A different administrator recovering an uncertain + // transaction must reach the same verdict. + t.from === plan.manager && + t.amountBaseUnits === expectedTotal, + ); + if (!funding) { + return { + outcome: 'UNVERIFIABLE', + attempt: viewAttempt(record), + reason: + `No transfer of ${expectedTotal} base units from ${plan.manager} to ` + + `${custody} was found in transaction ${record.hash}. The escrow exists, so ` + + 'this is most likely event retention rather than a missing transfer β€” ' + + 'funding is left unconfirmed rather than asserted.', + }; + } + } + + // ── Record it ── + const escrow = await db.$transaction(async (tx: any) => { + // The indexer may already have created this escrow from `escrow/created`. + // onChainId is unique, so whoever is first wins and the other links to it. + let row = await tx.escrow.findFirst({ + where: { orgId: ctx.orgId, onChainId: onChainEscrowId }, + select: { id: true, onChainId: true }, + }); + if (!row) { + row = await tx.escrow.create({ + data: { + orgId: ctx.orgId, + onChainId: onChainEscrowId, + contractId: record.contractId ?? custody, + network: record.network, + managerAddress: facts.manager, + financeApproverAddress: facts.financeApprover, + oraclePublicKey: plan.oraclePublicKey, + tokenAddress: plan.assetContractId, + assetDecimals: plan.assetDecimals, + totalAmountBaseUnits: expectedTotal, + projectId: plan.projectId, + }, + select: { id: true, onChainId: true }, + }); + } + + // Link each payment to its on-chain slot, in the order submitted. This is what + // lets the indexer recognise these rows instead of creating duplicates, and + // what makes re-indexing idempotent. + for (const [i, p] of payments.entries()) { + await tx.payment.updateMany({ + where: { id: p.id, orgId: ctx.orgId }, + data: { + escrowId: row!.id, + onChainPaymentIndex: i, + assetContractId: plan.assetContractId, + }, + }); + } + + await tx.blockchainTransaction.updateMany({ + where: { id: record.id, orgId: ctx.orgId }, + data: { + status: TxStatus.CONFIRMED, + escrowId: row!.id, + confirmedAt: new Date(), + errorMessage: null, + }, + }); + + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: 'funding.confirmed', + // Attributed to the verifier, not the person: this record exists because the + // chain was read, not because somebody asserted it. + actor: { kind: 'reconciler', system: 'funding-verifier' }, + batchId: record.batchId!, + escrowId: row!.id, + txHash: record.hash!, + metadata: { + onChainEscrowId, + totalBaseUnits: expectedTotal.toString(), + escrowResolvedFrom: resolution.source, + asset: plan.assetCode, + assetContractId: plan.assetContractId, + planDigest: record.planDigest, + custodyDestination: custody, + paymentCount: payments.length, + verifiedManager: facts.manager, + verifiedFinanceApprover: facts.financeApprover, + }, + }); + + return row!; + }); + + const after = await db.blockchainTransaction.findFirst({ + where: { id: record.id, orgId: ctx.orgId }, + }); + + return { + outcome: 'CONFIRMED', + attempt: viewAttempt(after), + escrow: { id: escrow.id, onChainId: onChainEscrowId }, + }; +} + +async function markFailed( + db: any, + ctx: TenantContext, + record: any, + reason: string, +): Promise { + await db.$transaction(async (tx: any) => { + await tx.blockchainTransaction.updateMany({ + where: { id: record.id, orgId: ctx.orgId }, + data: { status: TxStatus.FAILED, errorMessage: reason.slice(0, 500) }, + }); + // The transaction reached the chain and failed there, so nothing moved and the + // payments may be prepared again. + if (record.batchId) { + await tx.payment.updateMany({ + where: { orgId: ctx.orgId, batchId: record.batchId, state: PaymentState.VALIDATING }, + data: { + state: PaymentState.DRAFT, + stateUpdatedAt: new Date(), + stateReason: 'Funding transaction failed on-chain; no funds moved.', + }, + }); + } + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: 'funding.failed', + actor: { kind: 'reconciler', system: 'funding-verifier' }, + batchId: record.batchId ?? undefined, + txHash: record.hash ?? undefined, + metadata: { attemptId: record.id, reason }, + }); + }); + + const after = await db.blockchainTransaction.findFirst({ + where: { id: record.id, orgId: ctx.orgId }, + }); + return viewAttempt(after); +} + +export { FUNDING_ROLES }; diff --git a/src/lib/indexer/__tests__/indexer.test.ts b/src/lib/indexer/__tests__/indexer.test.ts index 76a2dbf..08acbb3 100644 --- a/src/lib/indexer/__tests__/indexer.test.ts +++ b/src/lib/indexer/__tests__/indexer.test.ts @@ -1,133 +1,101 @@ // @vitest-environment node -import { describe, it, expect, vi } from 'vitest'; -import { parseCoreFlowEvent } from '../events'; -import { processBatch, type RawIndexedEvent, type IndexerDeps } from '../index'; +/** + * Event decoding tests. + * + * The projection itself is covered in projection.test.ts; this file pins the + * boundary where raw contract events become typed domain events. A decoding + * mistake here is invisible downstream β€” a dropped field becomes a zero, and a + * zero amount projects as a real payment of nothing. + */ +import { describe, it, expect } from 'vitest'; +import { parseCoreFlowEvent, paymentIndexOf } from '../events'; + +const WORKER = 'G' + 'W'.repeat(55); +const TOKEN = 'C' + 'T'.repeat(55); +const MANAGER = 'G' + 'M'.repeat(55); describe('parseCoreFlowEvent', () => { - it('maps each contract topic pair to a domain event', () => { - expect(parseCoreFlowEvent('escrow', 'created', [5n, 'GM', 13000n])).toEqual({ - kind: 'created', - escrowId: 5, + it('decodes escrow creation', () => { + expect(parseCoreFlowEvent('escrow', 'created', [7, MANAGER, 28_600_000_000n])).toEqual({ + kind: 'created', escrowId: 7, manager: MANAGER, totalAmount: 28_600_000_000n, + }); + }); + + it('decodes a per-payment add with its full financial identity', () => { + expect( + parseCoreFlowEvent('payment', 'add', [ + 7, 2, WORKER, TOKEN, 9_000_000_000n, 200_000_000n, 1000n, 2000n, + ]) + ).toEqual({ + kind: 'payment_added', escrowId: 7, paymentIndex: 2, + worker: WORKER, token: TOKEN, + amountBaseUnits: 9_000_000_000n, rateBaseUnits: 200_000_000n, + periodStart: 1000n, periodEnd: 2000n, + }); + }); + + it('decodes a per-payment settlement', () => { + expect( + parseCoreFlowEvent('payment', 'paid', [7, 1, WORKER, TOKEN, 9_600_000_000n, 32n]) + ).toEqual({ + kind: 'payment_paid', escrowId: 7, paymentIndex: 1, + worker: WORKER, token: TOKEN, amountBaseUnits: 9_600_000_000n, hours: 32n, + }); + }); + + it('decodes hours, approvals, cancellation, rotation and the aggregate finalize', () => { + expect(parseCoreFlowEvent('hours', 'submit', [7, 0, 40n])).toEqual({ + kind: 'hours', escrowId: 7, paymentIndex: 0, hours: 40n, }); - expect(parseCoreFlowEvent('hours', 'submit', [5n, 0n, 40n])).toEqual({ - kind: 'hours', - escrowId: 5, - paymentId: 0, - hours: 40, + expect(parseCoreFlowEvent('approve', 'manager', 7)).toEqual({ + kind: 'manager_approved', escrowId: 7, }); - expect(parseCoreFlowEvent('approve', 'manager', 5n)).toEqual({ - kind: 'manager_approved', - escrowId: 5, + expect(parseCoreFlowEvent('approve', 'finance', 7)).toEqual({ + kind: 'finance_approved', escrowId: 7, }); - expect(parseCoreFlowEvent('approve', 'finance', 5n)).toEqual({ - kind: 'finance_approved', - escrowId: 5, + expect(parseCoreFlowEvent('payment', 'cancel', [7, 2])).toEqual({ + kind: 'payment_cancelled', escrowId: 7, paymentIndex: 2, }); - expect(parseCoreFlowEvent('payment', 'final', [5n, 13000n, 1n])).toEqual({ - kind: 'finalized', - escrowId: 5, + expect(parseCoreFlowEvent('escrow', 'cancel', 7)).toEqual({ + kind: 'cancelled', escrowId: 7, }); - expect(parseCoreFlowEvent('escrow', 'cancel', 5n)).toEqual({ - kind: 'cancelled', - escrowId: 5, + expect(parseCoreFlowEvent('oracle', 'rotate', [7, 3])).toEqual({ + kind: 'oracle_rotated', escrowId: 7, rotations: 3, + }); + expect(parseCoreFlowEvent('payment', 'final', [7, 28_600_000_000n, 3])).toEqual({ + kind: 'finalized', escrowId: 7, totalAmount: 28_600_000_000n, count: 3, }); }); - it('returns null for unrelated events', () => { - expect(parseCoreFlowEvent('transfer', 'token', [1n])).toBeNull(); + it('returns null for unrelated events rather than throwing', () => { + // A future contract version emitting something new must not halt ingestion. + expect(parseCoreFlowEvent('something', 'else', [1])).toBeNull(); + expect(parseCoreFlowEvent('escrow', 'unknown', 1)).toBeNull(); }); -}); - -/** Minimal in-memory Prisma double for the indexer. */ -function makeDb() { - const escrows = new Map>(); - const chainEvents = new Map(); - let cursor: { lastLedger: number } | null = null; - - return { - escrow: { - upsert: vi.fn(async ({ where, create, update }: any) => { - const ex = escrows.get(where.onChainId); - if (ex) Object.assign(ex, update); - else escrows.set(where.onChainId, { ...create }); - }), - updateMany: vi.fn(async ({ where, data }: any) => { - const ex = escrows.get(where.onChainId); - if (ex) Object.assign(ex, data); - return { count: ex ? 1 : 0 }; - }), - }, - chainEvent: { - findUnique: vi.fn(async ({ where }: any) => chainEvents.get(where.id) ?? null), - create: vi.fn(async ({ data }: any) => { - chainEvents.set(data.id, data); - }), - }, - indexerCursor: { - upsert: vi.fn(async ({ update }: any) => { - cursor = { lastLedger: update.lastLedger }; - }), - }, - _state: { escrows, chainEvents, getCursor: () => cursor }, - }; -} - -const fullLifecycle: RawIndexedEvent[] = [ - { id: 'e1', ledger: 10, topic0: 'escrow', topic1: 'created', value: [1n, 'GM', 13000n] }, - { id: 'e2', ledger: 11, topic0: 'hours', topic1: 'submit', value: [1n, 0n, 40n] }, - { id: 'e3', ledger: 12, topic0: 'approve', topic1: 'manager', value: 1n }, - { id: 'e4', ledger: 13, topic0: 'approve', topic1: 'finance', value: 1n }, - { id: 'e5', ledger: 14, topic0: 'payment', topic1: 'final', value: [1n, 13000n, 1n] }, -]; -function deps(db: ReturnType): IndexerDeps { - return { - db, - fetchEscrowDetail: vi.fn(async () => ({ - worker: 'GWORKER', - amountCents: 13000, - rateCents: 250, - tokenAddress: 'CTOKEN', - })), - }; -} - -describe('processBatch', () => { - it('projects a full lifecycle to the correct final state', async () => { - const db = makeDb(); - const result = await processBatch(fullLifecycle, deps(db)); - - expect(result.processed).toBe(5); - expect(result.skipped).toBe(0); - expect(result.lastLedger).toBe(14); - - const escrow = db._state.escrows.get(1)!; - expect(escrow.status).toBe('paid'); - expect(escrow.managerApproved).toBe(true); - expect(escrow.financeApproved).toBe(true); - expect(escrow.workerPubKey).toBe('GWORKER'); - expect(db._state.getCursor()).toEqual({ lastLedger: 14 }); + it('keeps money as bigint, never Number', () => { + // Above 2^53-1 a Number cast silently rounds, which for a ledger means a + // wrong amount recorded as fact. + const huge = 9_007_199_254_740_993n; // 2^53 + 1 + const ev = parseCoreFlowEvent('payment', 'paid', [1, 0, WORKER, TOKEN, huge, 1n]); + expect(ev).toMatchObject({ amountBaseUnits: huge }); + expect(typeof (ev as any).amountBaseUnits).toBe('bigint'); }); - it('is idempotent β€” reprocessing the same batch is a no-op', async () => { - const db = makeDb(); - await processBatch(fullLifecycle, deps(db)); - const escrowAfterFirst = { ...db._state.escrows.get(1) }; + it('accepts an amount delivered as a decimal string', () => { + // scValToNative yields bigint, but a replayed stored payload is JSON strings. + const ev = parseCoreFlowEvent('payment', 'paid', [1, 0, WORKER, TOKEN, '10000000000', '40']); + expect(ev).toMatchObject({ amountBaseUnits: 10_000_000_000n, hours: 40n }); + }); - const second = await processBatch(fullLifecycle, deps(db)); - expect(second.processed).toBe(0); - expect(second.skipped).toBe(5); - expect(db._state.escrows.get(1)).toEqual(escrowAfterFirst); + it('identifies which events refer to a payment slot', () => { + const add = parseCoreFlowEvent('payment', 'add', [7, 2, WORKER, TOKEN, 1n, 1n, 0n, 1n])!; + const approval = parseCoreFlowEvent('approve', 'manager', 7)!; + expect(paymentIndexOf(add)).toBe(2); + expect(paymentIndexOf(approval)).toBeNull(); }); - it('skips unrecognized events without recording them', async () => { - const db = makeDb(); - const result = await processBatch( - [{ id: 'x1', ledger: 9, topic0: 'transfer', topic1: 'token', value: [1n] }], - deps(db) - ); - expect(result.processed).toBe(0); - expect(result.skipped).toBe(1); - expect(db._state.chainEvents.size).toBe(0); + it('treats a scalar value as a one-element tuple', () => { + expect(parseCoreFlowEvent('escrow', 'cancel', 9)).toEqual({ kind: 'cancelled', escrowId: 9 }); }); }); diff --git a/src/lib/indexer/__tests__/live-tenancy.test.ts b/src/lib/indexer/__tests__/live-tenancy.test.ts new file mode 100644 index 0000000..6c2eb63 --- /dev/null +++ b/src/lib/indexer/__tests__/live-tenancy.test.ts @@ -0,0 +1,164 @@ +// @vitest-environment node +/** + * Live two-tenant isolation test against the deployed v2 Testnet contract. + * + * OPT-IN (COREFLOW_LIVE_TESTNET=1): needs real Soroban RPC and real Postgres. + * + * What it proves, on real chain data: + * 1. An escrow with no tenant mapping is NOT projected β€” the indexer does not + * invent an owner. + * 2. Once organization A claims it, its payments appear under A, and the history + * that arrived before the claim is replayed rather than lost. + * 3. Organization B cannot see any of it, by id or by on-chain id. + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { PaymentState, OrgRole, MembershipStatus } from '@prisma/client'; + +const LIVE = process.env.COREFLOW_LIVE_TESTNET === '1'; + +const A_SLUG = 'live-test-org-a'; +const B_SLUG = 'live-test-org-b'; + +describe.skipIf(!LIVE)('multi-tenancy β€” live v2 Testnet', () => { + let prisma: any; + let orgA: string; + let orgB: string; + let escrowOnChainId: number; + + beforeAll(async () => { + prisma = (await import('@/lib/db/prisma')).default; + + // Two tenants, each with an owner. + const a = await prisma.organization.upsert({ + where: { slug: A_SLUG }, create: { name: 'Live Test A', slug: A_SLUG }, update: {}, + }); + const b = await prisma.organization.upsert({ + where: { slug: B_SLUG }, create: { name: 'Live Test B', slug: B_SLUG }, update: {}, + }); + orgA = a.id; orgB = b.id; + + const ua = await prisma.user.upsert({ + where: { walletAddress: 'GLIVEA' }, create: { walletAddress: 'GLIVEA' }, update: {}, + }); + const ub = await prisma.user.upsert({ + where: { walletAddress: 'GLIVEB' }, create: { walletAddress: 'GLIVEB' }, update: {}, + }); + for (const [org, user] of [[orgA, ua.id], [orgB, ub.id]] as const) { + await prisma.orgMember.upsert({ + where: { orgId_userId: { orgId: org, userId: user } }, + create: { orgId: org, userId: user, role: OrgRole.OWNER, status: MembershipStatus.ACTIVE }, + update: { status: MembershipStatus.ACTIVE }, + }); + } + }); + + afterAll(async () => { + // Leave the database as found: these tenants exist only for the test. + if (!prisma) return; + for (const org of [orgA, orgB].filter(Boolean)) { + await prisma.organization.delete({ where: { id: org } }).catch(() => {}); + } + }); + + it('does not project an escrow with no tenant mapping', async () => { + const { runIndexerFromRpc } = await import('@/lib/indexer/run'); + + // Start from scratch so the whole history is re-read. + await prisma.chainEvent.deleteMany({}); + await prisma.indexerCursor.deleteMany({}); + + const result = await runIndexerFromRpc(); + console.log('PASS 1 (no mapping):', JSON.stringify(result)); + + // Real escrows exist on chain and none are claimed, so everything is + // unattributed and nothing is projected. + expect(result.unattributed).toBeGreaterThan(0); + expect(result.paymentsCreated).toBe(0); + + const projected = await prisma.payment.count({ where: { orgId: { in: [orgA, orgB] } } }); + expect(projected).toBe(0); + + // And the indexer invented no organization of its own. + const invented = await prisma.organization.count({ + where: { slug: { startsWith: 'chain-' } }, + }); + expect(invented).toBe(0); + + // The unattributed events were recorded, not dropped. + expect(await prisma.chainEvent.count({ where: { attributed: false } })).toBeGreaterThan(0); + }, 300_000); + + it('projects the escrow under org A once A claims it, replaying earlier events', async () => { + const { runIndexerFromRpc } = await import('@/lib/indexer/run'); + const { STELLAR_CONFIG } = await import('@/lib/config'); + + // Find a settled multi-payee escrow from the recorded events. + const paidEvent = await prisma.chainEvent.findFirst({ + where: { type: 'payment_paid' }, + orderBy: { ledger: 'desc' }, + }); + expect(paidEvent, 'expected a settled escrow in the chain log').toBeTruthy(); + escrowOnChainId = paidEvent.escrowOnChainId; + + // Org A claims it. (The HTTP route additionally verifies the caller is the + // on-chain manager; here the mapping is written directly, which is what a + // successful claim produces.) + await prisma.escrow.create({ + data: { + orgId: orgA, + onChainId: escrowOnChainId, + contractId: STELLAR_CONFIG.contract.id, + network: STELLAR_CONFIG.contract.network, + managerAddress: 'GLIVEA', + financeApproverAddress: 'GLIVEF', + assetDecimals: 7, + }, + }); + + const result = await runIndexerFromRpc(); + console.log('PASS 2 (after claim):', JSON.stringify(result)); + + const payments = await prisma.payment.findMany({ + where: { orgId: orgA }, + orderBy: { onChainPaymentIndex: 'asc' }, + }); + for (const p of payments) { + console.log( + ` org A idx=${p.onChainPaymentIndex} ${p.recipientAddress.slice(0, 10)}… ` + + `amount=${p.amountBaseUnits} state=${p.state}` + ); + } + + // The history that arrived BEFORE the claim was replayed. + expect(payments.length).toBeGreaterThanOrEqual(3); + expect(payments.every((p: any) => p.state === PaymentState.PAID)).toBe(true); + expect(new Set(payments.map((p: any) => p.recipientAddress)).size).toBe(payments.length); + }, 300_000); + + it('shows org B nothing belonging to org A', async () => { + const { findPayment, findEscrowByOnChainId, resolveTenant } = + await import('@/lib/tenancy/resolve'); + + const ub = await prisma.user.findUnique({ where: { walletAddress: 'GLIVEB' } }); + const tenantB = await resolveTenant(prisma, ub.id, orgB); + expect(tenantB.ok).toBe(true); + if (!tenantB.ok) return; + + // B's own scoped queries return nothing. + expect(await prisma.payment.count({ where: { orgId: orgB } })).toBe(0); + + // Every one of A's payments is a 404 to B, by id. + const aPayments = await prisma.payment.findMany({ where: { orgId: orgA }, select: { id: true } }); + expect(aPayments.length).toBeGreaterThan(0); + for (const { id } of aPayments) { + const r = await findPayment(prisma, tenantB.value, id); + expect(r.ok, `org B must not read payment ${id}`).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + } + + // And by on-chain id β€” the identifier B would most plausibly guess. + const byChain = await findEscrowByOnChainId(prisma, tenantB.value, escrowOnChainId); + expect(byChain.ok).toBe(false); + if (!byChain.ok) expect(byChain.status).toBe(404); + }, 120_000); +}); diff --git a/src/lib/indexer/__tests__/live-testnet.test.ts b/src/lib/indexer/__tests__/live-testnet.test.ts new file mode 100644 index 0000000..07ae43f --- /dev/null +++ b/src/lib/indexer/__tests__/live-testnet.test.ts @@ -0,0 +1,147 @@ +// @vitest-environment node +/** + * Live indexer integration test against the deployed v2 Testnet contract. + * + * OPT-IN. Skipped unless COREFLOW_LIVE_TESTNET=1, because it needs both a + * reachable Soroban RPC and a real Postgres. The standard suite stays hermetic; + * keeping this in the repo means the chainβ†’DB projection is verifiable rather + * than asserted. + * + * THE CLAIM UNDER TEST: a three-payee settlement produces THREE Payment rows + * with their own recipients and amounts. That is the defect P2 #1 existed to fix, + * and proving it against real chain data is the acceptance gate. + * + * set -a && . ./.env.testnet.local && set +a + * DATABASE_URL=postgresql://coreflow:coreflow@localhost:5433/coreflow \ + * COREFLOW_LIVE_TESTNET=1 npx vitest run src/lib/indexer/__tests__/live-testnet.test.ts + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { PaymentState, OrgRole, MembershipStatus } from '@prisma/client'; + +const LIVE = process.env.COREFLOW_LIVE_TESTNET === '1'; +const SLUG = 'live-indexer-test'; + +describe.skipIf(!LIVE)('indexer β€” live v2 Testnet', () => { + let prisma: any; + let orgId: string; + + /** + * Self-contained tenant setup. + * + * Since P2 #2 the indexer refuses to invent an organization, so an escrow with no + * mapping is recorded unattributed and nothing is projected. This test must + * therefore create its own tenant and claim an escrow β€” relying on another + * suite's leftover state would make it order-dependent and, worse, make it pass + * for the wrong reason. + */ + beforeAll(async () => { + prisma = (await import('@/lib/db/prisma')).default; + const org = await prisma.organization.upsert({ + where: { slug: SLUG }, create: { name: 'Live Indexer Test', slug: SLUG }, update: {}, + }); + orgId = org.id; + const user = await prisma.user.upsert({ + where: { walletAddress: 'GLIVEINDEXER' }, + create: { walletAddress: 'GLIVEINDEXER' }, update: {}, + }); + await prisma.orgMember.upsert({ + where: { orgId_userId: { orgId, userId: user.id } }, + create: { orgId, userId: user.id, role: OrgRole.OWNER, status: MembershipStatus.ACTIVE }, + update: { status: MembershipStatus.ACTIVE }, + }); + }); + + afterAll(async () => { + if (prisma && orgId) { + await prisma.organization.delete({ where: { id: orgId } }).catch(() => {}); + } + }); + + it('projects a multi-payee settlement into one Payment per payee', async () => { + const { runIndexerFromRpc } = await import('@/lib/indexer/run'); + const { STELLAR_CONFIG } = await import('@/lib/config'); + + // Read the retained window, discover a settled escrow, claim it, re-index. + await prisma.chainEvent.deleteMany({}); + await prisma.indexerCursor.deleteMany({}); + const discovery = await runIndexerFromRpc(); + console.log('INDEXER discovery:', JSON.stringify(discovery)); + + const paidEvent = await prisma.chainEvent.findFirst({ + where: { type: 'payment_paid' }, orderBy: { ledger: 'desc' }, + }); + expect(paidEvent, 'expected a settled escrow in the retained log').toBeTruthy(); + + await prisma.escrow.create({ + data: { + orgId, onChainId: paidEvent.escrowOnChainId, + contractId: STELLAR_CONFIG.contract.id, + network: STELLAR_CONFIG.contract.network, + managerAddress: 'GLIVEINDEXER', financeApproverAddress: 'GLIVEFIN', + assetDecimals: 7, + }, + }); + + const result = await runIndexerFromRpc(); + console.log('INDEXER:', JSON.stringify(result)); + + // Find an escrow with more than one payment β€” the golden path settles three. + const escrows = await prisma.escrow.findMany({ + where: { orgId }, + include: { payments: { orderBy: { onChainPaymentIndex: 'asc' } } }, + orderBy: { onChainId: 'desc' }, + }); + for (const e of escrows) { + console.log(`escrow ${e.onChainId}: ${e.payments.length} payment(s)`); + for (const p of e.payments) { + console.log( + ` idx=${p.onChainPaymentIndex} ${p.recipientAddress.slice(0, 10)}… ` + + `amount=${p.amountBaseUnits} hours=${p.hours} state=${p.state}` + ); + } + } + + const multi = escrows.find((e) => e.payments.length > 1); + expect(multi, 'expected an escrow with multiple payments').toBeDefined(); + + const payments = multi!.payments; + expect(payments.length).toBeGreaterThanOrEqual(3); + + // Each payment is its own financial record: distinct recipient, own amount. + const recipients = payments.map((p) => p.recipientAddress); + expect(new Set(recipients).size).toBe(recipients.length); + + const amounts = payments.map((p) => p.amountBaseUnits); + expect(new Set(amounts.map(String)).size).toBeGreaterThan(1); + + // The golden path settles 1000 / 960 / 900 USDC at 7 decimals. + expect(amounts.map(String).sort()).toEqual( + ['10000000000', '9000000000', '9600000000'].sort() + ); + + // Settled, with chain-derived state. + expect(payments.every((p) => p.state === PaymentState.PAID)).toBe(true); + expect(payments.every((p) => p.settlementTxHash !== null)).toBe(true); + expect(payments.every((p) => p.settledAt !== null)).toBe(true); + + // Money stays bigint at the asset's own precision. + for (const p of payments) { + expect(typeof p.amountBaseUnits).toBe('bigint'); + expect(p.assetDecimals).toBe(7); + } + + // Per-payment audit history exists, attributed to the indexer. + const audits = await prisma.auditEvent.findMany({ + where: { paymentId: { in: payments.map((p) => p.id) }, newState: PaymentState.PAID }, + }); + expect(audits.length).toBe(payments.length); + expect(audits.every((a) => a.actorSystem === 'indexer')).toBe(true); + + // Re-running adds no duplicate events and no duplicate payments. + const eventsBefore = await prisma.chainEvent.count(); + const paymentsBefore = await prisma.payment.count(); + await runIndexerFromRpc(); + expect(await prisma.chainEvent.count()).toBe(eventsBefore); + expect(await prisma.payment.count()).toBe(paymentsBefore); + }, 300_000); +}); diff --git a/src/lib/indexer/__tests__/projection.test.ts b/src/lib/indexer/__tests__/projection.test.ts new file mode 100644 index 0000000..3b94b80 --- /dev/null +++ b/src/lib/indexer/__tests__/projection.test.ts @@ -0,0 +1,657 @@ +// @vitest-environment node +/** + * Indexer projection tests. + * + * THE REGRESSION THIS GUARDS: the previous projection stored one worker and one + * amount per Escrow, so a three-payee settlement collapsed into a single row + * carrying the first payee's figures. Two of the three payments did not exist in + * the product at all. The first test below is the one that must never go green + * again for the wrong reason. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { PaymentState } from '@prisma/client'; +import { processBatch, type RawIndexedEvent, type IndexerContext } from '../index'; +import { createFakeDb, seedOrg, type FakeDb } from '@/lib/payments/__tests__/fake-db'; + +const CONTRACT = 'CDN4FIKLJ72WYNPBIKWYSDJWDZG22QNPLWI37VTUAE4EKKIBVAQRG5F4'; +const TOKEN = 'CBW2ZKFBHLHNNVCZ7JP4AXHQOOC3S6NLAMORXOAIWQNWMKUVJS743Q5M'; +const MANAGER = 'G' + 'M'.repeat(55); +const W = (n: number) => 'G' + String(n).repeat(55).slice(0, 55); + +let db: FakeDb; +let ctx: IndexerContext; +let ORG: string; + +/** + * Pre-register the tenant mapping for an escrow. + * + * The indexer no longer invents an organization: ownership comes only from an + * Escrow row the application wrote. In production that row is created when a + * member submits the creation transaction; here it is seeded explicitly, which + * also makes each test state which tenant it is about. + */ +function mapEscrowToTenant(onChainId: number, orgId = ORG) { + db.__tables.escrow.rows.push({ + id: `esc_${onChainId}`, + orgId, + onChainId, + contractId: CONTRACT, + network: 'testnet', + managerAddress: MANAGER, + financeApproverAddress: 'G' + 'F'.repeat(55), + assetDecimals: 7, + totalAmountBaseUnits: 0n, + managerApproved: false, + financeApproved: false, + cancelled: false, + oracleRotations: 0, + }); +} + +beforeEach(() => { + db = createFakeDb(); + ORG = seedOrg(db); + ctx = { contractId: CONTRACT, network: 'testnet', assetDecimals: 7 }; +}); + +let tokenSeq = 0; +/** Unique RPC paging token per event, as the real RPC provides. */ +const tok = () => `0000000${++tokenSeq}-0000000001`; + +function created(escrowId: number, total: bigint, ledger = 100): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'escrow', topic1: 'created', + value: [escrowId, MANAGER, total], txHash: `tx_created_${escrowId}`, + }; +} +function added( + escrowId: number, index: number, worker: string, amount: bigint, rate: bigint, ledger = 100 +): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'payment', topic1: 'add', + value: [escrowId, index, worker, TOKEN, amount, rate, 1000n, 2000n], + txHash: `tx_created_${escrowId}`, + }; +} +function hours(escrowId: number, index: number, h: bigint, ledger = 110): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'hours', topic1: 'submit', + value: [escrowId, index, h], txHash: `tx_hours_${escrowId}_${index}`, + }; +} +function approve(escrowId: number, who: 'manager' | 'finance', ledger = 120): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'approve', topic1: who, + value: escrowId, txHash: `tx_${who}_${escrowId}`, + }; +} +function paid( + escrowId: number, index: number, worker: string, amount: bigint, h: bigint, ledger = 130 +): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'payment', topic1: 'paid', + value: [escrowId, index, worker, TOKEN, amount, h], txHash: `tx_paid_${escrowId}`, + }; +} +function finalized(escrowId: number, total: bigint, count: number, ledger = 130): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'payment', topic1: 'final', + value: [escrowId, total, count], txHash: `tx_paid_${escrowId}`, + }; +} +function cancelPayment(escrowId: number, index: number, ledger = 140): RawIndexedEvent { + return { + id: tok(), ledger, topic0: 'payment', topic1: 'cancel', + value: [escrowId, index], txHash: `tx_cancel_${escrowId}`, + }; +} + +/** The validated golden path: 3 contractors, 1000/960/900 USDC at 25/30/20 per hour. */ +const THREE_PAYEES = [ + { index: 0, worker: W(1), amount: 10_000_000_000n, rate: 250_000_000n, hours: 40n }, + { index: 1, worker: W(2), amount: 9_600_000_000n, rate: 300_000_000n, hours: 32n }, + { index: 2, worker: W(3), amount: 9_000_000_000n, rate: 200_000_000n, hours: 45n }, +]; +const TOTAL = THREE_PAYEES.reduce((a, p) => a + p.amount, 0n); + +function goldenPathEvents(escrowId = 2): RawIndexedEvent[] { + return [ + created(escrowId, TOTAL), + ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate)), + ...THREE_PAYEES.map((p) => hours(escrowId, p.index, p.hours)), + approve(escrowId, 'manager'), + approve(escrowId, 'finance'), + ...THREE_PAYEES.map((p) => paid(escrowId, p.index, p.worker, p.amount, p.hours)), + finalized(escrowId, TOTAL, 3), + ]; +} + +const payments = () => db.__tables.payment.rows; +const findings = () => db.__tables.reconciliationFinding.rows; + +describe('multi-payment projection', () => { + it('produces one Payment per payee, not one per escrow', async () => { + mapEscrowToTenant(2); + mapEscrowToTenant(2); + const result = await processBatch(goldenPathEvents(), { db, ctx }); + + expect(result.paymentsCreated).toBe(3); + expect(payments()).toHaveLength(3); + + // Each carries its OWN recipient, amount, rate and hours β€” the figures that + // the old single-row projection discarded for payees 2 and 3. + for (const expected of THREE_PAYEES) { + const row = payments().find((p) => p.onChainPaymentIndex === expected.index); + expect(row, `payment ${expected.index} missing`).toBeDefined(); + expect(row!.recipientAddress).toBe(expected.worker); + expect(row!.amountBaseUnits).toBe(expected.amount); + expect(row!.rateBaseUnits).toBe(expected.rate); + expect(row!.hours).toBe(expected.hours); + } + + // Distinct recipients β€” a collapsed projection would repeat the first. + expect(new Set(payments().map((p) => p.recipientAddress)).size).toBe(3); + }); + + it('keeps the batch relationship while payments stay individual', async () => { + mapEscrowToTenant(2); + await processBatch(goldenPathEvents(), { db, ctx }); + const batches = db.__tables.payrollBatch.rows; + expect(batches).toHaveLength(1); + expect(payments().every((p) => p.batchId === batches[0].id)).toBe(true); + }); + + it('settles all three to PAID with the correct amounts', async () => { + mapEscrowToTenant(2); + const result = await processBatch(goldenPathEvents(), { db, ctx }); + + expect(result.paymentsPaid).toBe(3); + expect(payments().every((p) => p.state === PaymentState.PAID)).toBe(true); + + const settled = payments().reduce((a, p) => a + p.amountBaseUnits, 0n); + expect(settled).toBe(TOTAL); + expect(settled).toBe(28_600_000_000n); // 2,860 USDC + }); + + it('records no reconciliation findings on a clean golden path', async () => { + mapEscrowToTenant(2); + const result = await processBatch(goldenPathEvents(), { db, ctx }); + expect(result.findings).toBe(0); + expect(findings()).toHaveLength(0); + }); + + it('creates a Worker row per distinct payee', async () => { + mapEscrowToTenant(2); + await processBatch(goldenPathEvents(), { db, ctx }); + expect(db.__tables.worker.rows).toHaveLength(3); + }); + + it('walks each payment through the full lifecycle in order', async () => { + const escrowId = 5; + mapEscrowToTenant(5); + const step = async (evs: RawIndexedEvent[]) => processBatch(evs, { db, ctx }); + + await step([created(escrowId, TOTAL), ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate))]); + expect(payments().every((p) => p.state === PaymentState.AWAITING_ORACLE)).toBe(true); + + await step(THREE_PAYEES.map((p) => hours(escrowId, p.index, p.hours))); + expect(payments().every((p) => p.state === PaymentState.ORACLE_VERIFIED)).toBe(true); + + await step([approve(escrowId, 'manager')]); + expect(payments().every((p) => p.state === PaymentState.AWAITING_FINANCE)).toBe(true); + + await step([approve(escrowId, 'finance')]); + expect(payments().every((p) => p.state === PaymentState.READY_TO_SETTLE)).toBe(true); + + await step(THREE_PAYEES.map((p) => paid(escrowId, p.index, p.worker, p.amount, p.hours))); + expect(payments().every((p) => p.state === PaymentState.PAID)).toBe(true); + }); +}); + +describe('idempotency', () => { + it('skips re-delivered events and creates nothing twice', async () => { + mapEscrowToTenant(2); + const events = goldenPathEvents(); + const first = await processBatch(events, { db, ctx }); + const second = await processBatch(events, { db, ctx }); + + expect(first.processed).toBeGreaterThan(0); + expect(second.processed).toBe(0); + expect(second.skipped).toBe(events.length); + + // The property that matters: no duplicate payments, and no second payout. + expect(payments()).toHaveLength(3); + expect(second.paymentsCreated).toBe(0); + expect(second.paymentsPaid).toBe(0); + }); + + it('survives the same event appearing twice within one batch', async () => { + mapEscrowToTenant(2); + const events = goldenPathEvents(); + const doubled = [...events, ...events]; + const result = await processBatch(doubled, { db, ctx }); + + expect(payments()).toHaveLength(3); + expect(result.skipped).toBe(events.length); + }); + + it('does not re-pay a payment that is already PAID', async () => { + const escrowId = 7; + mapEscrowToTenant(7); + await processBatch(goldenPathEvents(escrowId), { db, ctx }); + + // A duplicate paid event arriving under a NEW paging token β€” a real + // possibility after an RPC replay β€” must still not double-count. + const replay = paid(escrowId, 0, THREE_PAYEES[0].worker, THREE_PAYEES[0].amount, 40n); + const result = await processBatch([replay], { db, ctx }); + + expect(result.processed).toBe(1); + expect(result.paymentsPaid).toBe(0); + const audits = db.__tables.auditEvent.rows.filter( + (a) => a.type === 'payment.state.changed' && a.newState === PaymentState.PAID + ); + expect(audits).toHaveLength(3); // one per payment, not four + }); + + it('does not create a second payment for a re-delivered add under a new token', async () => { + const escrowId = 8; + mapEscrowToTenant(8); + await processBatch([created(escrowId, TOTAL), added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n)], { db, ctx }); + await processBatch([added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n)], { db, ctx }); + expect(payments()).toHaveLength(1); + }); +}); + +describe('restart and partial ingestion', () => { + it('resumes from the cursor after a restart', async () => { + const escrowId = 11; + mapEscrowToTenant(11); + const head = [created(escrowId, TOTAL), ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate))]; + const tail = [...THREE_PAYEES.map((p) => hours(escrowId, p.index, p.hours)), approve(escrowId, 'manager')]; + + const a = await processBatch(head, { db, ctx }); + expect(a.lastLedger).toBe(100); + const cursor = db.__tables.indexerCursor.rows[0]; + expect(cursor.lastLedger).toBe(100); + expect(cursor.contractId).toBe(CONTRACT); + expect(cursor.network).toBe('testnet'); + + // Simulate a process restart: a brand-new run over the remaining events. + const b = await processBatch(tail, { db, ctx }); + expect(b.lastLedger).toBe(120); + expect(payments().every((p) => p.state === PaymentState.AWAITING_FINANCE)).toBe(true); + }); + + it('leaves no partial effect when an event fails mid-batch', async () => { + const escrowId = 12; + mapEscrowToTenant(12); + // Fail while creating the SECOND payment. + db.__failOn('payment', 'create', 1); + + await expect( + processBatch( + [created(escrowId, TOTAL), ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate))], + { db, ctx } + ) + ).rejects.toThrow(); + + // The escrow from the first event committed; the failed add rolled back + // entirely, leaving no half-written payment and no ChainEvent marker for it. + expect(db.__tables.escrow.rows).toHaveLength(1); + expect(payments()).toHaveLength(0); + const markers = db.__tables.chainEvent.rows.map((c) => c.type); + expect(markers).toEqual(['created']); + }); + + it('re-applies the failed event on the next run and completes', async () => { + const escrowId = 13; + mapEscrowToTenant(13); + const events = [ + created(escrowId, TOTAL), + ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate)), + ]; + db.__failOn('payment', 'create', 1); + await expect(processBatch(events, { db, ctx })).rejects.toThrow(); + expect(payments()).toHaveLength(0); + + // Same events, no injected failure: ingestion completes and the skipped + // marker for `created` prevents it being applied twice. + const retry = await processBatch(events, { db, ctx }); + expect(retry.skipped).toBe(1); + expect(payments()).toHaveLength(3); + }); + + it('does not advance the cursor past an event it failed to apply', async () => { + const escrowId = 14; + mapEscrowToTenant(14); + db.__failOn('payment', 'create', 1); + await expect( + processBatch( + [ + created(escrowId, TOTAL, 200), + added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n, 300), + ], + { db, ctx } + ) + ).rejects.toThrow(); + + // Cursor sits at the last COMMITTED ledger, never at the failed one. + expect(db.__tables.indexerCursor.rows[0].lastLedger).toBe(200); + }); + + it('orders events deterministically regardless of RPC delivery order', async () => { + const escrowId = 15; + mapEscrowToTenant(15); + const events = goldenPathEvents(escrowId); + const shuffled = [...events].reverse(); + + await processBatch(shuffled, { db, ctx }); + + // Reversed delivery still ends in the same terminal state, because the batch + // is sorted by (ledger, paging token) before application. + expect(payments()).toHaveLength(3); + expect(payments().every((p) => p.state === PaymentState.PAID)).toBe(true); + }); +}); + +describe('reconciliation findings from the log', () => { + it('flags a settlement for a payment it has no row for', async () => { + // Indexing started after escrow creation, so the add events were missed. + const result = await processBatch( + [paid(99, 0, W(1), 10_000_000_000n, 40n)], + { db, ctx } + ); + // No escrow row at all, so nothing to attach: the event is recorded and + // ignored rather than inventing an escrow. + expect(payments()).toHaveLength(0); + expect(result.processed).toBe(1); + }); + + it('flags an orphan settlement when the escrow exists but the slot does not', async () => { + const escrowId = 21; + mapEscrowToTenant(21); + await processBatch([created(escrowId, TOTAL), added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n)], { db, ctx }); + + const result = await processBatch([paid(escrowId, 9, W(9), 1n, 1n)], { db, ctx }); + + expect(result.findings).toBe(1); + expect(findings()[0].kind).toBe('ORPHAN_ON_CHAIN'); + }); + + it('flags a settled amount that disagrees with the record, and still marks PAID', async () => { + const escrowId = 22; + mapEscrowToTenant(22); + await processBatch( + [created(escrowId, TOTAL), added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n), + hours(escrowId, 0, 40n), approve(escrowId, 'manager'), approve(escrowId, 'finance')], + { db, ctx } + ); + + // The chain moved a different amount than we recorded. + const result = await processBatch( + [paid(escrowId, 0, W(1), 9_999_999_999n, 40n)], + { db, ctx } + ); + + expect(result.findings).toBe(1); + expect(findings()[0].kind).toBe('AMOUNT_MISMATCH'); + // The transfer happened either way β€” the payment is PAID, and the + // discrepancy is a separate recorded fact rather than a reason to hide it. + expect(payments()[0].state).toBe(PaymentState.PAID); + }); + + it('does not overwrite a stored payment when a replayed add disagrees', async () => { + const escrowId = 23; + mapEscrowToTenant(23); + await processBatch([created(escrowId, TOTAL), added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n)], { db, ctx }); + + const result = await processBatch( + [added(escrowId, 0, W(2), 5_000_000_000n, 250_000_000n)], + { db, ctx } + ); + + expect(result.findings).toBe(1); + expect(findings()[0].kind).toBe('AMOUNT_MISMATCH'); + // Financial identity is immutable: the original row stands. + expect(payments()[0].recipientAddress).toBe(W(1)); + expect(payments()[0].amountBaseUnits).toBe(10_000_000_000n); + }); + + it('flags an aggregate finalize that disagrees with per-payment state', async () => { + const escrowId = 24; + mapEscrowToTenant(24); + await processBatch( + [created(escrowId, TOTAL), ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate))], + { db, ctx } + ); + + // The escrow-level summary claims three settled, but no per-payment events + // arrived. The summary never sets PAID; it only surfaces the gap. + const result = await processBatch([finalized(escrowId, TOTAL, 3)], { db, ctx }); + + expect(result.findings).toBe(1); + expect(findings()[0].kind).toBe('CHAIN_PAID_DB_NOT'); + expect(payments().every((p) => p.state !== PaymentState.PAID)).toBe(true); + }); + + it('flags a cancellation arriving for an already-settled payment', async () => { + const escrowId = 25; + mapEscrowToTenant(25); + await processBatch(goldenPathEvents(escrowId), { db, ctx }); + + const result = await processBatch([cancelPayment(escrowId, 0)], { db, ctx }); + + expect(result.findings).toBe(1); + expect(findings()[0].kind).toBe('DB_PAID_CHAIN_NOT'); + expect(payments().find((p) => p.onChainPaymentIndex === 0)!.state).toBe(PaymentState.PAID); + }); +}); + +describe('cancellation and rotation', () => { + it('cancels each payment individually from per-payment events', async () => { + const escrowId = 31; + mapEscrowToTenant(31); + await processBatch( + [created(escrowId, TOTAL), ...THREE_PAYEES.map((p) => added(escrowId, p.index, p.worker, p.amount, p.rate))], + { db, ctx } + ); + await processBatch(THREE_PAYEES.map((p) => cancelPayment(escrowId, p.index)), { db, ctx }); + + expect(payments().every((p) => p.state === PaymentState.CANCELLED)).toBe(true); + }); + + it('returns payments to AWAITING_ORACLE when the oracle key rotates', async () => { + const escrowId = 32; + mapEscrowToTenant(32); + await processBatch( + [created(escrowId, TOTAL), added(escrowId, 0, W(1), 10_000_000_000n, 250_000_000n), + hours(escrowId, 0, 40n), approve(escrowId, 'manager')], + { db, ctx } + ); + expect(payments()[0].state).toBe(PaymentState.AWAITING_FINANCE); + + await processBatch( + [{ id: tok(), ledger: 150, topic0: 'oracle', topic1: 'rotate', value: [escrowId, 1] }], + { db, ctx } + ); + + // Rotation revokes verified proofs on-chain, so the projection must follow β€” + // otherwise the dashboard shows an approval chain resting on a revoked proof. + expect(payments()[0].state).toBe(PaymentState.AWAITING_ORACLE); + expect(db.__tables.escrow.rows[0].oracleRotations).toBe(1); + }); +}); + +describe('audit trail', () => { + it('records a state transition for every payment movement', async () => { + mapEscrowToTenant(41); + await processBatch(goldenPathEvents(41), { db, ctx }); + const events = db.__tables.auditEvent.rows; + + // Indexed, oracle-verified, both approvals, and paid β€” per payment. + expect(events.filter((e) => e.type === 'payment.indexed')).toHaveLength(3); + expect(events.filter((e) => e.type === 'payment.oracle.verified')).toHaveLength(3); + expect(events.filter((e) => e.type === 'approval.manager.observed')).toHaveLength(3); + expect(events.filter((e) => e.type === 'approval.finance.observed')).toHaveLength(3); + expect( + events.filter((e) => e.type === 'payment.state.changed' && e.newState === PaymentState.PAID) + ).toHaveLength(3); + }); + + it('attributes chain-driven transitions to the indexer, not a person', async () => { + mapEscrowToTenant(42); + await processBatch(goldenPathEvents(42), { db, ctx }); + const paidEvents = db.__tables.auditEvent.rows.filter( + (e) => e.type === 'payment.state.changed' && e.newState === PaymentState.PAID + ); + for (const e of paidEvents) { + expect(e.actorSystem).toBe('indexer'); + expect(e.actorAddress).toBeNull(); + } + }); + + it('carries previous and new state so history is reconstructable', async () => { + mapEscrowToTenant(43); + await processBatch(goldenPathEvents(43), { db, ctx }); + const transition = db.__tables.auditEvent.rows.find( + (e) => e.type === 'payment.state.changed' && e.newState === PaymentState.PAID + ); + expect(transition.previousState).toBe(PaymentState.READY_TO_SETTLE); + expect(transition.txHash).toBeTruthy(); + }); +}); + +describe('money handling', () => { + it('keeps amounts as bigint end to end', async () => { + mapEscrowToTenant(51); + await processBatch(goldenPathEvents(51), { db, ctx }); + for (const p of payments()) { + expect(typeof p.amountBaseUnits).toBe('bigint'); + expect(typeof p.rateBaseUnits).toBe('bigint'); + expect(typeof p.hours).toBe('bigint'); + } + }); + + it('handles an amount beyond Number.MAX_SAFE_INTEGER without loss', async () => { + // 10^18 base units β€” far past 2^53-1, where a Number cast starts rounding. + const huge = 1_000_000_000_000_000_000n; + mapEscrowToTenant(61); + await processBatch( + [created(61, huge), added(61, 0, W(1), huge, 1n)], + { db, ctx } + ); + expect(payments()[0].amountBaseUnits).toBe(huge); + expect(payments()[0].hours).toBe(huge); + }); + + it('stores bigints as strings in the chain event payload', async () => { + // JSON.stringify throws on bigint, so an unconverted payload is a crash. + await processBatch([created(62, TOTAL), added(62, 0, W(1), 10_000_000_000n, 250_000_000n)], { db, ctx }); + const marker = db.__tables.chainEvent.rows.find((c) => c.type === 'payment_added'); + expect(marker.payload.amountBaseUnits).toBe('10000000000'); + expect(() => JSON.stringify(marker.payload)).not.toThrow(); + }); +}); + +describe('tenant attribution', () => { + it('refuses to project an escrow it has no mapping for', async () => { + // The chain knows nothing about organizations. Attaching an unmapped escrow + // to whichever tenant seemed likely would place one party's payroll, + // recipients and amounts inside another's workspace. + const result = await processBatch(goldenPathEvents(81), { db, ctx }); + + expect(result.unattributed).toBeGreaterThan(0); + expect(result.paymentsCreated).toBe(0); + expect(payments()).toHaveLength(0); + // Nothing was invented: no organization, no escrow, no batch. + expect(db.__tables.organization.rows).toHaveLength(1); // only the seeded one + expect(db.__tables.escrow.rows).toHaveLength(0); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); + + it('records the unattributable event so it is visible, not dropped', async () => { + await processBatch([created(82, TOTAL)], { db, ctx }); + + const marker = db.__tables.chainEvent.rows[0]; + expect(marker).toBeDefined(); + expect(marker.attributed).toBe(false); + // The payload is kept, so the event can be applied after a claim. + expect(marker.payload).toBeTruthy(); + }); + + it('applies a previously unattributed event once the escrow is claimed', async () => { + // A claim must not lose the history that arrived before it. + const events = goldenPathEvents(83); + const first = await processBatch(events, { db, ctx }); + expect(first.unattributed).toBeGreaterThan(0); + expect(payments()).toHaveLength(0); + + // An operator claims escrow 83 into their organization. + mapEscrowToTenant(83); + + const second = await processBatch(events, { db, ctx }); + expect(second.unattributed).toBe(0); + expect(payments()).toHaveLength(3); + expect(payments().every((p) => p.state === PaymentState.PAID)).toBe(true); + expect(payments().every((p) => p.orgId === ORG)).toBe(true); + }); + + it('does not leak an escrow between tenants on the same on-chain id', async () => { + // Escrow ids are assigned per contract, so the same id exists in other + // deployments owned by other tenants. + const OTHER = 'org_other'; + db.__tables.organization.rows.push({ id: OTHER, name: 'Other', slug: 'other' }); + mapEscrowToTenant(91, OTHER); + + await processBatch(goldenPathEvents(91), { db, ctx }); + + expect(payments()).toHaveLength(3); + expect(payments().every((p) => p.orgId === OTHER)).toBe(true); + expect(payments().some((p) => p.orgId === ORG)).toBe(false); + }); + + it('does not match an escrow mapped to a DIFFERENT deployment', async () => { + // Same on-chain id, different contract: not the same escrow. + db.__tables.escrow.rows.push({ + id: 'esc_mainnet', orgId: ORG, onChainId: 95, + contractId: 'CCTF5WBOQR7JP2KPLQT372X7JCGCINHDFRSAPF4YTYRKZXZ3J2XPRFFW', + network: 'public', managerAddress: MANAGER, financeApproverAddress: 'GF', + assetDecimals: 7, + }); + + const result = await processBatch([created(95, TOTAL)], { db, ctx }); + + expect(result.unattributed).toBe(1); + expect(payments()).toHaveLength(0); + }); + + it('attributes every payment in a batch to the same organization', async () => { + mapEscrowToTenant(96); + await processBatch(goldenPathEvents(96), { db, ctx }); + + const orgs = new Set(payments().map((p) => p.orgId)); + expect(orgs.size).toBe(1); + expect([...orgs][0]).toBe(ORG); + + // Findings and audit rows inherit the same tenant. + for (const row of db.__tables.auditEvent.rows) expect(row.orgId).toBe(ORG); + }); +}); + +describe('unknown events', () => { + it('records but does not halt on an unrecognized event', async () => { + // A future contract version emitting something new must not stop ingestion + // of the events we do understand. + mapEscrowToTenant(71); + const result = await processBatch( + [ + { id: tok(), ledger: 100, topic0: 'future', topic1: 'thing', value: [1, 2, 3] }, + created(71, TOTAL), + ], + { db, ctx } + ); + expect(result.processed).toBe(2); + expect(db.__tables.chainEvent.rows.some((c) => c.type.startsWith('unknown:'))).toBe(true); + expect(db.__tables.escrow.rows).toHaveLength(1); + }); +}); diff --git a/src/lib/indexer/events.ts b/src/lib/indexer/events.ts index a95af7e..13d5065 100644 --- a/src/lib/indexer/events.ts +++ b/src/lib/indexer/events.ts @@ -1,62 +1,169 @@ /** * Parsing of CoreFlow contract events into typed domain events. * - * The contract emits these (topic0, topic1) symbol pairs: + * ── Escrow-level events ────────────────────────────────────────────────────── * ("escrow", "created") -> (escrow_id, manager, total_amount) - * ("hours", "submit") -> (escrow_id, payment_id, hours_logged) * ("approve", "manager") -> escrow_id * ("approve", "finance") -> escrow_id * ("payment", "final") -> (escrow_id, total_amount, count) * ("escrow", "cancel") -> escrow_id + * ("hours", "submit") -> (escrow_id, payment_id, hours_logged) + * + * ── Per-payment events ─────────────────────────────────────────────────────── + * ("payment", "add") -> (escrow_id, index, worker, token, amount, rate, start, end) + * ("payment", "paid") -> (escrow_id, index, worker, token, amount, hours) + * ("payment", "cancel") -> (escrow_id, index) + * + * The per-payment events are what make a multi-payee batch indexable. Without + * them the log says only how much moved in aggregate, so a projection would have + * to read `get_escrow` at index time β€” returning CURRENT state, not state at + * that ledger, which makes re-indexing produce different answers. With them the + * projection is a pure function of the log. + * + * Money stays `bigint` throughout. `scValToNative` yields bigint for i128, and + * coercing through `Number` silently loses precision above 2^53-1. */ export type CoreFlowEvent = - | { kind: 'created'; escrowId: number } - | { kind: 'hours'; escrowId: number; paymentId: number; hours: number } + | { kind: 'created'; escrowId: number; manager: string; totalAmount: bigint } + | { + kind: 'payment_added'; + escrowId: number; + paymentIndex: number; + worker: string; + token: string; + amountBaseUnits: bigint; + rateBaseUnits: bigint; + periodStart: bigint; + periodEnd: bigint; + } + | { kind: 'hours'; escrowId: number; paymentIndex: number; hours: bigint } | { kind: 'manager_approved'; escrowId: number } | { kind: 'finance_approved'; escrowId: number } - | { kind: 'finalized'; escrowId: number } - | { kind: 'cancelled'; escrowId: number }; + | { + kind: 'payment_paid'; + escrowId: number; + paymentIndex: number; + worker: string; + token: string; + amountBaseUnits: bigint; + hours: bigint; + } + | { kind: 'payment_cancelled'; escrowId: number; paymentIndex: number } + | { kind: 'finalized'; escrowId: number; totalAmount: bigint; count: number } + | { kind: 'cancelled'; escrowId: number } + | { kind: 'oracle_rotated'; escrowId: number; rotations: number }; -/** Coerce a possibly-bigint scalar to a JS number. */ +/** Coerce a u32/u64-ish scalar to a JS number. Safe: these are counters/ids. */ function toNum(v: unknown): number { if (typeof v === 'bigint') return Number(v); if (Array.isArray(v)) return Number(v[0]); return Number(v); } +/** Coerce an i128 to bigint WITHOUT passing through Number. */ +function toBig(v: unknown): bigint { + if (typeof v === 'bigint') return v; + if (typeof v === 'number') return BigInt(Math.trunc(v)); + if (typeof v === 'string') return BigInt(v); + if (Array.isArray(v)) return toBig(v[0]); + return 0n; +} + +function toAddr(v: unknown): string { + return typeof v === 'string' ? v : String(v); +} + /** - * Map a decoded contract event to a typed CoreFlowEvent, or null if it is not - * a recognized CoreFlow event. `value` is the event's data already decoded by - * scValToNative (a scalar or tuple/array). + * Map a decoded contract event to a typed CoreFlowEvent, or null if it is not a + * recognized CoreFlow event. `value` is the event data already decoded by + * `scValToNative` (a scalar or tuple/array). + * + * Unknown events return null rather than throwing: a future contract version + * emitting something new must not halt ingestion of the events we do understand. */ export function parseCoreFlowEvent( topic0: string, topic1: string, value: unknown ): CoreFlowEvent | null { - const tuple = Array.isArray(value) ? value : [value]; + const t = Array.isArray(value) ? value : [value]; const key = `${topic0}:${topic1}`; switch (key) { case 'escrow:created': - return { kind: 'created', escrowId: toNum(tuple[0]) }; + return { + kind: 'created', + escrowId: toNum(t[0]), + manager: toAddr(t[1]), + totalAmount: toBig(t[2]), + }; + + case 'payment:add': + return { + kind: 'payment_added', + escrowId: toNum(t[0]), + paymentIndex: toNum(t[1]), + worker: toAddr(t[2]), + token: toAddr(t[3]), + amountBaseUnits: toBig(t[4]), + rateBaseUnits: toBig(t[5]), + periodStart: toBig(t[6]), + periodEnd: toBig(t[7]), + }; + case 'hours:submit': return { kind: 'hours', - escrowId: toNum(tuple[0]), - paymentId: toNum(tuple[1]), - hours: toNum(tuple[2]), + escrowId: toNum(t[0]), + paymentIndex: toNum(t[1]), + hours: toBig(t[2]), }; + case 'approve:manager': - return { kind: 'manager_approved', escrowId: toNum(tuple[0]) }; + return { kind: 'manager_approved', escrowId: toNum(t[0]) }; + case 'approve:finance': - return { kind: 'finance_approved', escrowId: toNum(tuple[0]) }; + return { kind: 'finance_approved', escrowId: toNum(t[0]) }; + + case 'payment:paid': + return { + kind: 'payment_paid', + escrowId: toNum(t[0]), + paymentIndex: toNum(t[1]), + worker: toAddr(t[2]), + token: toAddr(t[3]), + amountBaseUnits: toBig(t[4]), + hours: toBig(t[5]), + }; + + case 'payment:cancel': + return { + kind: 'payment_cancelled', + escrowId: toNum(t[0]), + paymentIndex: toNum(t[1]), + }; + case 'payment:final': - return { kind: 'finalized', escrowId: toNum(tuple[0]) }; + return { + kind: 'finalized', + escrowId: toNum(t[0]), + totalAmount: toBig(t[1]), + count: toNum(t[2]), + }; + case 'escrow:cancel': - return { kind: 'cancelled', escrowId: toNum(tuple[0]) }; + return { kind: 'cancelled', escrowId: toNum(t[0]) }; + + case 'oracle:rotate': + return { kind: 'oracle_rotated', escrowId: toNum(t[0]), rotations: toNum(t[1]) }; + default: return null; } } + +/** The payment slot an event refers to, when it refers to one. */ +export function paymentIndexOf(ev: CoreFlowEvent): number | null { + return 'paymentIndex' in ev ? ev.paymentIndex : null; +} diff --git a/src/lib/indexer/index.ts b/src/lib/indexer/index.ts index df51460..5f26494 100644 --- a/src/lib/indexer/index.ts +++ b/src/lib/indexer/index.ts @@ -1,167 +1,891 @@ /** - * CoreFlow chain-event indexer. + * Chain β†’ database projection. * - * Projects on-chain contract events into the Postgres index so the DB is a - * faithful, authoritative reflection of chain state β€” replacing the previous - * fire-and-forget client write-through that could silently drift. + * ── Authority ──────────────────────────────────────────────────────────────── + * The contract's event log is the authority for settlement. This module is the + * ONLY writer permitted to move a payment to PAID, and it does so only on a + * `payment/paid` event β€” which the contract emits per payee, after that payee's + * SAC transfer succeeded. * - * Design: - * - A single-row IndexerCursor tracks the last processed ledger. - * - Each raw event has a unique RPC paging token; a ChainEvent row records it - * so reprocessing the same event is a no-op (idempotency). - * - applyEvent mutations are themselves idempotent (updateMany / upsert), so - * out-of-order or repeated delivery converges to the correct state. + * ── Properties this is built for ───────────────────────────────────────────── + * IDEMPOTENT Every event is keyed by its RPC paging token; a re-seen token is + * skipped. Payment rows are keyed by (escrowId, paymentIndex), so + * replaying a settlement cannot create a second payment. + * RESTARTABLE The cursor is per (contract, network) and advances only after the + * events in a batch are committed. + * DETERMINISTIC The projection reads only the event payload, never current + * contract state. Re-indexing from ledger zero produces the same + * rows. + * PARTIAL-SAFE Each event is applied in its own transaction together with its + * ChainEvent marker, so an interrupted batch leaves a prefix + * applied and resumes from exactly there β€” never half an event. + * RECONCILING A disagreement between log and database is recorded as a + * ReconciliationFinding rather than silently overwritten. * - * The RPC reader is injected so the core logic is testable without a network. + * ── The defect this replaces ───────────────────────────────────────────────── + * The previous projection stored one worker and one amount per Escrow, so a + * three-payee settlement collapsed into a single row carrying the first payee's + * figures. The other two payments simply did not exist in the product. */ -import prisma from '@/lib/db/prisma'; +import { PaymentState } from '@prisma/client'; import { parseCoreFlowEvent, type CoreFlowEvent } from './events'; +import { applyTransition } from '@/lib/payments/service'; -/** Full escrow detail the indexer needs when first seeing an escrow. */ -export interface EscrowDetailForIndex { - worker: string; - amountCents: number; - rateCents: number; - tokenAddress: string | null; +/** A raw event row from the RPC, normalized for the indexer. */ +export interface RawIndexedEvent { + /** RPC paging token β€” globally unique, and the basis of ingest idempotency. */ + id: string; + ledger: number; + topic0: string; + topic1: string; + value: unknown; + txHash?: string; +} + +export interface IndexerContext { + contractId: string; + network: string; + /** Default asset decimals when an event does not carry them. */ + assetDecimals?: number; +} + +/** + * How an on-chain escrow maps to a CoreFlow organization. + * + * ── The rule ───────────────────────────────────────────────────────────────── + * The chain knows nothing about organizations. The ONLY authoritative mapping is + * an `Escrow` row that the application itself created β€” written when a member of + * a known organization submitted the creation transaction, or when an operator + * explicitly claimed an escrow into their workspace. + * + * The indexer therefore never invents a tenant. An escrow it has no mapping for + * is recorded as UNATTRIBUTED and left for a human, because the alternative β€” + * attaching it to whichever organization seems likely β€” would silently place one + * party's payroll, recipients and amounts inside another's workspace. That is a + * data breach produced by a convenience default. + * + * Escrows created outside the app (CLI, validation scripts, another client) are + * consequently invisible until claimed. That is the intended trade. + */ +export type TenantResolution = + | { attributed: true; orgId: string; escrowId: string } + | { attributed: false; reason: string }; + +async function resolveEscrowTenant( + tx: any, + ctx: IndexerContext, + escrowOnChainId: number +): Promise { + const escrow = await tx.escrow.findFirst({ + where: { + onChainId: escrowOnChainId, + // Scoped to the deployment: escrow ids are assigned per contract, so id 3 + // exists on Testnet v2 AND on Mainnet v1, owned by different tenants. + contractId: ctx.contractId, + network: ctx.network, + }, + select: { id: true, orgId: true }, + }); + + if (escrow) { + return { attributed: true, orgId: escrow.orgId, escrowId: escrow.id }; + } + return { + attributed: false, + reason: + `Escrow ${escrowOnChainId} on ${ctx.network} (${ctx.contractId.slice(0, 8)}…) ` + + 'has no CoreFlow record, so it cannot be attributed to an organization. ' + + 'An operator must claim it.', + }; } export interface IndexerDeps { - db: any; // PrismaClient (or a test double) - /** Fetch full escrow detail from the contract when a `created` event arrives. */ - fetchEscrowDetail: (escrowId: number) => Promise; + db: any; + ctx: IndexerContext; +} + +export interface RunResult { + processed: number; + skipped: number; + lastLedger: number; + paymentsCreated: number; + paymentsPaid: number; + findings: number; + /** Events recorded but NOT projected, because no tenant mapping exists. */ + unattributed: number; +} + +const INDEXER_ACTOR = { kind: 'indexer' as const, system: 'indexer' }; + +/** Stable, deterministic reference for a batch auto-created by the indexer. */ +function escrowBatchReference(escrowId: number): string { + return `CHAIN-${String(escrowId).padStart(5, '0')}`; +} + +/** + * Ensure the Escrow row exists. Deterministic id derived from the deployment and + * on-chain id, so replaying `created` cannot produce a second escrow. + */ +async function ensureEscrow( + tx: any, + ctx: IndexerContext, + orgId: string, + escrowId: number, + patch: Record = {} +): Promise { + const existing = await tx.escrow.findUnique({ where: { onChainId: escrowId } }); + if (existing) { + if (Object.keys(patch).length > 0) { + return tx.escrow.update({ where: { id: existing.id }, data: patch }); + } + return existing; + } + + return tx.escrow.create({ + data: { + orgId, + onChainId: escrowId, + contractId: ctx.contractId, + network: ctx.network, + // Addresses are filled in by the `created` event; an escrow discovered + // mid-stream (indexing started after creation) carries empty strings until + // a reconciliation pass fills them, rather than inventing values. + managerAddress: (patch.managerAddress as string) ?? '', + financeApproverAddress: '', + assetDecimals: ctx.assetDecimals ?? 7, + ...patch, + }, + }); +} + +/** Ensure a PayrollBatch exists to hold an escrow's payments. */ +async function ensureBatch(tx: any, orgId: string, escrowId: number): Promise { + const reference = escrowBatchReference(escrowId); + const existing = await tx.payrollBatch.findUnique({ + where: { orgId_reference: { orgId, reference } }, + }); + if (existing) return existing; + return tx.payrollBatch.create({ + data: { orgId, reference, sourceFilename: null }, + }); +} + +/** Ensure a Worker row exists for a payee wallet. */ +async function ensureWorker(tx: any, orgId: string, wallet: string): Promise { + const existing = await tx.worker.findUnique({ + where: { orgId_walletAddress: { orgId, walletAddress: wallet } }, + }); + if (existing) return existing; + return tx.worker.create({ data: { orgId, walletAddress: wallet } }); } /** - * Apply a single domain event to the DB. Idempotent: uses upsert for creation - * and updateMany for transitions (no-op if the row is not present yet, so event - * ordering does not matter for correctness). + * Open a reconciliation finding. + * + * Findings are recorded, not corrected in place. Overwriting the losing side of a + * disagreement destroys the only evidence the two ever diverged β€” which is + * exactly what an auditor needs. + */ +async function openFinding( + tx: any, + orgId: string, + input: { + paymentId?: string; + kind: string; + dbState?: string; + chainState?: string; + detail: string; + metadata?: Record; + } +): Promise { + await tx.reconciliationFinding.create({ + data: { + orgId, + paymentId: input.paymentId ?? null, + kind: input.kind as any, + dbState: input.dbState ?? null, + chainState: input.chainState ?? null, + detail: input.detail, + metadata: (input.metadata ?? {}) as any, + }, + }); +} + +export interface ApplyResult { + paymentsCreated: number; + /** + * Payments the application already had, advanced by observed chain evidence. + * + * Distinct from `paymentsCreated`: a payroll uploaded as a CSV and then funded + * produces rows the indexer RECOGNISES rather than invents, and conflating the + * two would make a funded batch look like an indexer-discovered one. + */ + paymentsAdvanced: number; + paymentsPaid: number; + findings: number; + /** False when no organization could be resolved for the event's escrow. */ + attributed: boolean; + unattributedReason?: string; +} + +/** + * Apply one domain event inside an existing transaction. + * + * Takes `tx` rather than a client so the caller can commit the event's effects + * and its ChainEvent marker together β€” the property that makes a crash mid-batch + * resumable rather than ambiguous. */ -export async function applyEvent(ev: CoreFlowEvent, deps: IndexerDeps): Promise { - const { db, fetchEscrowDetail } = deps; +export async function applyEvent( + tx: any, + ctx: IndexerContext, + ev: CoreFlowEvent, + meta: { txHash?: string; ledger: number } +): Promise { + const out: ApplyResult = { + paymentsCreated: 0, + paymentsAdvanced: 0, + paymentsPaid: 0, + findings: 0, + attributed: true, + }; + + // Resolve the owning organization from the application's own record. An event + // for an escrow we have no mapping for is recorded and skipped β€” never guessed + // onto a tenant. + const tenant = await resolveEscrowTenant(tx, ctx, ev.escrowId); + if (!tenant.attributed) { + out.attributed = false; + out.unattributedReason = tenant.reason; + return out; + } + const orgId = tenant.orgId; switch (ev.kind) { case 'created': { - const detail = await fetchEscrowDetail(ev.escrowId); - await db.escrow.upsert({ - where: { onChainId: ev.escrowId }, - create: { - onChainId: ev.escrowId, - workerPubKey: detail.worker, - amountCents: detail.amountCents, - rateCents: detail.rateCents, - tokenAddress: detail.tokenAddress, - status: 'pending_hours', + await ensureEscrow(tx, ctx, orgId, ev.escrowId, { + managerAddress: ev.manager, + totalAmountBaseUnits: ev.totalAmount, + }); + await ensureBatch(tx, orgId, ev.escrowId); + return out; + } + + /** + * THE FIX. One Payment row per on-chain payment slot, carrying that slot's + * own recipient, asset, amount, rate and period. + */ + case 'payment_added': { + const escrow = await ensureEscrow(tx, ctx, orgId, ev.escrowId); + const batch = await ensureBatch(tx, orgId, ev.escrowId); + const worker = await ensureWorker(tx, orgId, ev.worker); + + const existing = await tx.payment.findUnique({ + where: { + escrowId_onChainPaymentIndex: { + escrowId: escrow.id, + onChainPaymentIndex: ev.paymentIndex, + }, + }, + }); + + if (existing) { + // Replay. The financial identity of a payment is immutable, so a + // re-delivered event that disagrees is a discrepancy to surface, not an + // update to apply. + if ( + existing.amountBaseUnits !== ev.amountBaseUnits || + existing.recipientAddress !== ev.worker + ) { + await openFinding(tx, orgId, { + paymentId: existing.id, + kind: 'AMOUNT_MISMATCH', + dbState: `${existing.recipientAddress}:${existing.amountBaseUnits}`, + chainState: `${ev.worker}:${ev.amountBaseUnits}`, + detail: + 'A replayed payment/add event disagrees with the stored payment. ' + + 'The stored row was NOT overwritten.', + metadata: { escrowId: ev.escrowId, paymentIndex: ev.paymentIndex }, + }); + out.findings++; + } + + // A payment the APPLICATION created and then funded. The CSV produced the + // row; the funding bridge linked it to this on-chain slot; this event is the + // chain evidence that custody exists for it. So advance it β€” the payment is + // waiting for an attestation now, not for funding. + // + // Without this, a payment created from a payroll file would sit in + // VALIDATING forever: the create branch below sets AWAITING_ORACLE, but it + // only runs for rows the indexer itself invents. + if (existing.state === PaymentState.VALIDATING) { + const advanced = await applyTransition(tx, { + paymentId: existing.id, + orgId, + to: PaymentState.AWAITING_ORACLE, + actor: { kind: 'indexer', system: 'indexer' }, + }); + if (advanced.ok && advanced.changed) out.paymentsAdvanced++; + } + + return out; + } + + const hours = + ev.rateBaseUnits > 0n ? ev.amountBaseUnits / ev.rateBaseUnits : 0n; + + await tx.payment.create({ + data: { + orgId, + batchId: batch.id, + escrowId: escrow.id, + workerId: worker.id, + recipientAddress: ev.worker, + onChainPaymentIndex: ev.paymentIndex, + assetContractId: ev.token, + assetDecimals: ctx.assetDecimals ?? escrow.assetDecimals ?? 7, + amountBaseUnits: ev.amountBaseUnits, + rateBaseUnits: ev.rateBaseUnits, + hours, + periodStart: ev.periodStart > 0n ? new Date(Number(ev.periodStart) * 1000) : null, + periodEnd: ev.periodEnd > 0n ? new Date(Number(ev.periodEnd) * 1000) : null, + // Funded on-chain, awaiting an attestation. + state: PaymentState.AWAITING_ORACLE, + stateUpdatedAt: new Date(), }, - update: { - workerPubKey: detail.worker, - tokenAddress: detail.tokenAddress, + }); + out.paymentsCreated++; + + await tx.auditEvent.create({ + data: { + orgId, + type: 'payment.indexed', + actorSystem: 'indexer', + escrowId: escrow.id, + batchId: batch.id, + newState: PaymentState.AWAITING_ORACLE, + txHash: meta.txHash ?? null, + metadata: { + escrowOnChainId: ev.escrowId, + paymentIndex: ev.paymentIndex, + ledger: meta.ledger, + } as any, }, }); - return; + return out; } - case 'hours': - await db.escrow.updateMany({ - where: { onChainId: ev.escrowId }, - data: { status: 'pending_manager' }, + + case 'hours': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + const payment = await tx.payment.findUnique({ + where: { + escrowId_onChainPaymentIndex: { + escrowId: escrow.id, + onChainPaymentIndex: ev.paymentIndex, + }, + }, + }); + if (!payment) return out; + + // The contract accepted an Ed25519 attestation for this payment β€” the only + // thing that makes ORACLE_VERIFIED true. + await tx.payment.updateMany({ + where: { id: payment.id, state: PaymentState.AWAITING_ORACLE }, + data: { + state: PaymentState.ORACLE_VERIFIED, + stateUpdatedAt: new Date(), + hours: ev.hours, + }, }); - return; + await tx.auditEvent.create({ + data: { + orgId, + type: 'payment.oracle.verified', + actorSystem: 'indexer', + paymentId: payment.id, + escrowId: escrow.id, + previousState: payment.state, + newState: PaymentState.ORACLE_VERIFIED, + txHash: meta.txHash ?? null, + metadata: { hours: ev.hours.toString(), ledger: meta.ledger } as any, + }, + }); + return out; + } + case 'manager_approved': - await db.escrow.updateMany({ - where: { onChainId: ev.escrowId }, - data: { managerApproved: true, status: 'pending_finance' }, - }); - return; - case 'finance_approved': - await db.escrow.updateMany({ - where: { onChainId: ev.escrowId }, - data: { financeApproved: true, status: 'ready' }, - }); - return; - case 'finalized': - await db.escrow.updateMany({ - where: { onChainId: ev.escrowId }, - data: { status: 'paid' }, - }); - return; - case 'cancelled': - await db.escrow.updateMany({ - where: { onChainId: ev.escrowId }, - data: { status: 'cancelled' }, - }); - return; + case 'finance_approved': { + const isManager = ev.kind === 'manager_approved'; + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + + await tx.escrow.update({ + where: { id: escrow.id }, + data: isManager ? { managerApproved: true } : { financeApproved: true }, + }); + + // Approval is per-escrow on-chain; it advances every payment in that escrow + // that is waiting on this specific approver. + const waitingOn = isManager + ? PaymentState.AWAITING_MANAGER + : PaymentState.AWAITING_FINANCE; + const nextState = isManager + ? PaymentState.AWAITING_FINANCE + : PaymentState.READY_TO_SETTLE; + + // ORACLE_VERIFIED payments enter the approval chain first. + await tx.payment.updateMany({ + where: { escrowId: escrow.id, state: PaymentState.ORACLE_VERIFIED }, + data: { state: PaymentState.AWAITING_MANAGER, stateUpdatedAt: new Date() }, + }); + + const affected = await tx.payment.findMany({ + where: { escrowId: escrow.id, state: waitingOn }, + select: { id: true }, + }); + await tx.payment.updateMany({ + where: { escrowId: escrow.id, state: waitingOn }, + data: { state: nextState, stateUpdatedAt: new Date() }, + }); + + for (const p of affected) { + await tx.auditEvent.create({ + data: { + orgId, + type: isManager ? 'approval.manager.observed' : 'approval.finance.observed', + actorSystem: 'indexer', + paymentId: p.id, + escrowId: escrow.id, + previousState: waitingOn, + newState: nextState, + txHash: meta.txHash ?? null, + metadata: { ledger: meta.ledger } as any, + }, + }); + } + return out; + } + + /** Chain-confirmed settlement for ONE payee. The only path to PAID. */ + case 'payment_paid': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + + const payment = await tx.payment.findUnique({ + where: { + escrowId_onChainPaymentIndex: { + escrowId: escrow.id, + onChainPaymentIndex: ev.paymentIndex, + }, + }, + }); + + if (!payment) { + // The chain settled a payment this database has no row for. + await openFinding(tx, orgId, { + kind: 'ORPHAN_ON_CHAIN', + chainState: 'PAID', + detail: + `Escrow ${ev.escrowId} payment ${ev.paymentIndex} settled on-chain ` + + 'but has no database row. Indexing likely started after creation.', + metadata: { + escrowOnChainId: ev.escrowId, + paymentIndex: ev.paymentIndex, + worker: ev.worker, + amountBaseUnits: ev.amountBaseUnits.toString(), + }, + }); + out.findings++; + return out; + } + + // The chain says this much moved. If our record disagrees, record it and + // still mark PAID β€” the transfer happened either way, and the amount + // discrepancy is a separate fact that needs a human. + if (payment.amountBaseUnits !== ev.amountBaseUnits) { + await openFinding(tx, orgId, { + paymentId: payment.id, + kind: 'AMOUNT_MISMATCH', + dbState: payment.amountBaseUnits.toString(), + chainState: ev.amountBaseUnits.toString(), + detail: 'Settled amount differs from the recorded payment amount.', + }); + out.findings++; + } + if (payment.recipientAddress !== ev.worker) { + await openFinding(tx, orgId, { + paymentId: payment.id, + kind: 'RECIPIENT_MISMATCH', + dbState: payment.recipientAddress, + chainState: ev.worker, + detail: 'Settled recipient differs from the recorded recipient.', + }); + out.findings++; + } + + if (payment.state === PaymentState.PAID) return out; // replay + + // Routed through the state machine so the transition table and the audit + // trail apply to chain-driven changes exactly as they do to user actions. + const result = await applyTransition(tx, { + paymentId: payment.id, + to: PaymentState.PAID, + actor: INDEXER_ACTOR, + orgId, + txHash: meta.txHash, + metadata: { + ledger: meta.ledger, + escrowOnChainId: ev.escrowId, + paymentIndex: ev.paymentIndex, + settledAmountBaseUnits: ev.amountBaseUnits.toString(), + hours: ev.hours.toString(), + }, + }); + + if (result.ok && result.changed) { + out.paymentsPaid++; + } else if (!result.ok) { + // The log says paid; the state machine would not allow it from where the + // payment currently sits. The log wins on fact, but the inconsistency is + // real and must be visible. + await openFinding(tx, orgId, { + paymentId: payment.id, + kind: 'CHAIN_PAID_DB_NOT', + dbState: payment.state, + chainState: 'PAID', + detail: + `Chain settled this payment but the recorded state (${payment.state}) ` + + `does not permit PAID: ${result.message}`, + }); + out.findings++; + } + return out; + } + + case 'payment_cancelled': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + const payment = await tx.payment.findUnique({ + where: { + escrowId_onChainPaymentIndex: { + escrowId: escrow.id, + onChainPaymentIndex: ev.paymentIndex, + }, + }, + }); + if (!payment) return out; + if (payment.state === PaymentState.PAID) { + // Cancelling something already settled is contradictory. + await openFinding(tx, orgId, { + paymentId: payment.id, + kind: 'DB_PAID_CHAIN_NOT', + dbState: 'PAID', + chainState: 'CANCELLED', + detail: 'A cancellation event arrived for a payment recorded as PAID.', + }); + out.findings++; + return out; + } + await applyTransition(tx, { + paymentId: payment.id, + to: PaymentState.CANCELLED, + actor: INDEXER_ACTOR, + orgId, + reason: 'Escrow cancelled on-chain; custody refunded.', + txHash: meta.txHash, + metadata: { ledger: meta.ledger }, + }); + return out; + } + + case 'cancelled': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + await tx.escrow.update({ where: { id: escrow.id }, data: { cancelled: true } }); + return out; + } + + case 'oracle_rotated': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + await tx.escrow.update({ + where: { id: escrow.id }, + data: { oracleRotations: ev.rotations }, + }); + // Rotation revokes every verified proof on that escrow, so the payments + // must go back to awaiting a fresh attestation. + await tx.payment.updateMany({ + where: { + escrowId: escrow.id, + state: { + in: [ + PaymentState.ORACLE_VERIFIED, + PaymentState.AWAITING_MANAGER, + PaymentState.AWAITING_FINANCE, + PaymentState.READY_TO_SETTLE, + ], + }, + }, + data: { + state: PaymentState.AWAITING_ORACLE, + stateUpdatedAt: new Date(), + stateReason: 'Oracle key rotated on-chain; prior attestations revoked.', + }, + }); + return out; + } + + /** + * Escrow-level settlement summary. Deliberately does NOT set any payment to + * PAID β€” that is what the per-payment events are for. It is used only to + * detect payments the aggregate says settled but which never produced a + * per-payment event. + */ + case 'finalized': { + const escrow = await tx.escrow.findUnique({ where: { onChainId: ev.escrowId } }); + if (!escrow) return out; + const unpaid = await tx.payment.count({ + where: { escrowId: escrow.id, state: { not: PaymentState.PAID } }, + }); + if (unpaid > 0) { + await openFinding(tx, orgId, { + kind: 'CHAIN_PAID_DB_NOT', + chainState: `finalized count=${ev.count}`, + detail: + `Escrow ${ev.escrowId} reported ${ev.count} settled payments on-chain, ` + + `but ${unpaid} database payment(s) are not PAID.`, + metadata: { escrowOnChainId: ev.escrowId, totalAmount: ev.totalAmount.toString() }, + }); + out.findings++; + } + return out; + } } } -/** A raw event row from the RPC, normalized for the indexer. */ -export interface RawIndexedEvent { - id: string; // RPC paging token (unique) - ledger: number; - topic0: string; - topic1: string; - value: unknown; +/** Read the cursor for a deployment. */ +export async function getCursor(db: any, ctx: IndexerContext): Promise { + const row = await db.indexerCursor.findUnique({ + where: { contractId_network: { contractId: ctx.contractId, network: ctx.network } }, + }); + return row?.lastLedger ?? 0; } -export interface RunResult { - processed: number; - skipped: number; - lastLedger: number; +async function setCursor(db: any, ctx: IndexerContext, ledger: number): Promise { + await db.indexerCursor.upsert({ + where: { contractId_network: { contractId: ctx.contractId, network: ctx.network } }, + create: { contractId: ctx.contractId, network: ctx.network, lastLedger: ledger }, + update: { lastLedger: ledger }, + }); } /** - * Process a batch of raw events idempotently and advance the cursor. - * Pure with respect to I/O via `deps` so it can be unit-tested. + * Process a batch of raw events and advance the cursor. + * + * Each event is committed together with its ChainEvent marker in ONE transaction. + * A crash therefore leaves a prefix of the batch applied, with the markers to + * prove which β€” so the next run resumes at the right place instead of either + * re-applying or skipping work. + * + * The cursor advances only after the loop, and only to the highest ledger whose + * events all committed. */ export async function processBatch( events: RawIndexedEvent[], deps: IndexerDeps ): Promise { - const { db } = deps; - let processed = 0; - let skipped = 0; - let lastLedger = 0; - - for (const raw of events) { - lastLedger = Math.max(lastLedger, raw.ledger); - - // Idempotency: skip already-recorded events. - const seen = await db.chainEvent.findUnique({ where: { id: raw.id } }); - if (seen) { - skipped++; - continue; + const { db, ctx } = deps; + const result: RunResult = { + processed: 0, skipped: 0, lastLedger: 0, + paymentsCreated: 0, paymentsPaid: 0, findings: 0, unattributed: 0, + }; + + // Deterministic order: by ledger, then by paging token. RPC ordering is not + // something to rely on when a projection's correctness depends on sequence. + const ordered = [...events].sort( + (a, b) => a.ledger - b.ledger || a.id.localeCompare(b.id) + ); + + let committedLedger = 0; + + for (const raw of ordered) { + const parsed = parseCoreFlowEvent(raw.topic0, raw.topic1, raw.value); + + try { + const applied = await db.$transaction(async (tx: any) => { + // Idempotency barrier inside the transaction: a concurrent worker that + // already recorded this token makes the unique constraint reject us, + // rather than both applying the same event. + // + // An UNATTRIBUTED event is deliberately NOT treated as done. It was + // recorded so it is visible, but nothing was projected β€” so once an + // operator claims the escrow, the next run must be able to apply it. + // Skipping it permanently would mean a claimed escrow silently missing + // all the history that arrived before the claim. + const seen = await tx.chainEvent.findUnique({ where: { id: raw.id } }); + if (seen && seen.attributed !== false) return null; + + const effect = parsed + ? await applyEvent(tx, ctx, parsed, { txHash: raw.txHash, ledger: raw.ledger }) + : { paymentsCreated: 0, paymentsPaid: 0, findings: 0, attributed: true }; + + // Upsert, because an unattributed event may be revisited after a claim. + await tx.chainEvent.upsert({ + where: { id: raw.id }, + update: { + attributed: effect.attributed, + processedAt: new Date(), + }, + create: { + id: raw.id, + contractId: ctx.contractId, + network: ctx.network, + type: parsed ? parsed.kind : `unknown:${raw.topic0}:${raw.topic1}`, + ledger: raw.ledger, + txHash: raw.txHash ?? null, + escrowOnChainId: parsed && 'escrowId' in parsed ? parsed.escrowId : null, + paymentIndex: + parsed && 'paymentIndex' in parsed ? parsed.paymentIndex : null, + // The decoded payload is stored so a projection bug can be fixed by + // replaying this log rather than re-reading the chain. + payload: serializePayload(parsed), + // Recorded either way. An unattributable event is kept so it can be + // replayed once an operator claims the escrow, rather than lost. + attributed: effect.attributed, + }, + }); + + return effect; + }); + + if (applied === null) { + result.skipped++; + } else { + result.processed++; + result.paymentsCreated += applied.paymentsCreated; + result.paymentsPaid += applied.paymentsPaid; + result.findings += applied.findings; + if (!applied.attributed) { + result.unattributed++; + console.warn(`[indexer] unattributed event: ${applied.unattributedReason}`); + } + } + committedLedger = Math.max(committedLedger, raw.ledger); + } catch (e: any) { + // A duplicate key on ChainEvent means another worker won the race β€” not an + // error worth halting for. + if (e?.code === 'P2002') { + result.skipped++; + committedLedger = Math.max(committedLedger, raw.ledger); + continue; + } + // Anything else: stop. Advancing past an event we failed to apply would + // lose it permanently, which for a payments ledger is worse than stalling. + result.lastLedger = committedLedger; + if (committedLedger > 0) await setCursor(db, ctx, committedLedger); + throw e; } + } - const ev = parseCoreFlowEvent(raw.topic0, raw.topic1, raw.value); + result.lastLedger = committedLedger; + if (committedLedger > 0) await setCursor(db, ctx, committedLedger); + return result; +} + +/** + * Re-apply stored events that could not be attributed when first seen. + * + * ── Why this exists ───────────────────────────────────────────────────────── + * The cursor advances past every ledger the indexer has read, including ledgers + * whose events had no tenant mapping. After an operator claims an escrow, those + * ledgers are behind the cursor and will never be fetched again β€” so without a + * replay path a claimed escrow would silently be missing all the history that + * arrived before the claim. + * + * This is the reason `ChainEvent.payload` stores the DECODED event: the projection + * can be rebuilt from the log rather than re-read from the chain. Re-reading would + * also work but is not equivalent β€” the chain returns current state, while the log + * returns what happened. + * + * Idempotent: an event that attributes successfully is flagged and not revisited; + * one that still cannot be attributed stays pending. + */ +export async function replayUnattributed( + db: any, + ctx: IndexerContext, + opts: { limit?: number } = {} +): Promise<{ examined: number; applied: number; stillUnattributed: number; paymentsCreated: number; paymentsPaid: number }> { + const pending = await db.chainEvent.findMany({ + where: { attributed: false, contractId: ctx.contractId, network: ctx.network }, + // Chronological, so a payment is created before it is marked paid. + orderBy: [{ ledger: 'asc' }, { id: 'asc' }], + take: opts.limit ?? 1000, + }); + + const out = { examined: pending.length, applied: 0, stillUnattributed: 0, paymentsCreated: 0, paymentsPaid: 0 }; + + for (const row of pending) { + const ev = deserializePayload(row.payload); if (!ev) { - skipped++; + // Unparseable or an unknown event type: nothing to project, so it is not + // "pending" in any useful sense. + await db.chainEvent.update({ where: { id: row.id }, data: { attributed: true } }); continue; } - await applyEvent(ev, deps); - await db.chainEvent.create({ - data: { - id: raw.id, - type: ev.kind, - ledger: raw.ledger, - escrowOnChainId: ev.escrowId, - }, - }); - processed++; - } + try { + const effect = await db.$transaction(async (tx: any) => { + const applied = await applyEvent(tx, ctx, ev, { + txHash: row.txHash ?? undefined, + ledger: row.ledger, + }); + await tx.chainEvent.update({ + where: { id: row.id }, + data: { attributed: applied.attributed, processedAt: new Date() }, + }); + return applied; + }); - if (lastLedger > 0) { - await db.indexerCursor.upsert({ - where: { id: 1 }, - create: { id: 1, lastLedger }, - update: { lastLedger }, - }); + if (effect.attributed) { + out.applied++; + out.paymentsCreated += effect.paymentsCreated; + out.paymentsPaid += effect.paymentsPaid; + } else { + out.stillUnattributed++; + } + } catch (e: any) { + // One bad event must not block the rest of the backlog. + console.error(`[indexer] replay failed for ${row.id}: ${e?.message}`); + out.stillUnattributed++; + } } - return { processed, skipped, lastLedger }; + return out; } -/** Read the persisted cursor (0 if none). */ -export async function getCursor(): Promise { - const row = await prisma.indexerCursor.findUnique({ where: { id: 1 } }); - return row?.lastLedger ?? 0; +/** Rebuild a typed event from a stored payload, reversing serializePayload. */ +function deserializePayload(payload: any): CoreFlowEvent | null { + if (!payload || typeof payload !== 'object' || !payload.kind) return null; + + // These fields were stringified on the way in because JSON has no bigint. + const BIGINT_FIELDS = [ + 'totalAmount', 'amountBaseUnits', 'rateBaseUnits', + 'periodStart', 'periodEnd', 'hours', + ]; + const out: Record = {}; + for (const [k, v] of Object.entries(payload)) { + out[k] = BIGINT_FIELDS.includes(k) && typeof v === 'string' ? BigInt(v) : v; + } + return out as unknown as CoreFlowEvent; +} + +/** JSON-safe payload: bigint is not serializable, so it is stored as a string. */ +function serializePayload(parsed: CoreFlowEvent | null): any { + if (!parsed) return null; + const out: Record = {}; + for (const [k, v] of Object.entries(parsed)) { + out[k] = typeof v === 'bigint' ? v.toString() : v; + } + return out; } diff --git a/src/lib/indexer/run.ts b/src/lib/indexer/run.ts index dd5a369..ebe9921 100644 --- a/src/lib/indexer/run.ts +++ b/src/lib/indexer/run.ts @@ -1,30 +1,50 @@ /** - * RPC-backed indexer run. Reads contract events from Soroban RPC since the last - * cursor and projects them into the DB via processBatch. Kept separate from - * ./index so the unit-tested core never imports the heavy Stellar SDK. + * RPC-backed indexer run. + * + * Reads CoreFlow contract events from Soroban RPC since the last cursor and hands + * them to `processBatch`. Kept separate from ./index so the unit-tested core + * never imports the heavy Stellar SDK β€” and, more importantly, so the projection + * logic can be tested without a network at all. + * + * No escrow state is read from the contract here. The projection is a function of + * the event log only; see the determinism note in ./index. */ import prisma from '@/lib/db/prisma'; import { STELLAR_CONFIG } from '@/lib/config'; +import { SAC_DECIMALS } from '@/lib/money'; import { - processBatch, - getCursor, - type RawIndexedEvent, - type EscrowDetailForIndex, - type RunResult, + processBatch, replayUnattributed, getCursor, + type RawIndexedEvent, type IndexerContext, type RunResult, } from './index'; +/** How far back to look when there is no cursor yet. */ const LOOKBACK_LEDGERS = 1000; -const PAGE_LIMIT = 100; +const PAGE_LIMIT = 200; export async function runIndexerFromRpc(): Promise { const sdk: any = await import('@stellar/stellar-sdk'); - const contractId = STELLAR_CONFIG.contract.id; + const contractId = STELLAR_CONFIG.requireContractId(); + const network = STELLAR_CONFIG.contract.network; const rpc = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); - const cursor = await getCursor(); + // No organization is resolved here, deliberately. + // + // This used to auto-create one organization per deployment and attach every + // discovered escrow to it. That is a guess: escrows created outside the app + // belong to whoever created them, and placing them in a shared bucket puts one + // party's payroll where another tenant might read it. Ownership now comes only + // from an Escrow row the application itself wrote β€” see resolveEscrowTenant. + const ctx: IndexerContext = { + contractId, + network, + assetDecimals: SAC_DECIMALS, + }; + + const cursor = await getCursor(prisma, ctx); const latest = await rpc.getLatestLedger(); - const startLedger = cursor > 0 ? cursor + 1 : Math.max(1, latest.sequence - LOOKBACK_LEDGERS); + const startLedger = + cursor > 0 ? cursor + 1 : Math.max(1, latest.sequence - LOOKBACK_LEDGERS); const resp = await rpc.getEvents({ startLedger, @@ -33,26 +53,30 @@ export async function runIndexerFromRpc(): Promise { }); const events: RawIndexedEvent[] = (resp.events ?? []).map((e: any) => ({ - id: e.id ?? e.pagingToken, + id: e.id, ledger: e.ledger, topic0: String(sdk.scValToNative(e.topic[0])), topic1: String(sdk.scValToNative(e.topic[1])), value: sdk.scValToNative(e.value), + txHash: e.txHash ?? e.transactionHash ?? undefined, })); - const fetchEscrowDetail = async (escrowId: number): Promise => { - const { CoreFlowClient } = await import('@/lib/contracts'); - const detail = await new CoreFlowClient().getEscrow(escrowId); - const p = detail.payments[0]; - return { - worker: p?.worker ?? 'unknown', - amountCents: p ? Number(p.amount) : 0, - rateCents: p ? Number(p.rate_per_hour) : 0, - // Token moved onto each payment row (per-payee assets); index the - // first row's asset, which is what the single-worker DB model expects. - tokenAddress: p?.token ?? null, - }; - }; + const result = await processBatch(events, { db: prisma, ctx }); - return processBatch(events, { db: prisma, fetchEscrowDetail }); + // Replay anything an earlier run could not attribute. An escrow claimed since + // then is behind the cursor, so its history would otherwise be unreachable. + const replayed = await replayUnattributed(prisma, ctx); + if (replayed.applied > 0) { + console.info( + `[indexer] replayed ${replayed.applied} previously unattributed event(s) ` + + `(+${replayed.paymentsCreated} payments, +${replayed.paymentsPaid} settled)` + ); + } + + return { + ...result, + paymentsCreated: result.paymentsCreated + replayed.paymentsCreated, + paymentsPaid: result.paymentsPaid + replayed.paymentsPaid, + unattributed: replayed.stillUnattributed, + }; } diff --git a/src/lib/money.ts b/src/lib/money.ts new file mode 100644 index 0000000..6772e46 --- /dev/null +++ b/src/lib/money.ts @@ -0,0 +1,130 @@ +/** + * Exact monetary conversion for CoreFlow. + * + * ── The bug this module exists to prevent ──────────────────────────────────── + * The dashboard used to collect dollars, multiply by 100 to get "cents", and + * pass that integer straight to the contract as the on-chain amount. Stellar + * assets carry SEVEN decimals, not two, so `$250.50` funded 25050 base units β€” + * 0.0025050 USDC β€” while the UI went on rendering "$250.50". A 100,000Γ— + * under-settlement, reported to the user as success. + * + * Two rules follow, and both are enforced here rather than left to call sites: + * + * 1. Money never touches a JS `number`. `parseFloat('0.1') * 100` is 10.000000 + * 000000002, and `Math.floor` of a value like that silently loses a unit. + * Amounts are parsed from their decimal STRING into `bigint` base units. + * 2. Base units are only meaningful alongside the asset's decimals. Every + * conversion takes them explicitly; there is no ambient default to get + * wrong. + * + * ── Decimals on Stellar ────────────────────────────────────────────────────── + * Every Stellar Asset Contract (a classic asset wrapped as a Soroban token β€” + * native XLM and issued USDC alike) uses 7 decimals. A non-classic Soroban + * token may declare anything, so `CoreFlowClient.getTokenDecimals()` reads the + * value from the contract; SAC_DECIMALS is the correct constant only for SACs. + */ + +/** Decimals for any Stellar Asset Contract (classic asset), including native XLM. */ +export const SAC_DECIMALS = 7; + +/** Thrown for input that cannot be converted exactly. */ +export class MoneyParseError extends Error {} + +const DECIMAL_RE = /^-?\d+(\.\d+)?$/; + +/** + * Parses a decimal string into integer base units. + * + * parseAmount('250.50', 7) === 2_505_000_000n + * parseAmount('0.0000001', 7) === 1n + * + * Rejects more fractional digits than the asset can represent instead of + * rounding: silently truncating a payroll amount is a financial error, not a + * formatting preference, and the caller must decide what to do about it. + */ +export function parseAmount(input: string, decimals: number): bigint { + const raw = input.trim().replace(/,/g, ''); + if (!DECIMAL_RE.test(raw)) { + throw new MoneyParseError( + `"${input}" is not a valid amount. Use digits and at most one decimal point.` + ); + } + + const negative = raw.startsWith('-'); + const [whole, fraction = ''] = (negative ? raw.slice(1) : raw).split('.'); + + if (fraction.length > decimals) { + throw new MoneyParseError( + `"${input}" has ${fraction.length} decimal places but this asset supports ${decimals}.` + ); + } + + const padded = fraction.padEnd(decimals, '0'); + const units = BigInt(whole + padded); + return negative ? -units : units; +} + +/** + * Renders base units as a decimal string. Exact β€” no rounding, no `toFixed`. + * + * formatAmount(2_505_000_000n, 7) === '250.50' + */ +export function formatAmount( + units: bigint, + decimals: number, + opts: { trimTrailingZeros?: boolean; minFractionDigits?: number } = {} +): string { + const { trimTrailingZeros = true, minFractionDigits = 2 } = opts; + + const negative = units < 0n; + const abs = negative ? -units : units; + const divisor = 10n ** BigInt(decimals); + + const whole = (abs / divisor).toString(); + let fraction = (abs % divisor).toString().padStart(decimals, '0'); + + if (trimTrailingZeros) { + fraction = fraction.replace(/0+$/, ''); + } + while (fraction.length < minFractionDigits) fraction += '0'; + + const body = fraction.length > 0 ? `${whole}.${fraction}` : whole; + return negative ? `-${body}` : body; +} + +/** Renders base units with thousands separators, e.g. `8,420.00`. */ +export function formatAmountWithSeparators( + units: bigint, + decimals: number, + opts?: Parameters[2] +): string { + const s = formatAmount(units, decimals, opts); + const negative = s.startsWith('-'); + const [whole, fraction] = (negative ? s.slice(1) : s).split('.'); + const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ','); + const body = fraction ? `${grouped}.${fraction}` : grouped; + return negative ? `-${body}` : body; +} + +/** + * Sum base units without overflow. `bigint` has no ceiling, which is the point: + * the previous schema stored money in a 32-bit column that overflowed at + * $21,474,836.47 β€” a hard payroll ceiling nobody would have discovered until a + * batch silently wrapped. + */ +export function sumAmounts(amounts: readonly bigint[]): bigint { + return amounts.reduce((a, b) => a + b, 0n); +} + +/** + * Whole hours implied by an amount at a given rate, or null when the amount is + * not a whole multiple. + * + * The contract enforces `hours Γ— rate_per_hour == amount` and refuses anything + * else, so a batch that fails this check would fund custody into an escrow that + * can never settle. Checking here turns that into a form error. + */ +export function hoursForAmount(amount: bigint, ratePerHour: bigint): bigint | null { + if (ratePerHour <= 0n) return null; + return amount % ratePerHour === 0n ? amount / ratePerHour : null; +} diff --git a/src/lib/oracle/__tests__/sign.test.ts b/src/lib/oracle/__tests__/sign.test.ts index 9fbd44d..5d267d2 100644 --- a/src/lib/oracle/__tests__/sign.test.ts +++ b/src/lib/oracle/__tests__/sign.test.ts @@ -1,43 +1,158 @@ // @vitest-environment node -import { describe, it, expect, beforeAll } from 'vitest'; -import { Keypair } from '@stellar/stellar-sdk'; -import { buildProofMessage, getOracleKeypair, getOraclePublicKeyHex, signHoursProof } from '../index'; +/** + * Oracle signing tests β€” CFWP schema v2. + * + * The vector below is the OTHER HALF of a cross-language pin. The identical + * constants are asserted by the Soroban contract suite in + * contracts/core-flow/src/test.rs (`test_proof_preimage_matches_cross_language_vector`), + * and the canonical copy lives in docs/evidence/proof-vector-v2.json. + * + * Two independent implementations pinned to one vector is what makes "the + * signer and the verifier agree" a tested claim rather than an assumption. A + * field added, reordered, resized or dropped on either side breaks one suite. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { + buildProofMessage, + signHoursProof, + verifyHoursProof, + getOraclePublicKeyHex, + resetOracleKeypairCache, + networkId, + PROOF_MESSAGE_BYTES, + type ProofContext, +} from '../index'; -// Deterministic 32-byte test seed. -const SEED = '0101010101010101010101010101010101010101010101010101010101010101'; +/** Deterministic 32-byte seed; the Rust suite derives the same keypair. */ +const SEED = '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20'; +const ORACLE_PUBKEY = '79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664'; -describe('oracle signing', () => { - beforeAll(() => { +const TESTNET = 'Test SDF Network ; September 2015'; +const MAINNET = 'Public Global Stellar Network ; September 2015'; + +const CTX: ProofContext = { + networkPassphrase: TESTNET, + contractId: 'CCQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2CNSG', + worker: 'GB43KVROR7TFJ6KAPCYRF2FJROTZAH4FHLTJLPWX4DRZCC5NASLGITR6', + token: 'CCZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLEB3K', + amount: 10000n, + startDate: 1000n, + endDate: 2000n, +}; + +/** escrow 1 / payment 0 / 40 hours / nonce 0 β€” the shared vector. */ +const VECTOR_MESSAGE_HEX = + '434657500002cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472' + + '5b0c63242683ea58b14aff3c6a455fa6dbf3573ddedc1e4fa218e0406711ba422cbbd006041e' + + 'ea71603dacf22e8af1a8cbf2f3b0083caa8b8bf333ab565ce2e0511957404a7b60b722a93985' + + '8e47fa9205c5f7c71de7941f15d2f489c8c53ceb000000010000000000000000000000000000' + + '0000000027100000000000000000000000000000002800000000000003e800000000000007d0' + + '0000000000000000'; +const VECTOR_SIGNATURE = + 'TdKHLe52uA6PSuj70Lkkkopd5gTgmzLic2ZT3HmOZ6e4U6DoKanImbyjUT40bA8uRxzWhbFo8Alfl2HYfKs+Cg=='; + +describe('oracle signing β€” CFWP v2', () => { + beforeEach(() => { process.env.ORACLE_SECRET_KEY = SEED; + resetOracleKeypairCache(); + }); + afterEach(() => { + delete process.env.ORACLE_SECRET_KEY; + resetOracleKeypairCache(); + }); + + it('derives the same public key the Rust suite does', () => { + expect(getOraclePublicKeyHex()).toBe(ORACLE_PUBKEY); }); - it('builds a 32-byte message with the contract byte layout', () => { - const msg = buildProofMessage(7, 2, 80n, 3n); - expect(msg.length).toBe(32); - expect(msg.readUInt32BE(0)).toBe(7); // escrow_id - expect(msg.readUInt32BE(4)).toBe(2); // payment_id - expect(msg.readBigUInt64BE(24)).toBe(3n); // nonce - // hours (i128) occupies bytes 8..24; low byte holds 80. - expect(msg[23]).toBe(80); + it('builds the exact preimage pinned by the cross-language vector', () => { + const msg = buildProofMessage(CTX, 1, 0, 40n, 0n); + expect(msg.length).toBe(PROOF_MESSAGE_BYTES); + expect(msg.toString('hex')).toBe(VECTOR_MESSAGE_HEX); }); - it('produces a signature the matching public key verifies', () => { - const sigB64 = signHoursProof(1, 0, 40, 0); - const sig = Buffer.from(sigB64, 'base64'); - expect(sig.length).toBe(64); + it('produces the exact signature pinned by the vector', () => { + expect(signHoursProof(CTX, 1, 0, 40, 0)).toBe(VECTOR_SIGNATURE); + }); + + it('derives network_id as sha256 of the passphrase, matching Soroban', () => { + // env.ledger().network_id() is sha256(passphrase); the preimage embeds it + // at offset 6. If these diverge, every signature is rejected on-chain. + expect(networkId(TESTNET).toString('hex')).toBe( + VECTOR_MESSAGE_HEX.slice(12, 76) + ); + }); + + it('verifies its own signature', () => { + const sig = signHoursProof(CTX, 1, 0, 40, 0); + expect(verifyHoursProof(CTX, 1, 0, 40, 0, sig)).toBe(true); + }); + + describe('domain separation β€” each field must change the signature', () => { + // Signing must happen inside each test: describe-scope bodies run at + // collection time, before beforeEach has set ORACLE_SECRET_KEY. + const sign = (ctx: ProofContext, e = 1, p = 0, h = 40, n = 0) => + signHoursProof(ctx, e, p, h, n); + + it('a Testnet proof is not valid on Mainnet', () => { + // This is the exact substitution v1 permitted: same escrow, same payment, + // same hours, same nonce, different chain. + const testnet = sign(CTX); + const mainnet = sign({ ...CTX, networkPassphrase: MAINNET }); + expect(mainnet).not.toBe(testnet); + expect(verifyHoursProof({ ...CTX, networkPassphrase: MAINNET }, 1, 0, 40, 0, testnet)).toBe(false); + }); + + it('a proof for one contract is not valid on another deployment', () => { + const other = { ...CTX, contractId: 'CCZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLFMVSWKZLEB3K' }; + expect(sign(other)).not.toBe(sign(CTX)); + expect(verifyHoursProof(other, 1, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof cannot be redirected to a different payee', () => { + const other = { ...CTX, worker: 'GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ' }; + expect(verifyHoursProof(other, 1, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof cannot be redirected to a different asset', () => { + const other = { ...CTX, token: 'CCQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2DINBUGQ2CNSG' }; + expect(verifyHoursProof(other, 1, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof is bound to the amount that will actually move', () => { + const other = { ...CTX, amount: 20000n }; + expect(verifyHoursProof(other, 1, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof is bound to its pay period', () => { + const other = { ...CTX, startDate: 5000n, endDate: 6000n }; + expect(verifyHoursProof(other, 1, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof for one escrow is not valid for another', () => { + expect(verifyHoursProof(CTX, 2, 0, 40, 0, sign(CTX))).toBe(false); + }); + + it('a proof for one payment row is not valid for another', () => { + expect(verifyHoursProof(CTX, 1, 1, 40, 0, sign(CTX))).toBe(false); + }); + + it('rejects a signature replayed at the next nonce', () => { + // The contract consumes nonces from a monotonic watermark; binding the + // nonce into the preimage is what makes a consumed proof unreusable. + expect(verifyHoursProof(CTX, 1, 0, 40, 1, sign(CTX))).toBe(false); + }); - const pubHex = getOraclePublicKeyHex(); - const kp = Keypair.fromPublicKey(getOracleKeypair().publicKey()); - const msg = buildProofMessage(1, 0, 40n, 0n); - expect(kp.verify(msg, sig)).toBe(true); - // Public key is the 32-byte raw Ed25519 key as hex. - expect(pubHex).toHaveLength(64); + it('rejects a signature for tampered hours', () => { + expect(verifyHoursProof(CTX, 1, 0, 41, 0, sign(CTX))).toBe(false); + }); }); - it('rejects a signature for a tampered nonce', () => { - const sig = Buffer.from(signHoursProof(1, 0, 40, 0), 'base64'); - const kp = Keypair.fromPublicKey(getOracleKeypair().publicKey()); - const wrongMsg = buildProofMessage(1, 0, 40n, 1n); // nonce changed - expect(kp.verify(wrongMsg, sig)).toBe(false); + it('refuses to build a short preimage', () => { + // A dropped field would otherwise sign cleanly and fail only on-chain, as + // an opaque signature rejection. + expect(() => + buildProofMessage({ ...CTX, contractId: '' }, 1, 0, 40n, 0n) + ).toThrow(); }); }); diff --git a/src/lib/oracle/index.ts b/src/lib/oracle/index.ts index 51b99fb..0e79868 100644 --- a/src/lib/oracle/index.ts +++ b/src/lib/oracle/index.ts @@ -1,20 +1,60 @@ /** * CoreFlow oracle signing service. * - * The oracle attests to verified work hours by producing an Ed25519 signature - * over the exact 32-byte message the on-chain contract reconstructs and checks: + * The oracle attests to verified work by producing an Ed25519 signature over + * the exact preimage the on-chain contract reconstructs and checks. * - * escrow_id (u32, 4 bytes BE) - * payment_id (u32, 4 bytes BE) - * hours (i128, 16 bytes BE, two's complement) - * nonce (u64, 8 bytes BE) + * ── Schema v2 (198 bytes) ──────────────────────────────────────────────────── * - * The signing key lives only on the server (ORACLE_SECRET_KEY) and never - * touches the client. The contract's per-escrow `oracle_pubkey` must equal this - * key's public half, which clients fetch from GET /api/oracle/pubkey. + * offset size field + * 0 4 magic "CFWP" (CoreFlow Work Proof) + * 4 2 version u16 BE (= 2) + * 6 32 network_id sha256(network passphrase) + * 38 32 contract sha256(ScVal XDR of the contract address) + * 70 32 worker sha256(ScVal XDR of the worker address) + * 102 32 token sha256(ScVal XDR of the settlement asset) + * 134 4 escrow_id u32 BE + * 138 4 payment_id u32 BE + * 142 16 amount i128 BE (two's complement) + * 158 16 hours i128 BE (two's complement) + * 174 8 start_date u64 BE + * 182 8 end_date u64 BE + * 190 8 nonce u64 BE + * + * WHY EACH FIELD EXISTS. v1 signed only `escrow_id β€– payment_id β€– hours β€– nonce`, + * which said nothing about which chain, which contract, which payee, or how much. + * One signature was therefore valid on every deployment of the contract on every + * network for the same tuple β€” a Testnet attestation replayed verbatim against + * Mainnet. Each field above closes one of those substitutions: + * + * network_id β†’ a Testnet signature is not a Mainnet signature + * contract β†’ a signature for one deployment is not valid on another + * worker β†’ an attestation cannot be redirected to a different payee + * token β†’ nor to a different asset + * amount β†’ the oracle attests to the sum that will actually move + * period β†’ an attestation is scoped to one pay period + * nonce β†’ single use, enforced by the contract's monotonic watermark + * version β†’ lets a future schema be distinguished rather than confused + * + * The signing key lives only on the server (ORACLE_SECRET_KEY) and never touches + * the client. The contract's per-escrow `oracle_pubkey` must equal this key's + * public half, and (once an admin is configured) must be on the contract's + * admin-managed oracle registry. + * + * Keep in sync with: + * contracts/core-flow/src/lib.rs (build_proof_message) + * scripts/oracle-cli.mjs (buildProofMessage) + * `PROOF_VECTOR_V2` in the tests pins all three to one shared vector; the + * contract also exposes `proof_preimage` so a signer can read the bytes rather + * than rebuild them. */ -import { Keypair } from '@stellar/stellar-sdk'; +import { createHash } from 'crypto'; +import { Keypair, nativeToScVal } from '@stellar/stellar-sdk'; + +export const PROOF_MAGIC = Buffer.from('CFWP', 'ascii'); +export const PROOF_VERSION = 2; +export const PROOF_MESSAGE_BYTES = 198; let cachedKeypair: Keypair | null = null; @@ -36,6 +76,17 @@ export function getOraclePublicKeyHex(): string { return getOracleKeypair().rawPublicKey().toString('hex'); } +/** Test seam β€” drops the memoised keypair so a changed env var takes effect. */ +export function resetOracleKeypairCache(): void { + cachedKeypair = null; +} + +function u16be(n: number): Buffer { + const b = Buffer.alloc(2); + b.writeUInt16BE(n); + return b; +} + function u32be(n: number): Buffer { const b = Buffer.alloc(4); b.writeUInt32BE(n >>> 0); @@ -59,23 +110,106 @@ function i128be(n: bigint): Buffer { return b; } -/** Builds the 32-byte message the contract verifies. */ +/** + * sha256 of a Stellar network passphrase β€” the same value Soroban exposes to a + * contract as `env.ledger().network_id()`. + */ +export function networkId(passphrase: string): Buffer { + return createHash('sha256').update(passphrase, 'utf8').digest(); +} + +/** + * sha256 of an address's ScVal XDR, matching the contract's + * `sha256(addr.to_xdr(env))`. + * + * Addresses serialize to a variable number of bytes (an account ScAddress and a + * contract ScAddress differ in length), so hashing to a fixed 32 keeps the + * preimage fixed-width. `soroban_sdk`'s `ToXdr` serializes the `Val`, i.e. the + * ScVal envelope β€” which is what `nativeToScVal(addr, { type: 'address' })` + * produces here, not the bare ScAddress. + */ +export function addressDigest(address: string): Buffer { + const xdr = nativeToScVal(address, { type: 'address' }).toXDR(); + return createHash('sha256').update(xdr).digest(); +} + +export interface ProofContext { + /** Stellar network passphrase, e.g. 'Test SDF Network ; September 2015'. */ + networkPassphrase: string; + /** The CoreFlow contract address (C…). */ + contractId: string; + /** Payee address (G… or C…), from the on-chain payment row. */ + worker: string; + /** Settlement asset SAC address, from the on-chain payment row. */ + token: string; + /** Escrowed amount in the asset's base units, from the on-chain payment row. */ + amount: bigint; + /** Pay period start (unix seconds), from the on-chain payment row. */ + startDate: bigint; + /** Pay period end (unix seconds), from the on-chain payment row. */ + endDate: bigint; +} + +/** Builds the 198-byte domain-separated message the contract verifies. */ export function buildProofMessage( + ctx: ProofContext, escrowId: number, paymentId: number, hours: bigint, nonce: bigint ): Buffer { - return Buffer.concat([u32be(escrowId), u32be(paymentId), i128be(hours), u64be(nonce)]); + const msg = Buffer.concat([ + PROOF_MAGIC, + u16be(PROOF_VERSION), + networkId(ctx.networkPassphrase), + addressDigest(ctx.contractId), + addressDigest(ctx.worker), + addressDigest(ctx.token), + u32be(escrowId), + u32be(paymentId), + i128be(ctx.amount), + i128be(hours), + u64be(ctx.startDate), + u64be(ctx.endDate), + u64be(nonce), + ]); + + // A short preimage would still sign and still verify against a matching + // short preimage, so a dropped field would fail only at the contract β€” as an + // opaque signature rejection. Fail here instead, where the cause is visible. + if (msg.length !== PROOF_MESSAGE_BYTES) { + throw new Error( + `Proof preimage must be ${PROOF_MESSAGE_BYTES} bytes, built ${msg.length}.` + ); + } + return msg; } -/** Signs a work-hours proof, returning the 64-byte Ed25519 signature as base64. */ +/** Signs a work proof, returning the 64-byte Ed25519 signature as base64. */ export function signHoursProof( + ctx: ProofContext, escrowId: number, paymentId: number, hours: number | bigint, nonce: number | bigint ): string { - const msg = buildProofMessage(escrowId, paymentId, BigInt(hours), BigInt(nonce)); + const msg = buildProofMessage(ctx, escrowId, paymentId, BigInt(hours), BigInt(nonce)); return getOracleKeypair().sign(msg).toString('base64'); } + +/** Verifies a signature locally against the same preimage. */ +export function verifyHoursProof( + ctx: ProofContext, + escrowId: number, + paymentId: number, + hours: number | bigint, + nonce: number | bigint, + signatureBase64: string +): boolean { + const msg = buildProofMessage(ctx, escrowId, paymentId, BigInt(hours), BigInt(nonce)); + try { + return getOracleKeypair().verify(msg, Buffer.from(signatureBase64, 'base64')); + } catch { + return false; + } +} diff --git a/src/lib/payments/__tests__/actions.test.ts b/src/lib/payments/__tests__/actions.test.ts new file mode 100644 index 0000000..e5ed58c --- /dev/null +++ b/src/lib/payments/__tests__/actions.test.ts @@ -0,0 +1,439 @@ +// @vitest-environment node +/** + * Payment action and tenant-isolation tests. + * + * Two properties dominate here: + * 1. A user from Organization A cannot read or mutate Organization B's + * payments by changing an id β€” and cannot even learn that the id exists. + * 2. A retried financial mutation does not produce a second payment. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { PaymentState, OrgRole, ApprovalDecision, TxStatus } from '@prisma/client'; +import { createFakeDb, seedOrg, seedMember, type FakeDb } from './fake-db'; +import { resolveMembership, findPaymentForMember, canRead } from '../authz'; +import { + approvePayment, rejectPayment, cancelPayment, + submitPaymentForSettlement, retryPayment, flagForReconciliation, +} from '../actions'; +import { transitionPayment, rollupBatch } from '../service'; + +const ORG_A = 'org_a'; +const ORG_B = 'org_b'; + +let db: FakeDb; + +/** Seed two tenants, each with their own payment and a full role set. */ +function seedTwoTenants() { + for (const [org, slug] of [[ORG_A, 'a'], [ORG_B, 'b']] as const) { + db.__tables.organization.rows.push({ id: org, name: org, slug }); + } + seedMember(db, ORG_A, 'u_a_owner', OrgRole.OWNER, 'G' + 'A'.repeat(55)); + seedMember(db, ORG_A, 'u_a_mgr', OrgRole.MANAGER, 'G' + 'M'.repeat(55)); + seedMember(db, ORG_A, 'u_a_fin', OrgRole.FINANCE, 'G' + 'F'.repeat(55)); + seedMember(db, ORG_A, 'u_a_view', OrgRole.VIEWER, 'G' + 'V'.repeat(55)); + seedMember(db, ORG_A, 'u_a_work', OrgRole.WORKER, 'G' + 'W'.repeat(55)); + seedMember(db, ORG_B, 'u_b_owner', OrgRole.OWNER, 'G' + 'B'.repeat(55)); + + db.__tables.payrollBatch.rows.push( + { id: 'bat_a', orgId: ORG_A, reference: 'CF-00001' }, + { id: 'bat_b', orgId: ORG_B, reference: 'CF-00002' } + ); + const base = { + recipientAddress: 'G' + 'R'.repeat(55), + onChainPaymentIndex: 0, + assetCode: 'USDC', assetDecimals: 7, + amountBaseUnits: 10_000_000_000n, rateBaseUnits: 250_000_000n, hours: 40n, + stateUpdatedAt: new Date(), createdAt: new Date(), + }; + db.__tables.payment.rows.push( + { id: 'pay_a', orgId: ORG_A, batchId: 'bat_a', escrowId: 'esc_a', state: PaymentState.READY_TO_SETTLE, ...base }, + { id: 'pay_b', orgId: ORG_B, batchId: 'bat_b', escrowId: 'esc_b', state: PaymentState.READY_TO_SETTLE, ...base } + ); +} + +beforeEach(() => { + db = createFakeDb(); + seedTwoTenants(); +}); + +const member = async (userId: string, orgId: string) => { + const r = await resolveMembership(db, userId, orgId); + if (!r.ok) throw new Error(`expected membership: ${r.message}`); + return r.value; +}; + +describe('tenant isolation', () => { + it('reports a foreign organization as not found, not forbidden', async () => { + // A 403 would confirm the organization exists, letting anyone enumerate + // tenants by id. + const r = await resolveMembership(db, 'u_a_owner', ORG_B); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + }); + + it('refuses to load another tenant’s payment by id', async () => { + const m = await member('u_a_owner', ORG_A); + const r = await findPaymentForMember(db, m, 'pay_b'); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + }); + + it('loads the caller’s own payment', async () => { + const m = await member('u_a_owner', ORG_A); + const r = await findPaymentForMember(db, m, 'pay_a'); + expect(r.ok).toBe(true); + }); + + it.each([ + ['approve', approvePayment], + ['reject', rejectPayment], + ['cancel', cancelPayment], + ['retry', retryPayment], + ['reconcile', flagForReconciliation], + ['submit', submitPaymentForSettlement], + ] as const)('refuses cross-tenant %s', async (_name, action) => { + const m = await member('u_a_owner', ORG_A); + const r = await action({ + db, membership: m, paymentId: 'pay_b', + reason: 'cross-tenant attempt', idempotencyKey: 'key-x', + }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + + // And the foreign payment is untouched. + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_b')!.state) + .toBe(PaymentState.READY_TO_SETTLE); + }); + + it('refuses a transition scoped to the wrong organization', async () => { + const r = await transitionPayment(db, { + paymentId: 'pay_b', to: PaymentState.CANCELLED, + actor: { kind: 'user', role: OrgRole.OWNER, address: 'GA' }, + orgId: ORG_A, // caller's org, payment belongs to B + }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + }); + + it('writes no audit event for a refused cross-tenant action', async () => { + const m = await member('u_a_owner', ORG_A); + await cancelPayment({ db, membership: m, paymentId: 'pay_b' }); + expect(db.__tables.auditEvent.rows).toHaveLength(0); + }); + + it('excludes WORKER from payment reads', () => { + expect(canRead(OrgRole.WORKER)).toBe(false); + expect(canRead(OrgRole.VIEWER)).toBe(true); + }); +}); + +describe('approval and separation of duties', () => { + it('derives the approval role from membership, never from the request', async () => { + const mgr = await member('u_a_mgr', ORG_A); + const r = await approvePayment({ db, membership: mgr, paymentId: 'pay_a' }); + expect(r.ok).toBe(true); + const approval = db.__tables.approval.rows[0]; + expect(approval.role).toBe(OrgRole.MANAGER); + expect(approval.decision).toBe(ApprovalDecision.APPROVED); + }); + + it('does not advance the payment on an off-chain approval', async () => { + // The authoritative approval is the on-chain signature. Recording a decision + // here must not move the payment, or the dashboard would show an approval the + // chain never received. + const mgr = await member('u_a_mgr', ORG_A); + await approvePayment({ db, membership: mgr, paymentId: 'pay_a' }); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state) + .toBe(PaymentState.READY_TO_SETTLE); + }); + + it('treats a repeat approval as a duplicate, not a second fact', async () => { + const mgr = await member('u_a_mgr', ORG_A); + await approvePayment({ db, membership: mgr, paymentId: 'pay_a' }); + const second = await approvePayment({ db, membership: mgr, paymentId: 'pay_a' }); + + expect(second.ok).toBe(true); + if (second.ok) expect(second.body.changed).toBe(false); + expect(db.__tables.approval.rows).toHaveLength(1); + }); + + it('records manager and finance as separate approvals', async () => { + await approvePayment({ db, membership: await member('u_a_mgr', ORG_A), paymentId: 'pay_a' }); + await approvePayment({ db, membership: await member('u_a_fin', ORG_A), paymentId: 'pay_a' }); + + const roles = db.__tables.approval.rows.map((a) => a.role).sort(); + expect(roles).toEqual([OrgRole.FINANCE, OrgRole.MANAGER].sort()); + }); + + it('refuses one wallet supplying both halves of the gate', async () => { + // An OWNER acts for whichever approval is outstanding. Having recorded one, + // the same wallet must not be able to record the other. + const owner = await member('u_a_owner', ORG_A); + const first = await approvePayment({ db, membership: owner, paymentId: 'pay_a' }); + expect(first.ok).toBe(true); + + // Move the payment so the owner would now be asked for the finance half. + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.AWAITING_FINANCE; + + const second = await approvePayment({ db, membership: owner, paymentId: 'pay_a' }); + expect(second.ok).toBe(false); + if (!second.ok) expect(second.status).toBe(409); + expect(db.__tables.approval.rows).toHaveLength(1); + }); + + it.each([OrgRole.VIEWER, OrgRole.WORKER])('refuses %s approving', async (role) => { + seedMember(db, ORG_A, `u_${role}`, role, `G${role}`.padEnd(56, 'X')); + const m = await member(`u_${role}`, ORG_A); + const r = await approvePayment({ db, membership: m, paymentId: 'pay_a' }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(403); + }); +}); + +describe('rejection', () => { + it('requires a reason', async () => { + // An unexplained rejection is unauditable: nobody downstream can tell a data + // error from a dispute. + const fin = await member('u_a_fin', ORG_A); + const r = await rejectPayment({ db, membership: fin, paymentId: 'pay_a' }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(400); + }); + + it('refuses a MANAGER rejecting at the finance stage', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.AWAITING_FINANCE; + const mgr = await member('u_a_mgr', ORG_A); + const r = await rejectPayment({ + db, membership: mgr, paymentId: 'pay_a', reason: 'Not my decision to make', + }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(403); + }); + + it('lets FINANCE reject at the finance stage and records the decision', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.AWAITING_FINANCE; + const fin = await member('u_a_fin', ORG_A); + const r = await rejectPayment({ db, membership: fin, paymentId: 'pay_a', reason: 'Duplicate invoice' }); + + expect(r.ok).toBe(true); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state).toBe(PaymentState.REJECTED); + expect(db.__tables.approval.rows[0].decision).toBe(ApprovalDecision.REJECTED); + expect(db.__tables.approval.rows[0].reason).toBe('Duplicate invoice'); + }); +}); + +describe('settlement submission idempotency', () => { + it('requires an idempotency key', async () => { + const mgr = await member('u_a_mgr', ORG_A); + const r = await submitPaymentForSettlement({ db, membership: mgr, paymentId: 'pay_a' }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(400); + }); + + it('records one attempt and moves the payment to SUBMITTING', async () => { + const mgr = await member('u_a_mgr', ORG_A); + const r = await submitPaymentForSettlement({ + db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'idem-1', + }); + expect(r.ok).toBe(true); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state).toBe(PaymentState.SUBMITTING); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + expect(db.__tables.blockchainTransaction.rows[0].attempt).toBe(1); + }); + + it('replays the original attempt for a repeated key β€” the double-pay guard', async () => { + // This is the property that matters most in payroll: a client that times out + // and retries must not cause a second payment. + const mgr = await member('u_a_mgr', ORG_A); + const first = await submitPaymentForSettlement({ + db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'idem-2', + }); + const second = await submitPaymentForSettlement({ + db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'idem-2', + }); + + expect(first.ok && second.ok).toBe(true); + if (second.ok) expect(second.body.changed).toBe(false); + expect(db.__tables.blockchainTransaction.rows).toHaveLength(1); + }); + + it('refuses a key already used for a different payment', async () => { + const mgrA = await member('u_a_mgr', ORG_A); + db.__tables.payment.rows.push({ + id: 'pay_a2', orgId: ORG_A, batchId: 'bat_a', escrowId: 'esc_a', + state: PaymentState.READY_TO_SETTLE, onChainPaymentIndex: 1, + recipientAddress: 'GX', assetDecimals: 7, assetCode: 'USDC', + amountBaseUnits: 1n, rateBaseUnits: 1n, hours: 1n, + stateUpdatedAt: new Date(), createdAt: new Date(), + }); + + await submitPaymentForSettlement({ db, membership: mgrA, paymentId: 'pay_a', idempotencyKey: 'shared' }); + const r = await submitPaymentForSettlement({ db, membership: mgrA, paymentId: 'pay_a2', idempotencyKey: 'shared' }); + + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(409); + }); + + it('does not reach PAID', async () => { + const mgr = await member('u_a_mgr', ORG_A); + await submitPaymentForSettlement({ db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'idem-3' }); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state).not.toBe(PaymentState.PAID); + }); + + it('refuses submission from a state that is not ready', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.AWAITING_ORACLE; + const mgr = await member('u_a_mgr', ORG_A); + const r = await submitPaymentForSettlement({ + db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'idem-4', + }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(409); + }); + + it('numbers a genuine second attempt after a failed submission', async () => { + const mgr = await member('u_a_mgr', ORG_A); + await submitPaymentForSettlement({ db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'a1' }); + + // Submission failed; operator retries, then submits again with a NEW key. + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.SUBMISSION_FAILED; + await retryPayment({ db, membership: mgr, paymentId: 'pay_a' }); + await submitPaymentForSettlement({ db, membership: mgr, paymentId: 'pay_a', idempotencyKey: 'a2' }); + + const attempts = db.__tables.blockchainTransaction.rows.map((t) => t.attempt).sort(); + expect(attempts).toEqual([1, 2]); + }); +}); + +describe('retry', () => { + it('allows retry after a submission that never reached the chain', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.SUBMISSION_FAILED; + const mgr = await member('u_a_mgr', ORG_A); + const r = await retryPayment({ db, membership: mgr, paymentId: 'pay_a' }); + + expect(r.ok).toBe(true); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state) + .toBe(PaymentState.READY_TO_SETTLE); + }); + + it('refuses retry after a settlement that DID reach the chain', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.SETTLEMENT_FAILED; + const owner = await member('u_a_owner', ORG_A); + const r = await retryPayment({ db, membership: owner, paymentId: 'pay_a' }); + + expect(r.ok).toBe(false); + if (!r.ok) { + expect(r.status).toBe(409); + expect(r.code).toBe('RECONCILIATION_FIRST'); + } + }); +}); + +describe('reconciliation flag', () => { + it('is restricted to owners and admins', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.CONFIRMING; + const mgr = await member('u_a_mgr', ORG_A); + const r = await flagForReconciliation({ db, membership: mgr, paymentId: 'pay_a' }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(403); + }); + + it('lets an owner flag a confirming payment', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.CONFIRMING; + const owner = await member('u_a_owner', ORG_A); + const r = await flagForReconciliation({ db, membership: owner, paymentId: 'pay_a', reason: 'stuck' }); + + expect(r.ok).toBe(true); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state) + .toBe(PaymentState.RECONCILIATION_REQUIRED); + }); +}); + +describe('cancellation authority', () => { + it('refuses a user cancelling a payment whose approvals are already on-chain', async () => { + // Escrowed funds are released or refunded by the CONTRACT. Letting an + // operator mark an approved payment cancelled off-chain would desync the + // product from custody that is still held on-chain. + const owner = await member('u_a_owner', ORG_A); + const r = await cancelPayment({ db, membership: owner, paymentId: 'pay_a', reason: 'changed mind' }); + + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(403); + expect(db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state) + .toBe(PaymentState.READY_TO_SETTLE); + }); + + it('lets a user cancel before anything is funded', async () => { + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.DRAFT; + const mgr = await member('u_a_mgr', ORG_A); + const r = await cancelPayment({ db, membership: mgr, paymentId: 'pay_a' }); + expect(r.ok).toBe(true); + }); +}); + +describe('concurrency', () => { + it('lets only one of two racing transitions win', async () => { + // Both read READY_TO_SETTLE, both try to move. Without a compare-and-swap + // both would write, losing one transition and its audit entry. + const actor = { kind: 'user' as const, role: OrgRole.MANAGER, address: 'GM' }; + const [a, b] = await Promise.all([ + transitionPayment(db, { paymentId: 'pay_a', to: PaymentState.SUBMITTING, actor, orgId: ORG_A }), + transitionPayment(db, { paymentId: 'pay_a', to: PaymentState.CANCELLED, actor, orgId: ORG_A }), + ]); + + const winners = [a, b].filter((r) => r.ok && r.changed); + expect(winners).toHaveLength(1); + + // Exactly one state-change audit row, matching the winner. + const transitions = db.__tables.auditEvent.rows.filter((e) => e.type === 'payment.state.changed'); + expect(transitions).toHaveLength(1); + }); +}); + +describe('audit trail', () => { + it('records actor, previous and new state for a user transition', async () => { + // Cancelled from DRAFT: once approvals exist on-chain, cancellation is a + // chain action (cancel_escrow) and the table makes it indexer-only. + db.__tables.payment.rows.find((p) => p.id === 'pay_a')!.state = PaymentState.DRAFT; + const mgr = await member('u_a_mgr', ORG_A); + await cancelPayment({ db, membership: mgr, paymentId: 'pay_a', reason: 'Wrong recipient' }); + + const e = db.__tables.auditEvent.rows.find((x) => x.type === 'payment.state.changed'); + expect(e.previousState).toBe(PaymentState.DRAFT); + expect(e.newState).toBe(PaymentState.CANCELLED); + expect(e.actorAddress).toBe(mgr.walletAddress); + expect(e.actorSystem).toBeNull(); + expect(e.orgId).toBe(ORG_A); + expect(e.metadata.operatorReason).toBe('Wrong recipient'); + }); +}); + +describe('batch rollup', () => { + const p = (state: PaymentState, amount = 1_000_000_000n) => ({ state, amountBaseUnits: amount }); + + it('is derived, never stored', () => { + const r = rollupBatch([p(PaymentState.PAID), p(PaymentState.PAID), p(PaymentState.AWAITING_FINANCE)]); + expect(r.total).toBe(3); + expect(r.paidAmountBaseUnits).toBe(2_000_000_000n); + expect(r.totalAmountBaseUnits).toBe(3_000_000_000n); + }); + + it('reports a batch containing a failure as failed, not as mostly-fine', () => { + // Summarising by the most common state would let one broken payment hide + // inside a healthy batch β€” the one a finance team most needs to see. + const r = rollupBatch([ + p(PaymentState.PAID), p(PaymentState.PAID), p(PaymentState.PAID), + p(PaymentState.SETTLEMENT_FAILED), + ]); + expect(r.headline).toBe('Settlement failed'); + expect(r.needsAttention).toBe(1); + }); + + it('reports the least-advanced state when nothing is broken', () => { + const r = rollupBatch([p(PaymentState.PAID), p(PaymentState.AWAITING_MANAGER)]); + expect(r.headline).toBe('Awaiting manager approval'); + }); + + it('handles an empty batch', () => { + expect(rollupBatch([]).headline).toBe('Empty'); + }); +}); diff --git a/src/lib/payments/__tests__/fake-db.ts b/src/lib/payments/__tests__/fake-db.ts new file mode 100644 index 0000000..422f456 --- /dev/null +++ b/src/lib/payments/__tests__/fake-db.ts @@ -0,0 +1,568 @@ +/** + * In-memory stand-in for the Prisma client, covering the subset the payment + * domain uses. + * + * Why a fake rather than mocks: the properties under test are about CONSTRAINTS + * and SEQUENCE β€” that replaying an event cannot create a second payment, that a + * compare-and-swap loses a race safely. `vi.fn()` returning canned values proves + * none of that. This fake enforces the unique constraints that carry the + * idempotency guarantees, so a test can actually observe them being relied upon. + * + * It is deliberately small and explicit. Anything it does not implement throws, + * so a call site that starts depending on new behaviour fails loudly here instead + * of passing against a silently permissive double. + * + * ── What this fake does NOT enforce ───────────────────────────────────────── + * + * Verified against real PostgreSQL by the integration suite + * (`*.integration.test.ts`). A passing unit test says nothing about any of these: + * + * - COMPOSITE FOREIGN KEYS. No referential integrity is checked at all, so a + * cross-tenant row that PostgreSQL rejects with P2003 is accepted here. + * Unit tests therefore prove that queries are SCOPED correctly; they cannot + * prove the database would refuse an unscoped write. + * Covered by: constraints.integration.test.ts "B. Composite foreign keys". + * - CASCADES and ON DELETE actions. Deleting a row deletes nothing else. + * - COLUMN TYPES. A bigint column will happily hold a JS number here. + * - OVERFLOW. int8 bounds are not enforced. + * - PARTIAL INDEX PREDICATES. The `WHERE status = 'RUNNING'` run lock is + * modelled as an ordinary unique constraint. + * - TRANSACTION ISOLATION. Writes are immediately visible to every reader; + * there is no READ COMMITTED snapshot, and no row locking. + * - PLANNER BEHAVIOUR, obviously. + * + * Where it is deliberately STRICTER in form but not in substance: a create missing + * a required column throws a clear `fake-db: ... missing required argument` error, + * whereas Prisma raises `PrismaClientValidationError: Argument \`org\` is missing` + * β€” naming the RELATION, not the column. Same rejection, different words. The + * fake is never made stricter than PostgreSQL in what it ACCEPTS, because a test + * that passes here and fails in production is the failure mode this file exists + * to prevent. + */ + +interface Row { + [k: string]: any; +} + +class Table { + rows: Row[] = []; + private seq = 0; + + constructor( + readonly name: string, + /** Unique constraints, each a list of column names. */ + readonly uniques: readonly (readonly string[])[] = [], + /** + * Columns Prisma requires on create (no default, not nullable). + * + * Without this the fake accepted a row missing a required scalar and the + * test passed, while the same call failed against real Postgres. That is + * exactly how `approval.create` shipped without its `orgId` β€” the tenant + * half of a composite foreign key β€” and went unnoticed. + */ + readonly required: readonly string[] = [] + ) {} + + /** Throws a Prisma-shaped missing-argument error, as the real client does. */ + assertRequired(row: Row): void { + for (const col of this.required) { + if (row[col] === undefined || row[col] === null) { + throw new Error( + `fake-db: ${this.name}.create is missing required argument \`${col}\`. ` + + `Prisma would reject this against a real database.` + ); + } + } + } + + nextId(prefix: string): string { + this.seq += 1; + return `${prefix}_${this.seq}`; + } + + private matches(row: Row, where: Row): boolean { + for (const [k, v] of Object.entries(where)) { + if (v === undefined) continue; + // An unset column and an explicit NULL are the same thing in SQL, so a + // `where: { resolvedAt: null }` must match a row that never set it. + // Without this the fake misses existing rows and every dedup check fails. + const actual = row[k] === undefined ? null : row[k]; + if (v !== null && typeof v === 'object' && !Array.isArray(v)) { + if ('not' in v) { + if (v.not === null ? actual === null : actual === v.not) return false; + continue; + } + if ('in' in v) { + if (!v.in.includes(actual)) return false; + continue; + } + if ('lt' in v) { + if (!(actual < v.lt)) return false; + continue; + } + if ('lte' in v) { + if (!(actual <= v.lte)) return false; + continue; + } + if ('gt' in v) { + if (!(actual > v.gt)) return false; + continue; + } + if ('gte' in v) { + if (!(actual >= v.gte)) return false; + continue; + } + if ('some' in v) { + // Relation filter: handled by callers that need it. + continue; + } + throw new Error(`fake-db: unsupported filter on ${this.name}.${k}: ${JSON.stringify(v)}`); + } + if (actual !== v) return false; + } + return true; + } + + find(where: Row): Row | undefined { + return this.rows.find((r) => this.matches(r, where)); + } + + findMany(where: Row = {}): Row[] { + return this.rows.filter((r) => this.matches(r, where)); + } + + /** Throws a Prisma-shaped P2002 so callers can exercise their duplicate paths. */ + assertUnique(row: Row): void { + for (const cols of this.uniques) { + if (cols.some((c) => row[c] === null || row[c] === undefined)) continue; + const clash = this.rows.find((r) => cols.every((c) => r[c] === row[c])); + if (clash) { + const err: any = new Error( + `Unique constraint failed on ${this.name}(${cols.join(',')})` + ); + err.code = 'P2002'; + err.meta = { target: cols }; + throw err; + } + } + } +} + +/** + * Columns declared `@default(now())` in the schema. + * + * The fake must populate these, because Prisma/PostgreSQL would. Leaving them + * undefined silently breaks any comparison against them β€” a `heartbeatAt` of + * undefined made every freshly-started reconciliation run look abandoned, which + * looked like a locking bug rather than a test-double gap. + */ +const NOW_DEFAULT_COLUMNS = [ + 'createdAt', 'updatedAt', 'startedAt', 'heartbeatAt', + 'detectedAt', 'lastObservedAt', 'processedAt', 'stateUpdatedAt', +]; + +function applyNowDefaults(data: Row): Row { + const out = { ...data }; + for (const col of NOW_DEFAULT_COLUMNS) { + if (out[col] === undefined) out[col] = new Date(); + } + return out; +} + +/** + * Apply an update payload, honouring Prisma's atomic `{ increment: n }` form. + * Assigning it verbatim would store the operator object as the column value. + */ +function applyData(row: Row, data: Row): void { + for (const [k, v] of Object.entries(data ?? {})) { + if (v && typeof v === 'object' && !Array.isArray(v) && 'increment' in v) { + row[k] = (row[k] ?? 0) + (v as any).increment; + } else if (v && typeof v === 'object' && !Array.isArray(v) && 'decrement' in v) { + row[k] = (row[k] ?? 0) - (v as any).decrement; + } else { + row[k] = v; + } + } +} + +/** Resolve a Prisma compound-unique `where` into a flat filter. */ +function flattenWhere(where: Row): Row { + const out: Row = {}; + for (const [k, v] of Object.entries(where ?? {})) { + if (v !== null && typeof v === 'object' && !Array.isArray(v) && k.includes('_')) { + Object.assign(out, v); // e.g. { orgId_userId: { orgId, userId } } + } else { + out[k] = v; + } + } + return out; +} + +export interface FakeDb { + [model: string]: any; + $transaction: (fn: (tx: any) => Promise) => Promise; + __tables: Record; + /** Forces the next matching write to throw, to simulate a mid-batch crash. */ + __failOn: (model: string, op: string, times?: number) => void; +} + +export function createFakeDb(): FakeDb { + const tables: Record = { + organization: new Table('organization', [['slug']]), + orgMember: new Table('orgMember', [['orgId', 'userId']], ['orgId', 'userId']), + invitation: new Table('invitation', [['tokenHash'], ['orgId', 'email']], ['orgId']), + user: new Table('user', [['walletAddress']]), + worker: new Table('worker', [['orgId', 'walletAddress']], ['orgId']), + project: new Table('project', [['orgId', 'code']], ['orgId']), + escrow: new Table('escrow', [['onChainId']], ['orgId']), + payrollBatch: new Table( + 'payrollBatch', + [['orgId', 'reference'], ['orgId', 'idempotencyKey']], + ['orgId', 'reference'] + ), + payment: new Table('payment', [['escrowId', 'onChainPaymentIndex']], ['orgId', 'batchId']), + approval: new Table('approval', [['paymentId', 'role']], ['orgId', 'paymentId']), + oracleAttestation: new Table('oracleAttestation', [ + ['escrowOnChainId', 'onChainPaymentIndex', 'nonce'], + ]), + blockchainTransaction: new Table( + 'blockchainTransaction', + [['idempotencyKey'], ['hash']], + ['orgId'] + ), + auditEvent: new Table('auditEvent', [], ['orgId']), + reconciliationFinding: new Table('reconciliationFinding', [], ['orgId']), + reconciliationRun: new Table('reconciliationRun', [['correlationId']], ['orgId']), + chainEvent: new Table('chainEvent', [['id']]), + indexerCursor: new Table('indexerCursor', [['contractId', 'network']]), + }; + + const failures: { model: string; op: string; times: number }[] = []; + + function maybeFail(model: string, op: string) { + const f = failures.find((x) => x.model === model && x.op === op && x.times > 0); + if (f) { + f.times -= 1; + throw new Error(`fake-db: injected failure on ${model}.${op}`); + } + } + + const prefixes: Record = { + organization: 'org', orgMember: 'ogm', user: 'usr', worker: 'wrk', + escrow: 'esc', payrollBatch: 'bat', payment: 'pay', approval: 'apr', + oracleAttestation: 'att', blockchainTransaction: 'btx', auditEvent: 'aud', + invitation: 'inv', reconciliationRun: 'run', + reconciliationFinding: 'fnd', chainEvent: 'cev', indexerCursor: 'cur', + project: 'prj', + }; + + /** + * Relations the code under test includes, as (parent model) -> (relation name) + * -> how to resolve it. Declared explicitly rather than inferred: an include + * this fake does not know about should fail loudly, not return undefined and + * surface as a confusing TypeError deep in the code under test. + */ + const RELATIONS: Record> = { + escrow: { payments: { table: 'payment', fk: 'escrowId', many: true, orderBy: 'onChainPaymentIndex' } }, + orgMember: { + org: { table: 'organization', fk: 'id', many: false, belongsTo: true }, + user: { table: 'user', fk: 'id', many: false, belongsTo: true }, + }, + payrollBatch: { payments: { table: 'payment', fk: 'batchId', many: true } }, + payment: { + approvals: { table: 'approval', fk: 'paymentId', many: true }, + attestations: { table: 'oracleAttestation', fk: 'paymentId', many: true }, + transactions: { table: 'blockchainTransaction', fk: 'paymentId', many: true }, + auditEvents: { table: 'auditEvent', fk: 'paymentId', many: true }, + findings: { table: 'reconciliationFinding', fk: 'paymentId', many: true }, + }, + }; + + /** Order rows per a Prisma `orderBy`. Shared so findFirst and findMany agree. */ + function sortRows(rows: Row[], orderBy: any): Row[] { + if (!orderBy) return rows; + const spec = Array.isArray(orderBy) ? orderBy[0] : orderBy; + const [key, dir] = Object.entries(spec)[0] as [string, string]; + return [...rows].sort((a, b) => + a[key] === b[key] ? 0 : (a[key] < b[key] ? -1 : 1) * (dir === 'desc' ? -1 : 1) + ); + } + + function hydrate(name: string, row: Row, include?: Row): Row { + if (!include) return { ...row }; + const out: Row = { ...row }; + for (const [rel, spec] of Object.entries(include)) { + if (spec === false || spec === undefined) continue; + const def = RELATIONS[name]?.[rel]; + if (!def) throw new Error(`fake-db: unsupported include ${name}.${rel}`); + if (def.belongsTo) { + // Parent side: the FK lives on THIS row, pointing at the parent's id. + const parentId = row[`${rel}Id`]; + const parent = tables[def.table].rows.find((r) => r[def.fk] === parentId); + out[rel] = parent ? { ...parent } : null; + continue; + } + let rows = tables[def.table].rows.filter((r) => r[def.fk] === row.id); + + // A relation can be included as `true` or as a spec carrying its own + // orderBy / include. Honouring the spec matters: ignoring a nested include + // returns rows whose relations are undefined, and the code under test then + // silently takes its `?? []` fallback β€” so a test asserting on nested data + // would pass without ever exercising it. + const spec_ = spec as any; + const nestedOrderBy = spec_ && typeof spec_ === 'object' ? spec_.orderBy : undefined; + if (nestedOrderBy) { + rows = sortRows(rows, nestedOrderBy); + } else if (def.orderBy) { + rows = [...rows].sort((a, b) => + a[def.orderBy!] === b[def.orderBy!] ? 0 : a[def.orderBy!] < b[def.orderBy!] ? -1 : 1 + ); + } + + const nestedInclude = spec_ && typeof spec_ === 'object' ? spec_.include : undefined; + out[rel] = rows.map((r) => hydrate(def.table, r, nestedInclude)); + } + return out; + } + + +function model(name: string) { + const t = tables[name]; + if (!t) throw new Error(`fake-db: unknown model ${name}`); + return { + // Reads return COPIES, as Prisma does. Returning the live row would let a + // caller's captured snapshot mutate underneath it β€” which silently breaks + // any code that compares previous state against new, and would make this + // fake disagree with production in exactly the place that matters. + findUnique: async ({ where, include }: any) => { + const row = t.find(flattenWhere(where)); + return row ? hydrate(name, row, include) : null; + }, + findFirst: async ({ where, orderBy, include }: any = {}) => { + const rows = sortRows(t.findMany(flattenWhere(where ?? {})), orderBy); + return rows[0] ? hydrate(name, rows[0], include) : null; + }, + findMany: async ({ where, take, orderBy, include, distinct, cursor, skip }: any = {}) => { + let rows = sortRows(t.findMany(flattenWhere(where ?? {})), orderBy); + if (cursor) { + const [[key, value]] = Object.entries(cursor) as [string, any][]; + const at = rows.findIndex((r) => r[key] === value); + if (at < 0) { + const err: any = new Error(`${name}: cursor row not found`); + err.code = 'P2025'; + throw err; + } + rows = rows.slice(at); + } + if (typeof skip === 'number') rows = rows.slice(skip); + if (distinct) { + const keys = Array.isArray(distinct) ? distinct : [distinct]; + const seen = new Set(); + rows = rows.filter((r) => { + const k = keys.map((c: string) => String(r[c])).join('|'); + if (seen.has(k)) return false; + seen.add(k); + return true; + }); + } + const out = typeof take === 'number' ? rows.slice(0, take) : rows; + return out.map((r) => hydrate(name, r, include)); + }, + count: async ({ where }: any = {}) => t.findMany(flattenWhere(where ?? {})).length, + create: async ({ data }: any) => { + maybeFail(name, 'create'); + const row = { + id: data.id ?? t.nextId(prefixes[name] ?? name), + ...applyNowDefaults(data), + }; + t.assertRequired(row); + t.assertUnique(row); + t.rows.push(row); + return row; + }, + update: async ({ where, data }: any) => { + maybeFail(name, 'update'); + const row = t.find(flattenWhere(where)); + if (!row) { + const err: any = new Error(`${name} not found`); + err.code = 'P2025'; + throw err; + } + applyData(row, data); + return row; + }, + updateMany: async ({ where, data }: any) => { + maybeFail(name, 'updateMany'); + const rows = t.findMany(flattenWhere(where ?? {})); + for (const r of rows) applyData(r, data); + return { count: rows.length }; + }, + upsert: async ({ where, create, update }: any) => { + const row = t.find(flattenWhere(where)); + if (row) { + Object.assign(row, update); + return row; + } + const created = { id: create.id ?? t.nextId(prefixes[name] ?? name), ...create }; + t.assertRequired(created); + t.assertUnique(created); + t.rows.push(created); + return created; + }, + deleteMany: async ({ where }: any = {}) => { + const rows = t.findMany(flattenWhere(where ?? {})); + t.rows = t.rows.filter((r) => !rows.includes(r)); + return { count: rows.length }; + }, + }; + } + + const db: any = { __tables: tables }; + for (const name of Object.keys(tables)) db[name] = model(name); + + /** + * Transactions roll back on throw by snapshotting and restoring. Crude, but it + * reproduces the property the indexer depends on: a failed event leaves NO + * partial effect, so resuming is unambiguous. + */ + /** + * Interactive transaction with PER-TRANSACTION rollback. + * + * This used to snapshot every table and restore the whole snapshot on failure. + * That is wrong under concurrency, and wrong in the direction that matters: when + * two transactions interleave at an await point and the second fails, restoring + * its snapshot also discards the FIRST one's committed writes. A test for + * concurrent idempotent creates then saw one batch with one payment instead of + * one batch with three, and would have been "fixed" by weakening the assertion + * β€” hiding the fact that the fake, not the code, was at fault. + * + * Real Postgres isolates transactions per connection, so each rolls back only + * its own work. This records an undo entry per write and replays it in reverse. + */ + db.$transaction = async (fn: (tx: any) => Promise) => { + const undo: (() => void)[] = []; + + const tx: any = {}; + for (const name of Object.keys(tables)) { + const base = db[name]; + const table = tables[name]; + tx[name] = { + ...base, + create: async (args: any) => { + const row = await base.create(args); + undo.push(() => { + const i = table.rows.findIndex((r) => r.id === row.id); + if (i >= 0) table.rows.splice(i, 1); + }); + return row; + }, + upsert: async (args: any) => { + const before = await base.findFirst({ where: args.where }); + const row = await base.upsert(args); + if (before) { + undo.push(() => { + const live = table.rows.find((r) => r.id === before.id); + if (live) { + for (const k of Object.keys(live)) delete live[k]; + Object.assign(live, before); + } + }); + } else { + undo.push(() => { + const i = table.rows.findIndex((r) => r.id === row.id); + if (i >= 0) table.rows.splice(i, 1); + }); + } + return row; + }, + update: async (args: any) => { + const before = await base.findFirst({ where: args.where }); + const row = await base.update(args); + if (before) { + undo.push(() => { + const live = table.rows.find((r) => r.id === before.id); + if (live) { + for (const k of Object.keys(live)) delete live[k]; + Object.assign(live, before); + } + }); + } + return row; + }, + updateMany: async (args: any) => { + const before = await base.findMany({ where: args.where }); + const result = await base.updateMany(args); + undo.push(() => { + for (const prior of before) { + const live = table.rows.find((r) => r.id === prior.id); + if (live) { + for (const k of Object.keys(live)) delete live[k]; + Object.assign(live, prior); + } + } + }); + return result; + }, + deleteMany: async (args: any = {}) => { + const before = await base.findMany({ where: args.where }); + const result = await base.deleteMany(args); + undo.push(() => { + for (const prior of before) table.rows.push(prior); + }); + return result; + }, + }; + } + + // The transaction client deliberately OMITS `$transaction`, exactly as + // Prisma's interactive client does. Passing `db` itself would let nested + // transaction code pass here and fail only against a real database β€” which is + // precisely what happened before this was tightened. + for (const k of Object.keys(db)) { + if (k === '$transaction' || k in tx) continue; + tx[k] = (db as any)[k]; + } + + try { + return await fn(tx); + } catch (e) { + for (const u of undo.reverse()) u(); + throw e; + } + }; + + db.__failOn = (m: string, op: string, times = 1) => failures.push({ model: m, op, times }); + + return db as FakeDb; +} + +/** Seed an organization and return its id. */ +export function seedOrg(db: FakeDb, id = 'org_test'): string { + db.__tables.organization.rows.push({ id, name: 'Test Org', slug: 'test-org' }); + return id; +} + +export function seedMember( + db: FakeDb, + orgId: string, + userId: string, + role: string, + walletAddress: string +): void { + if (!db.__tables.user.rows.some((u) => u.id === userId)) { + db.__tables.user.rows.push({ id: userId, walletAddress, role: 'EMPLOYEE' }); + } + if (!db.__tables.organization.rows.some((o) => o.id === orgId)) { + db.__tables.organization.rows.push({ id: orgId, name: orgId, slug: orgId }); + } + db.__tables.orgMember.rows.push({ + id: `ogm_${orgId}_${userId}`, + orgId, + userId, + role, + // ACTIVE by default. resolveTenant treats anything else as non-membership, + // so a seeded member without a status would look suspended. + status: 'ACTIVE', + createdAt: new Date(), + }); +} diff --git a/src/lib/payments/__tests__/reconcile.test.ts b/src/lib/payments/__tests__/reconcile.test.ts new file mode 100644 index 0000000..2ad8d52 --- /dev/null +++ b/src/lib/payments/__tests__/reconcile.test.ts @@ -0,0 +1,397 @@ +// @vitest-environment node +/** + * Reconciliation tests. + * + * The property under test is restraint: where the database and the chain + * disagree, the disagreement must become VISIBLE rather than be papered over. + * A reconciler that silently rewrites the losing side destroys the evidence an + * incident review depends on. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { PaymentState } from '@prisma/client'; +import { createFakeDb, seedOrg, type FakeDb } from './fake-db'; +import { + reconcileOrganization, + reconcileFailedTransactions, + CHAIN_STATUS, + type ChainEscrowView, +} from '../reconcile'; + +const ORG = 'org_test'; +const W1 = 'G' + '1'.repeat(55); +const W2 = 'G' + '2'.repeat(55); +const TOKEN = 'C' + 'T'.repeat(55); + +let db: FakeDb; + +function seedEscrowWithPayments( + onChainId: number, + payments: { index: number; worker: string; amount: bigint; state: PaymentState }[] +) { + db.__tables.escrow.rows.push({ + id: `esc_${onChainId}`, orgId: ORG, onChainId, + contractId: 'CCONTRACT', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', + assetDecimals: 7, totalAmountBaseUnits: 0n, + managerApproved: true, financeApproved: true, cancelled: false, + }); + db.__tables.payrollBatch.rows.push({ id: `bat_${onChainId}`, orgId: ORG, reference: `R${onChainId}` }); + for (const p of payments) { + db.__tables.payment.rows.push({ + id: `pay_${onChainId}_${p.index}`, orgId: ORG, + batchId: `bat_${onChainId}`, escrowId: `esc_${onChainId}`, + recipientAddress: p.worker, onChainPaymentIndex: p.index, + assetContractId: TOKEN, assetCode: 'USDC', assetDecimals: 7, + amountBaseUnits: p.amount, rateBaseUnits: 1n, hours: p.amount, + state: p.state, stateUpdatedAt: new Date(), createdAt: new Date(), + }); + } +} + +function chainView( + onChainId: number, + payments: { index: number; worker: string; amount: bigint; status: number }[] +): ChainEscrowView { + return { + onChainId, managerApproved: true, financeApproved: true, cancelled: false, + payments: payments.map((p) => ({ + index: p.index, worker: p.worker, token: TOKEN, + amountBaseUnits: p.amount, hours: p.amount, + proofVerified: true, status: p.status, + })), + }; +} + +const findings = () => db.__tables.reconciliationFinding.rows; +const payment = (id: string) => db.__tables.payment.rows.find((p) => p.id === id)!; + +beforeEach(() => { + db = createFakeDb(); + seedOrg(db, ORG); +}); + +describe('agreement', () => { + it('opens no findings when database and chain agree', async () => { + seedEscrowWithPayments(1, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + { index: 1, worker: W2, amount: 200n, state: PaymentState.PAID }, + ]); + const report = await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(1, [ + { index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.FINALIZED }, + { index: 1, worker: W2, amount: 200n, status: CHAIN_STATUS.FINALIZED }, + ]), + }, + ORG + ); + + expect(report.paymentsChecked).toBe(2); + expect(report.findingsOpened).toBe(0); + expect(findings()).toHaveLength(0); + }); +}); + +describe('chain settled, database behind', () => { + it('advances the database to PAID β€” the money moved regardless of our record', async () => { + seedEscrowWithPayments(2, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.CONFIRMING }, + ]); + const report = await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(2, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.FINALIZED }]), + }, + ORG + ); + + expect(report.advancedToPaid).toBe(1); + expect(payment('pay_2_0').state).toBe(PaymentState.PAID); + expect(payment('pay_2_0').settledAt).toBeTruthy(); + }); + + it('attributes the correction to the reconciler, not a person', async () => { + seedEscrowWithPayments(3, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.CONFIRMING }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(3, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.FINALIZED }]), + }, + ORG + ); + const e = db.__tables.auditEvent.rows.find((x) => x.newState === PaymentState.PAID); + expect(e.actorSystem).toBe('reconciler'); + expect(e.actorAddress).toBeNull(); + }); + + it('records a finding when it cannot advance from the current state', async () => { + // AWAITING_ORACLE β†’ PAID is not a declared transition. The table is NOT + // relaxed to accommodate the chain; the gap is surfaced instead. + seedEscrowWithPayments(4, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.AWAITING_ORACLE }, + ]); + const report = await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(4, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.FINALIZED }]), + }, + ORG + ); + + expect(report.advancedToPaid).toBe(0); + expect(findings().some((f) => f.kind === 'CHAIN_PAID_DB_NOT')).toBe(true); + expect(payment('pay_4_0').state).toBe(PaymentState.AWAITING_ORACLE); + }); +}); + +describe('database claims PAID, chain disagrees', () => { + it('records the discrepancy rather than silently un-paying it', async () => { + // The worst disagreement in the system: we are telling a finance team money + // moved when it did not. + seedEscrowWithPayments(5, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + const report = await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(5, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.PENDING }]), + }, + ORG + ); + + expect(report.findingsOpened).toBeGreaterThan(0); + const f = findings().find((x) => x.kind === 'DB_PAID_CHAIN_NOT'); + expect(f).toBeDefined(); + expect(f.dbState).toBe('PAID'); + + // PAID is terminal, so the state is not rewritten. The finding is the durable + // record, and the table is not weakened to permit an exit from PAID. + expect(payment('pay_5_0').state).toBe(PaymentState.PAID); + }); +}); + +describe('financial identity mismatches', () => { + it('flags an amount disagreement', async () => { + seedEscrowWithPayments(6, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(6, [{ index: 0, worker: W1, amount: 999n, status: CHAIN_STATUS.FINALIZED }]), + }, + ORG + ); + const f = findings().find((x) => x.kind === 'AMOUNT_MISMATCH'); + expect(f.dbState).toBe('100'); + expect(f.chainState).toBe('999'); + }); + + it('flags a recipient disagreement', async () => { + seedEscrowWithPayments(7, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(7, [{ index: 0, worker: W2, amount: 100n, status: CHAIN_STATUS.FINALIZED }]), + }, + ORG + ); + expect(findings().some((x) => x.kind === 'RECIPIENT_MISMATCH')).toBe(true); + }); +}); + +describe('missing and orphan payments', () => { + it('flags a database payment with no on-chain slot', async () => { + seedEscrowWithPayments(8, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.READY_TO_SETTLE }, + { index: 1, worker: W2, amount: 200n, state: PaymentState.READY_TO_SETTLE }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(8, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.PENDING }]), + }, + ORG + ); + expect(findings().some((x) => x.kind === 'MISSING_ON_CHAIN')).toBe(true); + }); + + it('flags an on-chain payment with no database row', async () => { + // Without this, the product simply does not show a payment that exists. + seedEscrowWithPayments(9, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.READY_TO_SETTLE }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(9, [ + { index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.PENDING }, + { index: 1, worker: W2, amount: 200n, status: CHAIN_STATUS.PENDING }, + ]), + }, + ORG + ); + expect(findings().some((x) => x.kind === 'ORPHAN_ON_CHAIN')).toBe(true); + }); +}); + +describe('unreadable chain state', () => { + it('does not treat a failed read as agreement', async () => { + // Assuming "all fine" when the chain cannot be read is how silent drift + // accumulates unnoticed. + seedEscrowWithPayments(10, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + const report = await reconcileOrganization( + { + db, + fetchChainEscrow: async () => { + throw new Error('rpc unavailable'); + }, + }, + ORG + ); + + expect(report.unreadable).toBe(1); + expect(report.paymentsChecked).toBe(0); + expect(findings().some((x) => x.kind === 'MISSING_ON_CHAIN')).toBe(true); + }); +}); + +describe('cancellation', () => { + it('cancels a database payment the chain reports as cancelled', async () => { + seedEscrowWithPayments(11, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.AWAITING_MANAGER }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(11, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.CANCELLED }]), + }, + ORG + ); + expect(payment('pay_11_0').state).toBe(PaymentState.CANCELLED); + }); + + it('does not cancel a payment already settled', async () => { + seedEscrowWithPayments(12, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + await reconcileOrganization( + { + db, + fetchChainEscrow: async () => + chainView(12, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.CANCELLED }]), + }, + ORG + ); + expect(payment('pay_12_0').state).toBe(PaymentState.PAID); + expect(findings().some((x) => x.kind === 'DB_PAID_CHAIN_NOT')).toBe(true); + }); +}); + +describe('idempotency of reconciliation itself', () => { + it('does not multiply findings for one unchanged discrepancy', async () => { + // A duplicated finding queue becomes noise, and a noisy queue gets ignored. + seedEscrowWithPayments(13, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + const deps = { + db, + fetchChainEscrow: async () => + chainView(13, [{ index: 0, worker: W1, amount: 999n, status: CHAIN_STATUS.FINALIZED }]), + }; + + await reconcileOrganization(deps, ORG); + const afterFirst = findings().length; + const second = await reconcileOrganization(deps, ORG); + + expect(second.findingsOpened).toBe(0); + expect(findings()).toHaveLength(afterFirst); + }); + + it('is safe to run repeatedly on a healthy organization', async () => { + seedEscrowWithPayments(14, [ + { index: 0, worker: W1, amount: 100n, state: PaymentState.PAID }, + ]); + const deps = { + db, + fetchChainEscrow: async () => + chainView(14, [{ index: 0, worker: W1, amount: 100n, status: CHAIN_STATUS.FINALIZED }]), + }; + await reconcileOrganization(deps, ORG); + await reconcileOrganization(deps, ORG); + expect(findings()).toHaveLength(0); + expect(db.__tables.auditEvent.rows).toHaveLength(0); + }); +}); + +describe('transactions recorded as failed that actually succeeded', () => { + beforeEach(() => { + db.__tables.payment.rows.push({ + id: 'pay_x', orgId: ORG, batchId: 'b', escrowId: 'e', + recipientAddress: W1, onChainPaymentIndex: 0, + amountBaseUnits: 100n, rateBaseUnits: 1n, hours: 100n, + assetDecimals: 7, assetCode: 'USDC', + state: PaymentState.SETTLEMENT_FAILED, stateUpdatedAt: new Date(), createdAt: new Date(), + }); + db.__tables.blockchainTransaction.rows.push({ + id: 'btx_1', orgId: ORG, paymentId: 'pay_x', kind: 'PAY_BATCH', + status: 'FAILED', idempotencyKey: 'k1', attempt: 1, hash: 'HASH_OK', + network: 'testnet', + }); + }); + + it('detects a false failure and warns against retrying', async () => { + // An RPC timeout reported as failure while the transaction landed is the most + // dangerous state in a payments system: it invites a retry that double-pays. + const r = await reconcileFailedTransactions( + { db, fetchTxSucceeded: async () => true }, + ORG + ); + + expect(r.falselyFailed).toBe(1); + const f = findings().find((x) => x.kind === 'FAILED_TX_ACTUALLY_SUCCEEDED'); + expect(f.detail).toMatch(/Do not retry/i); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('CONFIRMED'); + }); + + it('leaves a genuinely failed transaction alone', async () => { + const r = await reconcileFailedTransactions( + { db, fetchTxSucceeded: async () => false }, + ORG + ); + expect(r.falselyFailed).toBe(0); + expect(findings()).toHaveLength(0); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('FAILED'); + }); + + it('leaves a transaction alone when the chain cannot be read', async () => { + const r = await reconcileFailedTransactions( + { + db, + fetchTxSucceeded: async () => { + throw new Error('rpc down'); + }, + }, + ORG + ); + expect(r.falselyFailed).toBe(0); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('FAILED'); + }); +}); diff --git a/src/lib/payments/__tests__/state-machine.integration.test.ts b/src/lib/payments/__tests__/state-machine.integration.test.ts new file mode 100644 index 0000000..254a9c7 --- /dev/null +++ b/src/lib/payments/__tests__/state-machine.integration.test.ts @@ -0,0 +1,422 @@ +/** + * The payment state machine against real PostgreSQL. + * + * The unit tests prove the transition TABLE. These prove that the table and the + * database agree: that a transition is a compare-and-swap against a real row, that + * a losing race is reported rather than overwriting someone's work, and β€” most + * importantly β€” that no application actor can persist PAID. + */ + +import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest'; +import { OrgRole, PaymentState } from '@prisma/client'; +import prisma from '@/lib/db/prisma'; +import { transitionPayment } from '../service'; +import { + assertLocalDatabase, + resetDatabase, + seedOrganization, + payeeWallet, + type SeededOrg, +} from '@/lib/db/__tests__/helpers'; + +assertLocalDatabase(); + +let org: SeededOrg; +let paymentId: string; + +const USER_MANAGER = { kind: 'user' as const, role: OrgRole.MANAGER, address: '' }; +const INDEXER = { kind: 'indexer' as const, system: 'indexer' }; +const SYSTEM = { kind: 'system' as const, system: 'submitter' }; + +beforeAll(async () => { + await prisma.$connect(); +}); +afterAll(async () => { + await prisma.$disconnect(); +}); + +beforeEach(async () => { + await resetDatabase(prisma); + org = await seedOrganization(prisma, 'sm'); + USER_MANAGER.address = org.members.MANAGER.wallet; + + const batch = await prisma.payrollBatch.create({ + data: { orgId: org.orgId, reference: 'CF-SM1' }, + select: { id: true }, + }); + const payment = await prisma.payment.create({ + data: { + orgId: org.orgId, + batchId: batch.id, + recipientAddress: payeeWallet('smpayee'), + amountBaseUnits: 10_000_000_000n, + rateBaseUnits: 250_000_000n, + hours: 40n, + }, + select: { id: true }, + }); + paymentId = payment.id; +}); + +async function stateOf(): Promise { + const p = await prisma.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { state: true }, + }); + return p.state; +} + +/** Walk the happy path as far as the given state, using only legal transitions. */ +async function advanceTo(target: PaymentState): Promise { + const path: { to: PaymentState; actor: any }[] = [ + { to: PaymentState.VALIDATING, actor: USER_MANAGER }, + { to: PaymentState.AWAITING_ORACLE, actor: INDEXER }, + { to: PaymentState.ORACLE_VERIFIED, actor: INDEXER }, + { to: PaymentState.AWAITING_MANAGER, actor: INDEXER }, + { to: PaymentState.AWAITING_FINANCE, actor: INDEXER }, + { to: PaymentState.READY_TO_SETTLE, actor: INDEXER }, + { to: PaymentState.SUBMITTING, actor: USER_MANAGER }, + { to: PaymentState.CONFIRMING, actor: SYSTEM }, + { to: PaymentState.PAID, actor: INDEXER }, + ]; + for (const step of path) { + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: step.to, + actor: step.actor, + ...(step.to === PaymentState.PAID + ? { txHash: 'a'.repeat(64), settledAt: new Date() } + : {}), + }); + if (!outcome.ok) { + throw new Error(`could not reach ${step.to}: ${outcome.message}`); + } + if (step.to === target) return; + } +} + +describe('The happy path, persisted', () => { + it('walks DRAFT to PAID and records every step in the audit trail', async () => { + await advanceTo(PaymentState.PAID); + + const payment = await prisma.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { state: true, settlementTxHash: true, settledAt: true, amountBaseUnits: true }, + }); + expect(payment.state).toBe(PaymentState.PAID); + expect(payment.settlementTxHash).toBe('a'.repeat(64)); + expect(payment.settledAt).toBeInstanceOf(Date); + // The amount is never rewritten by a transition. + expect(payment.amountBaseUnits).toBe(10_000_000_000n); + + const events = await prisma.auditEvent.findMany({ + where: { orgId: org.orgId, paymentId }, + orderBy: { createdAt: 'asc' }, + select: { + type: true, + previousState: true, + newState: true, + actorSystem: true, + actorAddress: true, + txHash: true, + }, + }); + // Nine transitions, nine audit rows. An unaudited state change is not one. + expect(events).toHaveLength(9); + expect(events.every((e) => e.type === 'payment.state.changed')).toBe(true); + + // The trail is CONTINUOUS: each row's previous state is the one before it. A + // gap would mean a state change happened without being recorded. + expect(events[0].previousState).toBe(PaymentState.DRAFT); + for (let i = 1; i < events.length; i++) { + expect(events[i].previousState).toBe(events[i - 1].newState); + } + expect(events[events.length - 1].newState).toBe(PaymentState.PAID); + + const last = events[events.length - 1]; + // The indexer records settlement, not a person, and it carries the evidence. + expect(last.actorSystem).toBe('indexer'); + expect(last.actorAddress).toBeNull(); + expect(last.txHash).toBe('a'.repeat(64)); + + // No person appears as the actor on the transition into PAID. + const paidRows = events.filter((e) => e.newState === PaymentState.PAID); + expect(paidRows).toHaveLength(1); + expect(paidRows[0].actorAddress).toBeNull(); + }); +}); + +describe('PAID is reachable only from chain evidence', () => { + it('refuses a user actor driving CONFIRMING to PAID', async () => { + await advanceTo(PaymentState.CONFIRMING); + + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.PAID, + actor: USER_MANAGER, + txHash: 'b'.repeat(64), + }); + + expect(outcome.ok).toBe(false); + // The database still says CONFIRMING. The product cannot claim a payment + // settled because somebody asked it to. + expect(await stateOf()).toBe(PaymentState.CONFIRMING); + const payment = await prisma.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { settlementTxHash: true, settledAt: true }, + }); + expect(payment.settlementTxHash).toBeNull(); + expect(payment.settledAt).toBeNull(); + }); + + it.each([ + [OrgRole.OWNER], + [OrgRole.ADMIN], + [OrgRole.MANAGER], + [OrgRole.FINANCE], + ])('refuses %s, however privileged, from marking a payment PAID', async (role) => { + await advanceTo(PaymentState.CONFIRMING); + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.PAID, + actor: { kind: 'user', role, address: org.members[role].wallet }, + }); + expect(outcome.ok).toBe(false); + expect(await stateOf()).toBe(PaymentState.CONFIRMING); + }); + + it('refuses a jump from DRAFT straight to PAID even for the indexer', async () => { + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.PAID, + actor: INDEXER, + txHash: 'c'.repeat(64), + }); + expect(outcome.ok).toBe(false); + expect(await stateOf()).toBe(PaymentState.DRAFT); + }); +}); + +describe('Invalid transitions leave the row untouched', () => { + it('refuses a transition whose source state does not match', async () => { + // DRAFT -> CONFIRMING is not in the table at all. + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.CONFIRMING, + actor: SYSTEM, + }); + expect(outcome.ok).toBe(false); + expect(await stateOf()).toBe(PaymentState.DRAFT); + expect(await prisma.auditEvent.count({ where: { paymentId } })).toBe(0); + }); + + it('refuses a role that may not perform an otherwise valid transition', async () => { + // DRAFT -> VALIDATING is valid, but not for a VIEWER. + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.VALIDATING, + actor: { kind: 'user', role: OrgRole.VIEWER, address: org.members.VIEWER.wallet }, + }); + expect(outcome.ok).toBe(false); + expect(await stateOf()).toBe(PaymentState.DRAFT); + }); + + it('will not transition a payment belonging to another organization', async () => { + const other = await seedOrganization(prisma, 'smother'); + const outcome = await transitionPayment(prisma, { + paymentId, + // The payment exists, but not in this tenant. + orgId: other.orgId, + to: PaymentState.VALIDATING, + actor: { kind: 'user', role: OrgRole.MANAGER, address: other.members.MANAGER.wallet }, + }); + expect(outcome.ok).toBe(false); + if (outcome.ok) return; + expect(outcome.status).toBe(404); + expect(await stateOf()).toBe(PaymentState.DRAFT); + }); +}); + +describe('Concurrency on a real row', () => { + it('reports a repeated transition as unchanged rather than auditing it twice', async () => { + const first = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.VALIDATING, + actor: USER_MANAGER, + }); + const second = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.VALIDATING, + actor: USER_MANAGER, + }); + + expect(first.ok && first.changed).toBe(true); + expect(second.ok && second.changed).toBe(false); + // An indexer re-processing an event is a duplicate, not a failure β€” but it + // must not produce a second audit row either. + expect(await prisma.auditEvent.count({ where: { paymentId } })).toBe(1); + }); + + it('lets exactly one of two genuinely incompatible transitions win', async () => { + await advanceTo(PaymentState.READY_TO_SETTLE); + const auditBefore = await prisma.auditEvent.count({ where: { paymentId } }); + + // Both are legal FROM READY_TO_SETTLE, and neither is reachable from the + // other's destination: SUBMITTING has no path to CANCELLED, and CANCELLED is + // terminal. So exactly one can apply. + // + // (An earlier version of this test raced SUBMITTING against PAID and expected + // one winner. Both succeeded β€” correctly: the table allows SUBMITTING -> PAID, + // because a confirmation can arrive before our own update lands. The test was + // wrong, not the code.) + const [a, b] = await Promise.all([ + transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.SUBMITTING, + actor: USER_MANAGER, + }), + transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.CANCELLED, + actor: INDEXER, + reason: 'escrow cancelled on-chain', + }), + ]); + + const winners = [a, b].filter((r) => r.ok && r.changed === true); + const losers = [a, b].filter((r) => !r.ok); + expect(winners).toHaveLength(1); + expect(losers).toHaveLength(1); + + // The loser is told its work was not applied, rather than overwriting the + // winner's transition. Which refusal it gets depends on how the race resolved, + // and all three are correct: + // + // CONCURRENT_MODIFICATION both read READY_TO_SETTLE; the compare-and-swap + // matched zero rows because the winner moved first + // INVALID_TRANSITION the loser read SUBMITTING, and SUBMITTING has no + // path to CANCELLED + // TERMINAL the loser read CANCELLED, which nothing leaves + // + // This assertion originally listed only the first two and failed about half the + // time. The missing case was TERMINAL β€” the product was right, the test was + // incomplete. Worth keeping as a comment: a flaky financial test invites being + // silenced, and the reason it flaked is the interesting part. + const loser = losers[0]; + if (!loser.ok) { + expect(['CONCURRENT_MODIFICATION', 'INVALID_TRANSITION', 'TERMINAL']).toContain( + loser.code, + ); + } + + const final = await stateOf(); + expect([PaymentState.SUBMITTING, PaymentState.CANCELLED]).toContain(final); + + // One new audit row for the one transition that happened, and the trail stays + // continuous through the race. + const events = await prisma.auditEvent.findMany({ + where: { paymentId }, + orderBy: { createdAt: 'asc' }, + select: { previousState: true, newState: true }, + }); + expect(events).toHaveLength(auditBefore + 1); + expect(events[events.length - 1].previousState).toBe(PaymentState.READY_TO_SETTLE); + expect(events[events.length - 1].newState).toBe(final); + }); +}); + +describe('Failure states', () => { + it('records a submission failure with its reason and allows a retry', async () => { + await advanceTo(PaymentState.SUBMITTING); + + const failed = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.SUBMISSION_FAILED, + actor: SYSTEM, + reason: 'RPC timed out before the transaction was sent', + }); + expect(failed.ok).toBe(true); + + const payment = await prisma.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { state: true, stateReason: true, settlementTxHash: true }, + }); + expect(payment.state).toBe(PaymentState.SUBMISSION_FAILED); + expect(payment.stateReason).toBe('RPC timed out before the transaction was sent'); + // Nothing reached the chain, so there is no hash to record. + expect(payment.settlementTxHash).toBeNull(); + + // Safe to retry precisely because nothing was submitted. + const retried = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.READY_TO_SETTLE, + actor: USER_MANAGER, + }); + expect(retried.ok).toBe(true); + expect(await stateOf()).toBe(PaymentState.READY_TO_SETTLE); + }); + + it('does not move a PAID payment backwards, for any actor', async () => { + await advanceTo(PaymentState.PAID); + + for (const to of [ + PaymentState.READY_TO_SETTLE, + PaymentState.SUBMITTING, + PaymentState.CONFIRMING, + PaymentState.CANCELLED, + PaymentState.REJECTED, + PaymentState.SUBMISSION_FAILED, + ]) { + for (const actor of [USER_MANAGER, INDEXER, SYSTEM]) { + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to, + actor, + }); + expect(outcome.ok).toBe(false); + } + } + + // A financial terminal state is never rewritten to make the record look tidy. + expect(await stateOf()).toBe(PaymentState.PAID); + }); + + it('allows RECONCILIATION_REQUIRED from PAID, because a disagreement is a fact', async () => { + await advanceTo(PaymentState.PAID); + const outcome = await transitionPayment(prisma, { + paymentId, + orgId: org.orgId, + to: PaymentState.RECONCILIATION_REQUIRED, + actor: { kind: 'reconciler', system: 'reconciler' }, + reason: 'chain shows no transfer for this payment', + }); + + // Whether this is permitted is the state machine's decision; what matters is + // that the database agrees with it either way. + if (outcome.ok) { + expect(await stateOf()).toBe(PaymentState.RECONCILIATION_REQUIRED); + const p = await prisma.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { stateReason: true, settlementTxHash: true }, + }); + expect(p.stateReason).toContain('chain shows no transfer'); + // The settlement evidence is preserved, not erased. + expect(p.settlementTxHash).toBe('a'.repeat(64)); + } else { + expect(await stateOf()).toBe(PaymentState.PAID); + } + }); +}); diff --git a/src/lib/payments/__tests__/state-machine.test.ts b/src/lib/payments/__tests__/state-machine.test.ts new file mode 100644 index 0000000..860e5df --- /dev/null +++ b/src/lib/payments/__tests__/state-machine.test.ts @@ -0,0 +1,316 @@ +// @vitest-environment node +/** + * Payment state machine tests. + * + * The table is enumerated rather than spot-checked: every declared transition is + * exercised, and every UNDECLARED pair is asserted invalid. That second half is + * the one that matters β€” a state machine tested only on its happy paths will + * happily accept DRAFT β†’ PAID. + */ +import { describe, it, expect } from 'vitest'; +import { PaymentState, OrgRole } from '@prisma/client'; +import { + TRANSITIONS, + TERMINAL_STATES, + isTerminal, + transitionsFrom, + findTransition, + checkTransition, + assertTransition, + InvalidTransitionError, + describeState, + STATE_DESCRIPTORS, + type Actor, +} from '../state-machine'; + +const ALL_STATES = Object.values(PaymentState); + +const indexer: Actor = { kind: 'indexer', system: 'indexer' }; +const reconciler: Actor = { kind: 'reconciler', system: 'reconciler' }; +const system: Actor = { kind: 'system', system: 'validator' }; +const asUser = (role: OrgRole): Actor => ({ kind: 'user', role, address: 'GUSER' }); + +describe('transition table integrity', () => { + it('declares no duplicate fromβ†’to pairs', () => { + const seen = new Set(); + for (const t of TRANSITIONS) { + const key = `${t.from}->${t.to}`; + expect(seen.has(key), `duplicate transition ${key}`).toBe(false); + seen.add(key); + } + }); + + it('never declares a transition out of a terminal state', () => { + for (const t of TRANSITIONS) { + expect(TERMINAL_STATES).not.toContain(t.from); + } + }); + + it('gives every non-terminal state at least one way out', () => { + // A non-terminal state with no exit is a trap: a payment entering it can + // never be resolved, by anyone, ever. + for (const state of ALL_STATES) { + if (isTerminal(state)) continue; + expect(transitionsFrom(state).length, `${state} is a dead end`).toBeGreaterThan(0); + } + }); + + it('gives every state a non-generic descriptor', () => { + for (const state of ALL_STATES) { + const d = describeState(state); + expect(d.label.length).toBeGreaterThan(0); + expect(d.description.length).toBeGreaterThan(0); + // "Processing" for everything is exactly what this model replaces. + expect(d.label).not.toBe('Processing'); + } + }); + + it('gives every state a distinct label', () => { + const labels = ALL_STATES.map((s) => STATE_DESCRIPTORS[s].label); + expect(new Set(labels).size).toBe(labels.length); + }); + + it('declares a user path only where roles are listed', () => { + for (const t of TRANSITIONS) { + if (t.actors.includes('user')) { + expect(t.roles?.length ?? 0, `${t.from}->${t.to} allows user but lists no roles`) + .toBeGreaterThan(0); + } + } + }); + + it('states a reason for every transition', () => { + for (const t of TRANSITIONS) { + expect(t.reason.length, `${t.from}->${t.to} has no reason`).toBeGreaterThan(10); + } + }); +}); + +describe('every declared transition is accepted for its permitted actors', () => { + for (const t of TRANSITIONS) { + it(`${t.from} β†’ ${t.to} (${t.actors.join('/')})`, () => { + for (const kind of t.actors) { + if (kind === 'user') { + for (const role of t.roles ?? []) { + const r = checkTransition(t.from, t.to, asUser(role)); + expect(r.ok, `${role} should be allowed`).toBe(true); + } + } else { + const r = checkTransition(t.from, t.to, { kind }); + expect(r.ok, `${kind} should be allowed`).toBe(true); + } + } + }); + } +}); + +describe('every undeclared pair is rejected', () => { + it('rejects all fromβ†’to pairs absent from the table, for every actor kind', () => { + const kinds = ['user', 'indexer', 'reconciler', 'system'] as const; + let checked = 0; + for (const from of ALL_STATES) { + for (const to of ALL_STATES) { + if (from === to) continue; + if (findTransition(from, to)) continue; + for (const kind of kinds) { + const actor: Actor = kind === 'user' ? asUser(OrgRole.OWNER) : { kind }; + const r = checkTransition(from, to, actor); + expect(r.ok, `${from} β†’ ${to} must be invalid for ${kind}`).toBe(false); + checked++; + } + } + } + // Guards against the loop silently not running. + expect(checked).toBeGreaterThan(500); + }); +}); + +describe('PAID is reachable only by the indexer', () => { + it('has no user- or system-initiated path to PAID', () => { + // This is the central safety property: the frontend must not be able to + // manufacture a successful payment. + const intoPaid = TRANSITIONS.filter((t) => t.to === PaymentState.PAID); + expect(intoPaid.length).toBeGreaterThan(0); + for (const t of intoPaid) { + expect(t.actors).not.toContain('user'); + expect(t.actors).not.toContain('system'); + expect(t.actors.some((a) => a === 'indexer' || a === 'reconciler')).toBe(true); + } + }); + + it.each([ + OrgRole.OWNER, OrgRole.ADMIN, OrgRole.MANAGER, OrgRole.FINANCE, OrgRole.WORKER, OrgRole.VIEWER, + ])('refuses %s attempting CONFIRMING β†’ PAID', (role) => { + const r = checkTransition(PaymentState.CONFIRMING, PaymentState.PAID, asUser(role)); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('ACTOR_NOT_PERMITTED'); + }); + + it('refuses the shortcut DRAFT β†’ PAID outright', () => { + const r = checkTransition(PaymentState.DRAFT, PaymentState.PAID, indexer); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('INVALID_TRANSITION'); + }); + + it.each([ + PaymentState.SUBMITTING, + PaymentState.READY_TO_SETTLE, + PaymentState.CONFIRMING, + ])('refuses any USER reaching PAID from %s', (from) => { + // The indexer MAY reach PAID from these, because settlement can be driven + // outside this application and the chain is authoritative. What must never + // exist is a user-initiated path: "I submitted it" is not "it settled". + for (const role of [OrgRole.OWNER, OrgRole.ADMIN, OrgRole.MANAGER, OrgRole.FINANCE]) { + expect(checkTransition(from, PaymentState.PAID, asUser(role)).ok).toBe(false); + } + expect(checkTransition(from, PaymentState.PAID, system).ok).toBe(false); + expect(checkTransition(from, PaymentState.PAID, indexer).ok).toBe(true); + }); + + it('refuses PAID from any state before both approvals exist on-chain', () => { + // An approved payment may settle; an unapproved one reaching PAID would mean + // the dual-approval gate was bypassed, so the log disagreeing with our record + // must surface as a finding rather than be absorbed as a valid transition. + for (const from of [ + PaymentState.DRAFT, PaymentState.VALIDATING, PaymentState.AWAITING_ORACLE, + PaymentState.ORACLE_VERIFIED, PaymentState.AWAITING_MANAGER, PaymentState.AWAITING_FINANCE, + ]) { + expect(checkTransition(from, PaymentState.PAID, indexer).ok).toBe(false); + } + }); +}); + +describe('separation of duties', () => { + it('does not let a MANAGER exercise the finance rejection', () => { + const r = checkTransition( + PaymentState.AWAITING_FINANCE, PaymentState.REJECTED, asUser(OrgRole.MANAGER) + ); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('ROLE_NOT_PERMITTED'); + }); + + it('does let FINANCE exercise it', () => { + expect( + checkTransition(PaymentState.AWAITING_FINANCE, PaymentState.REJECTED, asUser(OrgRole.FINANCE)).ok + ).toBe(true); + }); + + it('does not let a WORKER advance their own payment', () => { + for (const to of ALL_STATES) { + if (to === PaymentState.AWAITING_MANAGER) continue; + const r = checkTransition(PaymentState.ORACLE_VERIFIED, to, asUser(OrgRole.WORKER)); + expect(r.ok, `WORKER must not drive ORACLE_VERIFIED β†’ ${to}`).toBe(false); + } + }); + + it('does not let a VIEWER change anything', () => { + for (const t of TRANSITIONS) { + const r = checkTransition(t.from, t.to, asUser(OrgRole.VIEWER)); + expect(r.ok, `VIEWER must not perform ${t.from} β†’ ${t.to}`).toBe(false); + } + }); +}); + +describe('retry semantics', () => { + it('lets a user retry a SUBMISSION_FAILED payment β€” nothing reached the chain', () => { + expect( + checkTransition(PaymentState.SUBMISSION_FAILED, PaymentState.READY_TO_SETTLE, asUser(OrgRole.MANAGER)).ok + ).toBe(true); + }); + + it('does NOT let a user retry a SETTLEMENT_FAILED payment', () => { + // It reached the chain. What it did there must be established first, and a + // human clicking "retry" has not established anything. + const r = checkTransition( + PaymentState.SETTLEMENT_FAILED, PaymentState.READY_TO_SETTLE, asUser(OrgRole.OWNER) + ); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('ACTOR_NOT_PERMITTED'); + }); + + it('lets the reconciler clear SETTLEMENT_FAILED once the chain is known', () => { + expect( + checkTransition(PaymentState.SETTLEMENT_FAILED, PaymentState.READY_TO_SETTLE, reconciler).ok + ).toBe(true); + }); + + it('lets a late paid event correct a SETTLEMENT_FAILED record', () => { + // The log wins over our failure record. + expect( + checkTransition(PaymentState.SETTLEMENT_FAILED, PaymentState.PAID, indexer).ok + ).toBe(true); + }); +}); + +describe('duplicate and terminal transitions', () => { + it.each(ALL_STATES)('rejects a no-op transition on %s', (state) => { + const r = checkTransition(state, state, indexer); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('SAME_STATE'); + }); + + it.each(TERMINAL_STATES)('rejects any transition out of terminal %s', (state) => { + for (const to of ALL_STATES) { + if (to === state) continue; + const r = checkTransition(state, to, reconciler); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('TERMINAL'); + } + }); + + it('rejects re-paying an already PAID payment', () => { + const r = checkTransition(PaymentState.PAID, PaymentState.SUBMITTING, asUser(OrgRole.OWNER)); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('TERMINAL'); + }); +}); + +describe('assertTransition', () => { + it('returns the transition when valid', () => { + const t = assertTransition(PaymentState.CONFIRMING, PaymentState.PAID, indexer); + expect(t.to).toBe(PaymentState.PAID); + }); + + it('throws InvalidTransitionError carrying the code', () => { + try { + assertTransition(PaymentState.DRAFT, PaymentState.PAID, indexer); + throw new Error('should have thrown'); + } catch (e) { + expect(e).toBeInstanceOf(InvalidTransitionError); + expect((e as InvalidTransitionError).code).toBe('INVALID_TRANSITION'); + } + }); + + it('requires a role when acting as a user', () => { + const r = checkTransition(PaymentState.DRAFT, PaymentState.VALIDATING, { kind: 'user' }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.code).toBe('ROLE_REQUIRED'); + }); +}); + +describe('transaction display safety', () => { + it('does not claim a transaction may exist before submission', () => { + // Rendering an explorer link for a payment that was never submitted invites + // a reader to believe something settled. + for (const s of [PaymentState.DRAFT, PaymentState.VALIDATING, PaymentState.AWAITING_ORACLE]) { + expect(describeState(s).mayHaveTransaction).toBe(false); + } + expect(describeState(PaymentState.SUBMISSION_FAILED).mayHaveTransaction).toBe(false); + }); + + it('marks only PAID as a success tone', () => { + const success = ALL_STATES.filter((s) => STATE_DESCRIPTORS[s].tone === 'success'); + expect(success).toEqual([PaymentState.PAID]); + }); + + it('flags every failure state as needing attention', () => { + for (const s of [ + PaymentState.SUBMISSION_FAILED, + PaymentState.SETTLEMENT_FAILED, + PaymentState.RECONCILIATION_REQUIRED, + PaymentState.EXPIRED, + ]) { + expect(describeState(s).needsAttention, `${s}`).toBe(true); + } + }); +}); diff --git a/src/lib/payments/actions.ts b/src/lib/payments/actions.ts new file mode 100644 index 0000000..ff6a2f4 --- /dev/null +++ b/src/lib/payments/actions.ts @@ -0,0 +1,385 @@ +/** + * Business actions on payments. + * + * Routes expose ACTIONS β€” approve, reject, submit, cancel, retry, reconcile β€” + * and this layer decides the resulting state. There is deliberately no + * "set status to X" operation: letting a caller name the destination state makes + * the state machine advisory, and the one state a caller would most like to name + * is PAID. + * + * Idempotency is built in rather than bolted on. In payroll, a retried request + * after a timeout must not produce a second payment, so every action that can + * reach the chain is keyed and replays its original outcome. + */ + +import { PaymentState, OrgRole, ApprovalDecision, TxKind, TxStatus } from '@prisma/client'; +import { transitionPayment, recordAuditEvent, type TransitionOutcome } from './service'; +import { actorFromMembership, findPaymentForMember, type Membership } from './authz'; +import type { Actor } from './state-machine'; + +export interface ActionContext { + db: any; + membership: Membership; + paymentId: string; + /** Caller-supplied key, from the `Idempotency-Key` header. */ + idempotencyKey?: string; + reason?: string; +} + +export type ActionResult = + | { ok: true; status: 200 | 201; body: Record } + | { ok: false; status: 400 | 401 | 403 | 404 | 409; message: string; code?: string }; + +function outcomeToResult( + outcome: TransitionOutcome, + extra: Record = {} +): ActionResult { + if (!outcome.ok) { + return { ok: false, status: outcome.status, message: outcome.message, code: outcome.code }; + } + return { + ok: true, + status: 200, + body: { + previousState: outcome.previousState, + state: outcome.newState, + // Distinguishes "we just did it" from "it was already done", so a client + // retrying after a timeout is not told it changed something twice. + changed: outcome.changed, + ...(outcome.changed ? {} : { note: outcome.note }), + ...extra, + }, + }; +} + +/** + * Approve a payment in the caller's own role. + * + * The ROLE IS NOT A PARAMETER. Taking it from the request body would let a + * manager submit `{role: 'FINANCE'}` and satisfy both halves of the dual-approval + * gate alone β€” the exact property the contract enforces with SignersNotDistinct. + * It is derived from membership instead. + * + * This records an off-chain approval decision. The authoritative approval is the + * on-chain signature, which the indexer observes; this is the workflow record of + * who decided what, and it does not by itself advance a payment to settlement. + */ +export async function approvePayment(ctx: ActionContext): Promise { + const { db, membership } = ctx; + + const role = membership.role; + if (role !== OrgRole.MANAGER && role !== OrgRole.FINANCE && + role !== OrgRole.OWNER && role !== OrgRole.ADMIN) { + return { ok: false, status: 403, message: 'Your role cannot approve payments.' }; + } + + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + const payment = found.value; + + // Which half of the gate this caller is exercising, from their role alone. + const approvalRole = + role === OrgRole.FINANCE + ? OrgRole.FINANCE + : role === OrgRole.MANAGER + ? OrgRole.MANAGER + : // OWNER/ADMIN act for whichever approval the payment is waiting on, and + // cannot supply both: the second call finds its role already recorded. + payment.state === PaymentState.AWAITING_FINANCE + ? OrgRole.FINANCE + : OrgRole.MANAGER; + + const existing = await db.approval.findUnique({ + where: { paymentId_role: { paymentId: payment.id, role: approvalRole } }, + }); + if (existing) { + return { + ok: true, + status: 200, + body: { + state: payment.state, + changed: false, + note: `${approvalRole} has already recorded a decision on this payment.`, + decision: existing.decision, + }, + }; + } + + // Separation of duties, enforced off-chain as well as on: the same wallet must + // not hold both halves. The contract rejects it too, but failing here gives the + // operator a readable reason instead of a trapped transaction. + const other = await db.approval.findFirst({ + where: { + paymentId: payment.id, + role: approvalRole === OrgRole.MANAGER ? OrgRole.FINANCE : OrgRole.MANAGER, + }, + }); + if (other && other.actorAddress === membership.walletAddress) { + return { + ok: false, + status: 409, + message: + 'You already recorded the other approval on this payment. CoreFlow ' + + 'requires two distinct approvers.', + }; + } + + await db.approval.create({ + data: { + // REQUIRED. Approval's parent relation is a composite foreign key on + // (orgId, paymentId), so the tenant is part of the row's identity rather + // than something to be reached by joining through the payment. + orgId: membership.orgId, + paymentId: payment.id, + role: approvalRole, + decision: ApprovalDecision.APPROVED, + actorAddress: membership.walletAddress, + reason: ctx.reason ?? null, + }, + }); + + await recordAuditEvent(db, { + orgId: membership.orgId, + type: 'approval.granted', + actor: actorFromMembership(membership), + paymentId: payment.id, + batchId: payment.batchId, + escrowId: payment.escrowId ?? undefined, + metadata: { role: approvalRole, decision: 'APPROVED' }, + }); + + return { + ok: true, + status: 201, + body: { + state: payment.state, + changed: false, + approvalRole, + note: + 'Approval recorded. The payment advances when the corresponding on-chain ' + + 'signature is observed by the indexer.', + }, + }; +} + +/** Decline a payment. Terminal. */ +export async function rejectPayment(ctx: ActionContext): Promise { + const { db, membership } = ctx; + if (!ctx.reason || ctx.reason.trim().length < 3) { + // A rejection without a reason is unauditable: nobody downstream can tell + // whether it was a data error, a dispute, or a mistake. + return { ok: false, status: 400, message: 'A reason is required to reject a payment.' }; + } + + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + const payment = found.value; + + const outcome = await transitionPayment(db, { + paymentId: payment.id, + to: PaymentState.REJECTED, + actor: actorFromMembership(membership), + orgId: membership.orgId, + reason: ctx.reason, + metadata: { action: 'reject' }, + }); + + if (outcome.ok && outcome.changed) { + await db.approval.upsert({ + where: { + paymentId_role: { + paymentId: payment.id, + role: membership.role === OrgRole.FINANCE ? OrgRole.FINANCE : OrgRole.MANAGER, + }, + }, + create: { + // REQUIRED, for the same reason as in approvePayment: the composite + // foreign key makes the tenant part of the row's identity. + orgId: membership.orgId, + paymentId: payment.id, + role: membership.role === OrgRole.FINANCE ? OrgRole.FINANCE : OrgRole.MANAGER, + decision: ApprovalDecision.REJECTED, + actorAddress: membership.walletAddress, + reason: ctx.reason, + }, + update: { decision: ApprovalDecision.REJECTED, reason: ctx.reason }, + }); + } + + return outcomeToResult(outcome); +} + +/** Withdraw a payment before settlement. */ +export async function cancelPayment(ctx: ActionContext): Promise { + const { db, membership } = ctx; + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + + return outcomeToResult( + await transitionPayment(db, { + paymentId: found.value.id, + to: PaymentState.CANCELLED, + actor: actorFromMembership(membership), + orgId: membership.orgId, + reason: ctx.reason ?? 'Cancelled by operator.', + metadata: { action: 'cancel' }, + }) + ); +} + +/** + * Begin settlement. + * + * Moves the payment to SUBMITTING and records a BlockchainTransaction attempt + * keyed by the caller's idempotency key. SUBMITTING is explicitly NOT paid; the + * indexer decides that later from the chain. + * + * A repeat request with the same key returns the ORIGINAL attempt rather than + * starting a second one. That is the difference between a retried request and a + * duplicated payment. + */ +export async function submitPaymentForSettlement(ctx: ActionContext): Promise { + const { db, membership } = ctx; + + if (!ctx.idempotencyKey) { + return { + ok: false, + status: 400, + message: + 'An Idempotency-Key header is required to submit a settlement, so a ' + + 'retried request cannot pay twice.', + }; + } + + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + const payment = found.value; + + // Replay of a known key: hand back what happened the first time. + const existing = await db.blockchainTransaction.findUnique({ + where: { idempotencyKey: ctx.idempotencyKey }, + }); + if (existing) { + if (existing.paymentId !== payment.id) { + return { + ok: false, + status: 409, + message: 'That Idempotency-Key was already used for a different payment.', + }; + } + const current = await db.payment.findUnique({ + where: { id: payment.id }, + select: { state: true }, + }); + return { + ok: true, + status: 200, + body: { + state: current?.state ?? payment.state, + changed: false, + note: 'This settlement request was already accepted.', + transaction: { + id: existing.id, + status: existing.status, + hash: existing.hash, + attempt: existing.attempt, + }, + }, + }; + } + + const outcome = await transitionPayment(db, { + paymentId: payment.id, + to: PaymentState.SUBMITTING, + actor: actorFromMembership(membership), + orgId: membership.orgId, + metadata: { action: 'submit', idempotencyKey: ctx.idempotencyKey }, + }); + if (!outcome.ok) { + return { ok: false, status: outcome.status, message: outcome.message, code: outcome.code }; + } + + const priorAttempts = await db.blockchainTransaction.count({ + where: { paymentId: payment.id, kind: TxKind.PAY_BATCH }, + }); + + const tx = await db.blockchainTransaction.create({ + data: { + orgId: membership.orgId, + paymentId: payment.id, + escrowId: payment.escrowId, + kind: TxKind.PAY_BATCH, + status: TxStatus.PREPARING, + idempotencyKey: ctx.idempotencyKey, + attempt: priorAttempts + 1, + contractId: payment.assetContractId ?? null, + }, + }); + + return outcomeToResult(outcome, { + transaction: { id: tx.id, status: tx.status, attempt: tx.attempt }, + }); +} + +/** + * Retry a payment whose submission never reached the chain. + * + * Only valid from SUBMISSION_FAILED. A payment in SETTLEMENT_FAILED reached the + * chain, and what it did there must be established by reconciliation before + * anything is resubmitted β€” so this refuses, with an explanation, rather than + * silently doing something riskier than the caller asked for. + */ +export async function retryPayment(ctx: ActionContext): Promise { + const { db, membership } = ctx; + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + const payment = found.value; + + if (payment.state === PaymentState.SETTLEMENT_FAILED) { + return { + ok: false, + status: 409, + message: + 'This transaction reached Stellar and failed there. Run reconciliation ' + + 'to establish what the chain actually did before retrying.', + code: 'RECONCILIATION_FIRST', + }; + } + + return outcomeToResult( + await transitionPayment(db, { + paymentId: payment.id, + to: PaymentState.READY_TO_SETTLE, + actor: actorFromMembership(membership), + orgId: membership.orgId, + reason: ctx.reason ?? 'Retried after a failed submission.', + metadata: { action: 'retry' }, + }) + ); +} + +/** Mark a payment as needing operator reconciliation. */ +export async function flagForReconciliation(ctx: ActionContext): Promise { + const { db, membership } = ctx; + if (membership.role !== OrgRole.OWNER && membership.role !== OrgRole.ADMIN) { + return { + ok: false, + status: 403, + message: 'Only an organization owner or admin can flag a payment for reconciliation.', + }; + } + + const found = await findPaymentForMember(db, membership, ctx.paymentId); + if (!found.ok) return { ok: false, status: found.status, message: found.message }; + + const actor: Actor = { kind: 'reconciler', system: 'reconciler', address: membership.walletAddress }; + const outcome = await transitionPayment(db, { + paymentId: found.value.id, + to: PaymentState.RECONCILIATION_REQUIRED, + actor, + orgId: membership.orgId, + reason: ctx.reason ?? 'Flagged for reconciliation by an operator.', + metadata: { action: 'reconcile' }, + }); + return outcomeToResult(outcome); +} diff --git a/src/lib/payments/authz.ts b/src/lib/payments/authz.ts new file mode 100644 index 0000000..c3aeee8 --- /dev/null +++ b/src/lib/payments/authz.ts @@ -0,0 +1,70 @@ +/** + * Payment-domain view of the tenant boundary. + * + * ── This module no longer implements isolation ─────────────────────────────── + * It delegates to `src/lib/tenancy/`, which is the single boundary every + * tenant-scoped request passes through. Two implementations of "is this record + * mine" is one more than a security boundary can afford: they drift, and the + * weaker one becomes the way in. + * + * What remains here is the payment domain's vocabulary β€” a `Membership` alias and + * helpers that turn a tenant context into a state-machine actor β€” so the payment + * service does not need to know how tenancy is resolved. + */ + +import { OrgRole } from '@prisma/client'; +import { can } from '@/lib/tenancy/rbac'; +import { + resolveTenant, + findPayment as findPaymentScoped, + findBatch as findBatchScoped, + type TenantContext, + type Result, +} from '@/lib/tenancy/resolve'; +import type { Actor } from './state-machine'; + +/** A resolved, active membership. Named for the payment domain's call sites. */ +export type Membership = TenantContext; + +export type AuthzResult = Result; + +/** Roles that may read an organization's payment data. Derived, not duplicated. */ +export const READ_ROLES: readonly OrgRole[] = Object.values(OrgRole).filter((r) => + can(r, 'payment:read') +); + +export async function resolveMembership( + db: any, + userId: string | undefined, + orgId: string +): Promise> { + return resolveTenant(db, userId, orgId); +} + +export async function findPaymentForMember( + db: any, + membership: Membership, + paymentId: string, + include?: Record +): Promise> { + return findPaymentScoped(db, membership, paymentId, include); +} + +export async function findBatchForMember( + db: any, + membership: Membership, + batchId: string, + include?: Record +): Promise> { + return findBatchScoped(db, membership, batchId, include); +} + +/** True if the role may read payment data organization-wide. */ +export function canRead(role: OrgRole): boolean { + return can(role, 'payment:read'); +} + +/** Build a state-machine actor from a resolved membership. */ +export function actorFromMembership(m: Membership): Actor { + return { kind: 'user', role: m.role, address: m.walletAddress }; +} diff --git a/src/lib/payments/http.ts b/src/lib/payments/http.ts new file mode 100644 index 0000000..25eff88 --- /dev/null +++ b/src/lib/payments/http.ts @@ -0,0 +1,92 @@ +/** + * Shared HTTP plumbing for payment action routes. + * + * Each action gets its own route (POST /api/payments/:id/approve, …) rather than + * one endpoint taking a status. The requirement is that users trigger business + * actions and the SERVER decides the resulting state; a single + * `PATCH {status: 'PAID'}` would invert that. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { resolveMembership } from './authz'; +import type { ActionContext, ActionResult } from './actions'; + +/** Where the caller's organization comes from, in precedence order. */ +async function resolveOrgId( + request: NextRequest, + body: any, + userId: string +): Promise { + const explicit = + request.headers.get('x-organization-id') ?? + body?.orgId ?? + new URL(request.url).searchParams.get('orgId'); + if (explicit) return String(explicit); + + // Fall back to the caller's sole membership. Ambiguity is NOT resolved by + // guessing: a user in several organizations must say which one, or a payment + // could be acted on in the wrong tenant's name. + const memberships = await prisma.orgMember.findMany({ + where: { userId }, + select: { orgId: true }, + take: 2, + }); + return memberships.length === 1 ? memberships[0].orgId : null; +} + +export async function runPaymentAction( + request: NextRequest, + paymentId: string, + action: (ctx: ActionContext) => Promise +): Promise { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const body = await request.json().catch(() => ({})); + + const orgId = await resolveOrgId(request, body, user.userId); + if (!orgId) { + return NextResponse.json( + { + error: + 'Specify the organization for this action via the X-Organization-Id ' + + 'header β€” you belong to more than one, or to none.', + }, + { status: 400 } + ); + } + + const membership = await resolveMembership(prisma, user.userId, orgId); + if (!membership.ok) { + return NextResponse.json({ error: membership.message }, { status: membership.status }); + } + + try { + const result = await action({ + db: prisma, + membership: membership.value, + paymentId, + idempotencyKey: + request.headers.get('idempotency-key') ?? body?.idempotencyKey ?? undefined, + reason: body?.reason, + }); + + if (!result.ok) { + return NextResponse.json( + { error: result.message, ...(result.code ? { code: result.code } : {}) }, + { status: result.status } + ); + } + return NextResponse.json(result.body, { status: result.status }); + } catch (e: any) { + console.error('[payment action] failed:', e?.message); + return NextResponse.json( + { error: 'The action could not be completed.' }, + { status: 500 } + ); + } +} diff --git a/src/lib/payments/reconcile.ts b/src/lib/payments/reconcile.ts new file mode 100644 index 0000000..59d7a63 --- /dev/null +++ b/src/lib/payments/reconcile.ts @@ -0,0 +1,347 @@ +/** + * Reconciliation between this database and the chain. + * + * ── The rule ───────────────────────────────────────────────────────────────── + * The chain is authoritative for settlement. Where the two disagree, the + * disagreement is RECORDED as a ReconciliationFinding β€” the losing side is not + * quietly rewritten, because overwriting it destroys the only evidence the two + * ever diverged, and that evidence is precisely what an auditor (or an incident + * review) needs. + * + * Two adjustments ARE applied automatically, because in both the chain's answer + * is unambiguous and acting on it reduces harm: + * * chain settled, database had not caught up β†’ database advanced to PAID + * * database claims PAID, chain disagrees β†’ database moved to + * RECONCILIATION_REQUIRED (never silently "unpaid": a wrong PAID is a + * false statement about money and must stop being presented as fact) + * + * Everything else is reported and left for an operator. + * + * Unlike the indexer, this reads CURRENT contract state. That is appropriate + * here: reconciliation asks "what is true now", not "what happened then". + */ + +import { PaymentState } from '@prisma/client'; +import { transitionPayment } from './service'; + +/** On-chain PaymentStatus discriminants, from the contract's #[repr(u32)] enum. */ +export const CHAIN_STATUS = { + PENDING: 0, + MANAGER_APPROVED: 1, + FINANCE_APPROVED: 2, + FINALIZED: 3, + CANCELLED: 4, +} as const; + +export interface ChainPaymentView { + index: number; + worker: string; + token: string; + amountBaseUnits: bigint; + hours: bigint; + proofVerified: boolean; + status: number; +} + +export interface ChainEscrowView { + onChainId: number; + managerApproved: boolean; + financeApproved: boolean; + cancelled: boolean; + payments: ChainPaymentView[]; +} + +export interface ReconcileDeps { + db: any; + /** Reads live contract state for an escrow. Injected so this is testable. */ + fetchChainEscrow: (onChainId: number) => Promise; +} + +export interface ReconcileReport { + escrowsChecked: number; + paymentsChecked: number; + findingsOpened: number; + advancedToPaid: number; + flaggedForReview: number; + /** Escrows whose chain state could not be read. Not treated as agreement. */ + unreadable: number; +} + +const RECONCILER = { kind: 'reconciler' as const, system: 'reconciler' }; + +/** Open a finding, unless an identical unresolved one already exists. */ +async function recordFinding( + db: any, + orgId: string, + input: { + paymentId?: string; + kind: string; + dbState?: string; + chainState?: string; + detail: string; + metadata?: Record; + } +): Promise { + // Re-running reconciliation must not multiply findings for one unchanged + // discrepancy, or the queue becomes noise and gets ignored. + const existing = await db.reconciliationFinding.findFirst({ + where: { + orgId, + paymentId: input.paymentId ?? null, + kind: input.kind as any, + resolvedAt: null, + }, + }); + if (existing) return false; + + await db.reconciliationFinding.create({ + data: { + orgId, + paymentId: input.paymentId ?? null, + kind: input.kind as any, + dbState: input.dbState ?? null, + chainState: input.chainState ?? null, + detail: input.detail, + metadata: (input.metadata ?? {}) as any, + }, + }); + return true; +} + +/** + * Reconcile every escrow belonging to an organization on a given deployment. + */ +export async function reconcileOrganization( + deps: ReconcileDeps, + orgId: string, + opts: { contractId?: string; network?: string; limit?: number } = {} +): Promise { + const { db, fetchChainEscrow } = deps; + const report: ReconcileReport = { + escrowsChecked: 0, paymentsChecked: 0, findingsOpened: 0, + advancedToPaid: 0, flaggedForReview: 0, unreadable: 0, + }; + + const escrows = await db.escrow.findMany({ + where: { + orgId, + onChainId: { not: null }, + ...(opts.contractId ? { contractId: opts.contractId } : {}), + ...(opts.network ? { network: opts.network } : {}), + }, + include: { payments: { orderBy: { onChainPaymentIndex: 'asc' } } }, + take: opts.limit ?? 200, + }); + + for (const escrow of escrows) { + report.escrowsChecked++; + + let chain: ChainEscrowView; + try { + chain = await fetchChainEscrow(escrow.onChainId); + } catch { + // An unreadable escrow is explicitly NOT recorded as agreeing. Treating a + // failed read as "all fine" is how silent drift accumulates. + report.unreadable++; + if (await recordFinding(db, orgId, { + kind: 'MISSING_ON_CHAIN', + dbState: `${escrow.payments.length} payment(s)`, + chainState: 'unreadable', + detail: + `Escrow ${escrow.onChainId} could not be read from ${escrow.network}. ` + + 'Its payments were not verified against the chain.', + metadata: { escrowId: escrow.id, onChainId: escrow.onChainId }, + })) report.findingsOpened++; + continue; + } + + const byIndex = new Map(); + for (const cp of chain.payments) byIndex.set(cp.index, cp); + + // Chain slots with no database row: the product cannot show them at all. + for (const cp of chain.payments) { + const has = escrow.payments.some( + (p: any) => p.onChainPaymentIndex === cp.index + ); + if (!has) { + if (await recordFinding(db, orgId, { + kind: 'ORPHAN_ON_CHAIN', + chainState: String(cp.status), + detail: + `Escrow ${escrow.onChainId} payment ${cp.index} exists on-chain ` + + 'but has no database row.', + metadata: { + escrowId: escrow.id, paymentIndex: cp.index, + worker: cp.worker, amountBaseUnits: cp.amountBaseUnits.toString(), + }, + })) report.findingsOpened++; + } + } + + for (const p of escrow.payments) { + report.paymentsChecked++; + const cp = p.onChainPaymentIndex === null ? undefined : byIndex.get(p.onChainPaymentIndex); + + if (!cp) { + if (await recordFinding(db, orgId, { + paymentId: p.id, + kind: 'MISSING_ON_CHAIN', + dbState: p.state, + detail: + `Payment ${p.id} references escrow ${escrow.onChainId} slot ` + + `${p.onChainPaymentIndex}, which does not exist on-chain.`, + })) report.findingsOpened++; + continue; + } + + const chainSettled = cp.status === CHAIN_STATUS.FINALIZED; + const chainCancelled = cp.status === CHAIN_STATUS.CANCELLED; + const dbSettled = p.state === PaymentState.PAID; + + // Financial identity must match regardless of state. + if (p.amountBaseUnits !== cp.amountBaseUnits) { + if (await recordFinding(db, orgId, { + paymentId: p.id, kind: 'AMOUNT_MISMATCH', + dbState: p.amountBaseUnits.toString(), + chainState: cp.amountBaseUnits.toString(), + detail: 'Recorded amount differs from the on-chain amount.', + })) report.findingsOpened++; + } + if (p.recipientAddress !== cp.worker) { + if (await recordFinding(db, orgId, { + paymentId: p.id, kind: 'RECIPIENT_MISMATCH', + dbState: p.recipientAddress, chainState: cp.worker, + detail: 'Recorded recipient differs from the on-chain recipient.', + })) report.findingsOpened++; + } + + // ── The two disagreements that matter most ── + if (chainSettled && !dbSettled) { + // The chain paid. Catch the database up β€” the money moved whatever our + // records said. + const outcome = await transitionPayment(db, { + paymentId: p.id, + to: PaymentState.PAID, + actor: RECONCILER, + orgId, + reason: 'Reconciliation: the chain reports this payment as settled.', + metadata: { + source: 'reconciliation', + chainStatus: cp.status, + escrowOnChainId: escrow.onChainId, + paymentIndex: cp.index, + }, + }); + if (outcome.ok && outcome.changed) { + report.advancedToPaid++; + } else { + // Could not advance from where it sits β€” needs a human. + if (await recordFinding(db, orgId, { + paymentId: p.id, kind: 'CHAIN_PAID_DB_NOT', + dbState: p.state, chainState: 'FINALIZED', + detail: + 'The chain reports settlement but the recorded state does not ' + + `permit PAID: ${outcome.ok ? 'already there' : outcome.message}`, + })) report.findingsOpened++; + } + continue; + } + + if (dbSettled && !chainSettled) { + // We are claiming money moved when the chain says otherwise. This is the + // worst discrepancy in the system, and it must stop being presented as + // settled β€” but it is NOT silently reverted to a healthy-looking state, + // because something caused it and that needs explaining. + if (await recordFinding(db, orgId, { + paymentId: p.id, kind: 'DB_PAID_CHAIN_NOT', + dbState: 'PAID', chainState: String(cp.status), + detail: + 'CoreFlow recorded this payment as PAID but the chain does not ' + + 'report it as settled. The payment has been moved to ' + + 'RECONCILIATION_REQUIRED and must be resolved by an operator.', + })) report.findingsOpened++; + + const outcome = await transitionPayment(db, { + paymentId: p.id, + to: PaymentState.RECONCILIATION_REQUIRED, + actor: RECONCILER, + orgId, + reason: 'Reconciliation: recorded as PAID but the chain disagrees.', + metadata: { chainStatus: cp.status }, + }); + // PAID is terminal in the state machine, so this transition is refused by + // design. The finding above is the durable record either way; we do not + // weaken the table to allow it. + if (outcome.ok && outcome.changed) report.flaggedForReview++; + continue; + } + + if (chainCancelled && p.state !== PaymentState.CANCELLED && !dbSettled) { + const outcome = await transitionPayment(db, { + paymentId: p.id, + to: PaymentState.CANCELLED, + actor: RECONCILER, + orgId, + reason: 'Reconciliation: the escrow was cancelled on-chain.', + }); + if (outcome.ok && outcome.changed) report.flaggedForReview++; + } + } + } + + return report; +} + +/** + * Cross-check transactions we recorded as failed against the chain. + * + * An RPC timeout that was reported as a failure while the transaction actually + * landed is the single most dangerous state for a payments system: it invites a + * retry that double-pays. The contract's PaymentAlreadyFinalized guard is the + * real backstop, but this finds the condition rather than waiting for it to be + * hit. + */ +export async function reconcileFailedTransactions( + deps: { db: any; fetchTxSucceeded: (hash: string) => Promise }, + orgId: string +): Promise<{ checked: number; falselyFailed: number }> { + const { db, fetchTxSucceeded } = deps; + const failed = await db.blockchainTransaction.findMany({ + where: { orgId, status: 'FAILED', hash: { not: null } }, + take: 200, + }); + + let falselyFailed = 0; + for (const tx of failed) { + let succeeded = false; + try { + succeeded = await fetchTxSucceeded(tx.hash); + } catch { + continue; // unreadable; leave it alone rather than guessing + } + if (!succeeded) continue; + + falselyFailed++; + await recordFinding(db, orgId, { + paymentId: tx.paymentId ?? undefined, + kind: 'FAILED_TX_ACTUALLY_SUCCEEDED', + dbState: 'FAILED', + chainState: 'SUCCESS', + detail: + `Transaction ${tx.hash} was recorded as failed but succeeded on-chain. ` + + 'Do not retry this payment until it is resolved.', + metadata: { transactionId: tx.id, hash: tx.hash }, + }); + await db.blockchainTransaction.update({ + where: { id: tx.id }, + data: { + status: 'CONFIRMED', + errorMessage: + 'Recorded as failed in error; the chain confirms success. ' + + 'Corrected by reconciliation.', + }, + }); + } + + return { checked: failed.length, falselyFailed }; +} diff --git a/src/lib/payments/service.ts b/src/lib/payments/service.ts new file mode 100644 index 0000000..8c52b69 --- /dev/null +++ b/src/lib/payments/service.ts @@ -0,0 +1,283 @@ +/** + * Payment transition service. + * + * Every state change in the system goes through `transitionPayment`. That is the + * point: a single chokepoint means the transition table, the audit trail, and + * the concurrency guard cannot be bypassed by a route handler that forgot one of + * them. Routes express business ACTIONS (approve, settle, retry); this decides + * the resulting state. + */ + +import { PaymentState, Prisma } from '@prisma/client'; +import { + checkTransition, + describeState, + type Actor, + type TransitionErrorCode, +} from './state-machine'; + +export interface TransitionRequest { + paymentId: string; + to: PaymentState; + actor: Actor; + /** Tenant scope. A transition is only ever applied within one organization. */ + orgId: string; + /** Operator-readable explanation, stored on the payment for failure states. */ + reason?: string; + txHash?: string; + /** Extra context recorded on the audit event. */ + metadata?: Prisma.InputJsonValue; + /** Set alongside PAID. */ + settledAt?: Date; +} + +export type TransitionOutcome = + | { ok: true; previousState: PaymentState; newState: PaymentState; changed: true } + | { ok: true; previousState: PaymentState; newState: PaymentState; changed: false; note: string } + | { + ok: false; + status: 404 | 409 | 403; + code: TransitionErrorCode | 'NOT_FOUND' | 'CONCURRENT_MODIFICATION'; + message: string; + }; + +/** + * Apply a state transition, atomically, with an audit record. + * + * Concurrency: the update is a compare-and-swap on the CURRENT state + * (`where: { id, state: from }`). Two approvals racing, or an indexer running + * while a user acts, would otherwise both read the same prior state and both + * write β€” losing one transition and its audit entry. If the swap matches zero + * rows, someone else moved the payment first and we report that rather than + * overwriting their work. + * + * Idempotency: re-requesting a transition that has ALREADY been applied returns + * `changed: false` instead of an error. An indexer re-processing an event, or a + * user double-clicking approve, is a duplicate β€” not a failure. + */ +export async function transitionPayment( + db: any, + req: TransitionRequest +): Promise { + // Opens its own transaction. Callers ALREADY inside one must use + // `applyTransition` instead: Prisma's interactive transaction client has no + // `$transaction` method, so nesting throws at runtime. + return db.$transaction((tx: any) => applyTransition(tx, req)); +} + +/** + * The transition itself, assuming an ambient transaction. + * + * Separated from {@link transitionPayment} because the indexer applies a whole + * event β€” state change, audit record and the ChainEvent marker β€” inside ONE + * transaction, so a crash cannot leave half an event applied. It therefore has a + * `tx` in hand already and must not try to open another. + */ +export async function applyTransition( + db: any, + req: TransitionRequest +): Promise { + const payment = await db.payment.findFirst({ + where: { id: req.paymentId, orgId: req.orgId }, + select: { id: true, state: true, orgId: true, batchId: true, escrowId: true }, + }); + + if (!payment) { + return { ok: false, status: 404, code: 'NOT_FOUND', message: 'Payment not found.' }; + } + + // Already there: a duplicate request, not an error. Reported distinctly so + // callers can tell "nothing to do" from "we just did it". + if (payment.state === req.to) { + return { + ok: true, + previousState: payment.state, + newState: req.to, + changed: false, + note: `Payment was already ${req.to}.`, + }; + } + + const check = checkTransition(payment.state, req.to, req.actor); + if (!check.ok) { + // A role or actor problem is a 403; anything else is a state conflict. + const status = + check.code === 'ROLE_NOT_PERMITTED' || + check.code === 'ACTOR_NOT_PERMITTED' || + check.code === 'ROLE_REQUIRED' + ? 403 + : 409; + return { ok: false, status, code: check.code, message: check.message }; + } + + const now = new Date(); + + { + const tx = db; + // Compare-and-swap: only applies if nobody changed the state since we read. + const updated = await tx.payment.updateMany({ + where: { id: payment.id, orgId: req.orgId, state: payment.state }, + data: { + state: req.to, + stateUpdatedAt: now, + stateReason: req.reason ?? null, + ...(req.txHash ? { settlementTxHash: req.txHash } : {}), + ...(req.to === PaymentState.PAID + ? { settledAt: req.settledAt ?? now } + : {}), + }, + }); + + if (updated.count === 0) { + return { + ok: false as const, + status: 409 as const, + code: 'CONCURRENT_MODIFICATION' as const, + message: + 'The payment changed state while this request was in flight. ' + + 'Re-read it and try again.', + }; + } + + await tx.auditEvent.create({ + data: { + orgId: req.orgId, + type: 'payment.state.changed', + actorAddress: req.actor.address ?? null, + actorSystem: req.actor.kind === 'user' ? null : req.actor.system ?? req.actor.kind, + paymentId: payment.id, + batchId: payment.batchId, + escrowId: payment.escrowId, + previousState: payment.state, + newState: req.to, + txHash: req.txHash ?? null, + metadata: { + actorKind: req.actor.kind, + ...(req.actor.role ? { actorRole: req.actor.role } : {}), + transitionReason: check.transition.reason, + ...(req.reason ? { operatorReason: req.reason } : {}), + ...(req.metadata && typeof req.metadata === 'object' ? req.metadata : {}), + } as Prisma.InputJsonValue, + }, + }); + + return { + ok: true as const, + previousState: payment.state, + newState: req.to, + changed: true as const, + }; + } +} + +/** + * Record an audit event that is not itself a state transition (an approval being + * requested, a reconciliation finding opened, a batch uploaded). + */ +export async function recordAuditEvent( + db: any, + input: { + orgId: string; + type: string; + actor?: Actor; + paymentId?: string; + batchId?: string; + escrowId?: string; + txHash?: string; + metadata?: Prisma.InputJsonValue; + } +): Promise { + await db.auditEvent.create({ + data: { + orgId: input.orgId, + type: input.type, + actorAddress: input.actor?.address ?? null, + actorSystem: + input.actor && input.actor.kind !== 'user' + ? input.actor.system ?? input.actor.kind + : null, + paymentId: input.paymentId ?? null, + batchId: input.batchId ?? null, + escrowId: input.escrowId ?? null, + txHash: input.txHash ?? null, + metadata: input.metadata ?? undefined, + }, + }); +} + +/** + * Derive a batch's standing from its payments. + * + * Computed, never stored. A persisted rollup is a second copy of mutable truth + * and will eventually disagree with the payments it claims to summarize β€” and + * when it does, it is the copy people have already acted on. + */ +export interface BatchRollup { + total: number; + byState: Record; + totalAmountBaseUnits: bigint; + paidAmountBaseUnits: bigint; + needsAttention: number; + /** The least-advanced meaningful state, for a single-line summary. */ + headline: string; +} + +const PROGRESS_ORDER: readonly PaymentState[] = [ + PaymentState.DRAFT, + PaymentState.VALIDATING, + PaymentState.AWAITING_ORACLE, + PaymentState.ORACLE_VERIFIED, + PaymentState.AWAITING_MANAGER, + PaymentState.AWAITING_FINANCE, + PaymentState.READY_TO_SETTLE, + PaymentState.SUBMITTING, + PaymentState.CONFIRMING, + PaymentState.PAID, +]; + +export function rollupBatch( + payments: readonly { state: PaymentState; amountBaseUnits: bigint }[] +): BatchRollup { + const byState: Record = {}; + let totalAmount = 0n; + let paidAmount = 0n; + let needsAttention = 0; + + for (const p of payments) { + byState[p.state] = (byState[p.state] ?? 0) + 1; + totalAmount += p.amountBaseUnits; + if (p.state === PaymentState.PAID) paidAmount += p.amountBaseUnits; + if (describeState(p.state).needsAttention) needsAttention++; + } + + // A batch containing anything broken is reported as broken. Summarising by the + // most common state would let a failure hide inside a mostly-healthy batch, + // which is precisely the one a finance team needs to see. + const states = payments.map((p) => p.state); + const BROKEN_STATES: readonly PaymentState[] = [ + PaymentState.RECONCILIATION_REQUIRED, + PaymentState.SETTLEMENT_FAILED, + PaymentState.SUBMISSION_FAILED, + PaymentState.EXPIRED, + ]; + const broken = states.find((s) => BROKEN_STATES.includes(s)); + + let headline: string; + if (payments.length === 0) { + headline = 'Empty'; + } else if (broken) { + headline = describeState(broken).label; + } else { + const least = PROGRESS_ORDER.find((s) => states.includes(s)); + headline = least ? describeState(least).label : describeState(states[0]).label; + } + + return { + total: payments.length, + byState, + totalAmountBaseUnits: totalAmount, + paidAmountBaseUnits: paidAmount, + needsAttention, + headline, + }; +} diff --git a/src/lib/payments/state-machine.ts b/src/lib/payments/state-machine.ts new file mode 100644 index 0000000..e54f323 --- /dev/null +++ b/src/lib/payments/state-machine.ts @@ -0,0 +1,540 @@ +/** + * CoreFlow payment state machine. + * + * ── The rule this file exists to enforce ───────────────────────────────────── + * A payment's state is a PROJECTION of chain truth, never an assertion about it. + * The single most important consequence: **only the indexer may move a payment + * to PAID**, and it does so only after observing a confirmed `payment/paid` + * event in the contract's log. No user action, API call, or optimistic UI update + * can reach PAID. `SUBMITTING` does not mean paid. `CONFIRMING` does not mean + * paid. A payroll system that lets the frontend manufacture "settled" is worse + * than one with no status at all, because it is confidently wrong. + * + * Every transition declares WHO may perform it. That is not decoration: it is + * how "the frontend cannot manufacture a successful state" becomes a property + * the type system and tests can check, rather than a convention. + * + * Documented in full in docs/PAYMENT_STATE_MACHINE.md. + */ + +import { PaymentState, OrgRole } from '@prisma/client'; + +/** + * Who is performing a transition. + * + * `indexer` and `reconciler` are distinct even though both are machines: the + * indexer reports what the log says, while the reconciler adjudicates a + * disagreement between the log and this database. Collapsing them would let + * routine ingestion silently resolve discrepancies that a human should see. + */ +export type ActorKind = 'user' | 'indexer' | 'reconciler' | 'system'; + +export interface Actor { + kind: ActorKind; + /** Organization role, required when `kind` is 'user'. */ + role?: OrgRole; + /** Wallet address, for audit attribution. */ + address?: string; + /** Names the machine actor, e.g. 'indexer' | 'reconciler' | 'validator'. */ + system?: string; +} + +export interface Transition { + from: PaymentState; + to: PaymentState; + /** Actor kinds permitted to perform this transition. */ + actors: readonly ActorKind[]; + /** When a user may do it, the org roles allowed. Empty = no user path. */ + roles?: readonly OrgRole[]; + /** Why this transition exists, in domain terms. */ + reason: string; +} + +/** Roles that can act on behalf of the organization generally. */ +const ADMINISTRATIVE: readonly OrgRole[] = [OrgRole.OWNER, OrgRole.ADMIN]; + +/** + * The two actors whose authority is "the chain says so". + * + * The indexer reads the event log; the reconciler reads live contract state. + * Both report chain truth, so any transition justified by chain evidence is + * available to both β€” and to neither user nor system. Listing only the indexer + * would leave reconciliation unable to record what it just verified, which is + * the entire point of running it. + */ +const CHAIN_OBSERVERS: readonly ActorKind[] = ['indexer', 'reconciler']; + +/** + * The complete transition table. Anything absent from this list is invalid. + * + * Deliberately exhaustive and declarative rather than a switch statement: a + * table can be enumerated by tests, rendered as a diagram, and audited by + * reading. Control flow spread across branches cannot. + */ +export const TRANSITIONS: readonly Transition[] = [ + // ── Authoring ────────────────────────────────────────────────────────────── + { + from: PaymentState.DRAFT, to: PaymentState.VALIDATING, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER], + reason: 'Submitted for validation by whoever is preparing the batch.', + }, + { + from: PaymentState.DRAFT, to: PaymentState.CANCELLED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER], + reason: 'A draft row is discarded before anything is funded.', + }, + { + from: PaymentState.VALIDATING, to: PaymentState.DRAFT, + actors: ['system'], + reason: 'Validation failed; the row returns to editable rather than stalling.', + }, + { + from: PaymentState.VALIDATING, to: PaymentState.AWAITING_ORACLE, + actors: CHAIN_OBSERVERS, + reason: + 'The escrow is funded on-chain. Only the indexer asserts this, because it ' + + 'means custody actually moved.', + }, + { + from: PaymentState.VALIDATING, to: PaymentState.REJECTED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER, OrgRole.FINANCE], + reason: 'Declined during review, before funding.', + }, + { + from: PaymentState.VALIDATING, to: PaymentState.CANCELLED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER], + reason: 'Withdrawn during review.', + }, + + // ── Oracle ───────────────────────────────────────────────────────────────── + { + from: PaymentState.AWAITING_ORACLE, to: PaymentState.ORACLE_VERIFIED, + actors: CHAIN_OBSERVERS, + reason: + 'A `hours/submit` event was observed, meaning the contract ACCEPTED an ' + + 'Ed25519 attestation for this payment. Requesting an attestation is not ' + + 'the same as the chain verifying one.', + }, + { + from: PaymentState.AWAITING_ORACLE, to: PaymentState.CANCELLED, + actors: CHAIN_OBSERVERS, reason: 'The escrow was cancelled on-chain; custody refunded.', + }, + { + from: PaymentState.AWAITING_ORACLE, to: PaymentState.EXPIRED, + actors: ['system'], reason: 'The attestation window lapsed without a proof.', + }, + { + from: PaymentState.ORACLE_VERIFIED, to: PaymentState.AWAITING_MANAGER, + actors: ['system', 'indexer'], + reason: 'Proof in hand; the payment enters the approval chain.', + }, + { + from: PaymentState.ORACLE_VERIFIED, to: PaymentState.CANCELLED, + actors: CHAIN_OBSERVERS, reason: 'The escrow was cancelled on-chain.', + }, + + // ── Dual approval ────────────────────────────────────────────────────────── + { + from: PaymentState.AWAITING_MANAGER, to: PaymentState.AWAITING_FINANCE, + actors: CHAIN_OBSERVERS, + reason: + 'An `approve/manager` event was observed. The approval is the on-chain ' + + 'signature, not the API call that prompted it.', + }, + { + from: PaymentState.AWAITING_MANAGER, to: PaymentState.REJECTED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER], + reason: 'The manager declined.', + }, + { + from: PaymentState.AWAITING_MANAGER, to: PaymentState.CANCELLED, + actors: CHAIN_OBSERVERS, reason: 'The escrow was cancelled on-chain.', + }, + { + from: PaymentState.AWAITING_MANAGER, to: PaymentState.EXPIRED, + actors: ['system'], reason: 'The approval window lapsed.', + }, + { + from: PaymentState.AWAITING_FINANCE, to: PaymentState.READY_TO_SETTLE, + actors: CHAIN_OBSERVERS, + reason: 'An `approve/finance` event was observed from the distinct finance key.', + }, + { + from: PaymentState.AWAITING_FINANCE, to: PaymentState.REJECTED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.FINANCE], + reason: + 'Finance declined. MANAGER is absent here on purpose: a manager who could ' + + 'exercise the finance decision would collapse the separation of duties.', + }, + { + from: PaymentState.AWAITING_FINANCE, to: PaymentState.CANCELLED, + actors: CHAIN_OBSERVERS, reason: 'The escrow was cancelled on-chain.', + }, + { + from: PaymentState.AWAITING_FINANCE, to: PaymentState.EXPIRED, + actors: ['system'], reason: 'The approval window lapsed.', + }, + + // ── Settlement ───────────────────────────────────────────────────────────── + { + from: PaymentState.READY_TO_SETTLE, to: PaymentState.SUBMITTING, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER, OrgRole.FINANCE], + reason: 'A settlement transaction is being built and signed.', + }, + { + from: PaymentState.READY_TO_SETTLE, to: PaymentState.CANCELLED, + actors: CHAIN_OBSERVERS, reason: 'The escrow was cancelled before settlement.', + }, + { + from: PaymentState.SUBMITTING, to: PaymentState.CONFIRMING, + actors: ['system'], + reason: 'The network accepted the transaction; it awaits ledger close.', + }, + { + from: PaymentState.SUBMITTING, to: PaymentState.SUBMISSION_FAILED, + actors: ['system'], + reason: + 'The transaction never reached the network (build, simulate, sign or RPC ' + + 'failure). Nothing was submitted, so a retry cannot double-pay.', + }, + { + from: PaymentState.CONFIRMING, to: PaymentState.PAID, + actors: CHAIN_OBSERVERS, + reason: + 'A confirmed `payment/paid` event was observed in the contract log, which ' + + 'the contract emits only after the SAC transfer for that payee succeeded.', + }, + { + from: PaymentState.READY_TO_SETTLE, to: PaymentState.PAID, + actors: CHAIN_OBSERVERS, + reason: + 'Settled without this application driving the submission β€” by the CLI, a ' + + 'validation script, or another client. The chain is authoritative for ' + + 'settlement, so a `payment/paid` event is accepted from an approved ' + + 'payment even though we never recorded a SUBMITTING step. Refusing would ' + + 'strand every externally-settled payment in RECONCILIATION_REQUIRED, which ' + + 'is noise rather than safety.', + }, + { + from: PaymentState.SUBMITTING, to: PaymentState.PAID, + actors: CHAIN_OBSERVERS, + reason: + 'Confirmation arrived before our own SUBMITTING β†’ CONFIRMING update landed. ' + + 'A real race, and the log is the side that knows.', + }, + { + from: PaymentState.CONFIRMING, to: PaymentState.SETTLEMENT_FAILED, + actors: ['indexer', 'system'], + reason: 'The transaction reached the chain and failed there.', + }, + { + from: PaymentState.CONFIRMING, to: PaymentState.RECONCILIATION_REQUIRED, + actors: ['reconciler', 'system'], + reason: + 'Confirmation timed out or the result was ambiguous. The outcome is ' + + 'genuinely unknown, and saying so beats guessing either way.', + }, + + // ── Recovery ─────────────────────────────────────────────────────────────── + { + from: PaymentState.SUBMISSION_FAILED, to: PaymentState.READY_TO_SETTLE, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER, OrgRole.FINANCE], + reason: + 'Retry. Safe without reconciliation precisely because nothing reached the ' + + 'chain; the approvals that authorized it are still on-chain and intact.', + }, + { + from: PaymentState.SUBMISSION_FAILED, to: PaymentState.CANCELLED, + actors: ['user'], roles: [...ADMINISTRATIVE, OrgRole.MANAGER], + reason: 'Abandoned after a failed submission.', + }, + { + from: PaymentState.SUBMISSION_FAILED, to: PaymentState.RECONCILIATION_REQUIRED, + actors: ['reconciler'], + reason: + 'Reconciliation found chain activity for a submission we recorded as ' + + 'never sent β€” our record of "never submitted" was wrong.', + }, + { + from: PaymentState.SETTLEMENT_FAILED, to: PaymentState.RECONCILIATION_REQUIRED, + actors: ['reconciler', 'system'], + reason: 'Establish what the chain actually did before anything is retried.', + }, + { + from: PaymentState.SETTLEMENT_FAILED, to: PaymentState.PAID, + actors: CHAIN_OBSERVERS, + reason: + 'A `payment/paid` event arrived for a payment we had recorded as failed. ' + + 'The log wins: our failure record was wrong.', + }, + { + from: PaymentState.SETTLEMENT_FAILED, to: PaymentState.READY_TO_SETTLE, + actors: ['reconciler'], + reason: + 'Reconciliation confirmed the chain did NOT settle. Deliberately not a ' + + 'user transition: retrying a transaction that reached the chain requires ' + + 'first establishing what it did, and a human clicking retry has not.', + }, + + // ── Reconciliation outcomes ──────────────────────────────────────────────── + { + from: PaymentState.RECONCILIATION_REQUIRED, to: PaymentState.PAID, + actors: ['reconciler', 'indexer'], + reason: 'Chain evidence confirms settlement.', + }, + { + from: PaymentState.RECONCILIATION_REQUIRED, to: PaymentState.READY_TO_SETTLE, + actors: ['reconciler'], + reason: 'Chain evidence confirms no settlement occurred; approvals still stand.', + }, + { + from: PaymentState.RECONCILIATION_REQUIRED, to: PaymentState.SETTLEMENT_FAILED, + actors: ['reconciler'], + reason: 'Chain evidence confirms the settlement attempt failed.', + }, + { + from: PaymentState.RECONCILIATION_REQUIRED, to: PaymentState.CANCELLED, + actors: ['user', 'reconciler'], roles: ADMINISTRATIVE, + reason: 'An administrator closes out an unrecoverable payment.', + }, +]; + +/** States from which no further transition is defined. */ +export const TERMINAL_STATES: readonly PaymentState[] = [ + PaymentState.PAID, + PaymentState.REJECTED, + PaymentState.CANCELLED, + PaymentState.EXPIRED, +]; + +export function isTerminal(state: PaymentState): boolean { + return TERMINAL_STATES.includes(state); +} + +/** Every transition defined out of `state`. */ +export function transitionsFrom(state: PaymentState): readonly Transition[] { + return TRANSITIONS.filter((t) => t.from === state); +} + +export function findTransition( + from: PaymentState, + to: PaymentState +): Transition | undefined { + return TRANSITIONS.find((t) => t.from === from && t.to === to); +} + +export type TransitionCheck = + | { ok: true; transition: Transition } + | { ok: false; code: TransitionErrorCode; message: string }; + +export type TransitionErrorCode = + | 'SAME_STATE' + | 'TERMINAL' + | 'INVALID_TRANSITION' + | 'ACTOR_NOT_PERMITTED' + | 'ROLE_NOT_PERMITTED' + | 'ROLE_REQUIRED'; + +/** + * Decide whether `actor` may move a payment from `from` to `to`. + * + * Returns a result rather than throwing, so callers can map the code onto an + * HTTP status and an operator-readable message instead of a generic 500. + */ +export function checkTransition( + from: PaymentState, + to: PaymentState, + actor: Actor +): TransitionCheck { + if (from === to) { + return { + ok: false, + code: 'SAME_STATE', + message: `Payment is already ${from}.`, + }; + } + + if (isTerminal(from)) { + return { + ok: false, + code: 'TERMINAL', + message: `${from} is a terminal state; a payment cannot leave it.`, + }; + } + + const transition = findTransition(from, to); + if (!transition) { + return { + ok: false, + code: 'INVALID_TRANSITION', + message: `${from} β†’ ${to} is not a defined transition.`, + }; + } + + if (!transition.actors.includes(actor.kind)) { + return { + ok: false, + code: 'ACTOR_NOT_PERMITTED', + message: + `${from} β†’ ${to} may only be performed by ` + + `${transition.actors.join(' or ')}, not ${actor.kind}.`, + }; + } + + if (actor.kind === 'user') { + const allowed = transition.roles ?? []; + if (allowed.length === 0) { + return { + ok: false, + code: 'ROLE_NOT_PERMITTED', + message: `${from} β†’ ${to} has no user-initiated path.`, + }; + } + if (!actor.role) { + return { + ok: false, + code: 'ROLE_REQUIRED', + message: 'An organization role is required to act as a user.', + }; + } + if (!allowed.includes(actor.role)) { + return { + ok: false, + code: 'ROLE_NOT_PERMITTED', + message: + `${from} β†’ ${to} requires one of ${allowed.join(', ')}; ` + + `you hold ${actor.role}.`, + }; + } + } + + return { ok: true, transition }; +} + +export class InvalidTransitionError extends Error { + constructor( + readonly code: TransitionErrorCode, + message: string + ) { + super(message); + this.name = 'InvalidTransitionError'; + } +} + +/** Throwing form of {@link checkTransition}, for service-layer call sites. */ +export function assertTransition( + from: PaymentState, + to: PaymentState, + actor: Actor +): Transition { + const result = checkTransition(from, to, actor); + if (!result.ok) throw new InvalidTransitionError(result.code, result.message); + return result.transition; +} + +// ── Presentation ───────────────────────────────────────────────────────────── + +export interface StateDescriptor { + /** Label for operators. Never a generic "Processing". */ + label: string; + /** One line explaining what is actually true right now. */ + description: string; + /** Visual family, for consistent treatment across the UI. */ + tone: 'neutral' | 'progress' | 'pending' | 'success' | 'warning' | 'danger'; + /** + * Whether a settlement transaction plausibly exists in this state. The UI + * shows transaction details only when this is true AND a hash is present β€” + * rendering an explorer link for a payment that was never submitted invites a + * reader to believe something settled. + */ + mayHaveTransaction: boolean; + /** Whether an operator needs to act. */ + needsAttention: boolean; +} + +export const STATE_DESCRIPTORS: Record = { + [PaymentState.DRAFT]: { + label: 'Draft', + description: 'Not yet submitted. Still editable.', + tone: 'neutral', mayHaveTransaction: false, needsAttention: false, + }, + [PaymentState.VALIDATING]: { + label: 'Validating', + description: 'Checking recipient, amount, asset and hours.', + tone: 'progress', mayHaveTransaction: false, needsAttention: false, + }, + [PaymentState.AWAITING_ORACLE]: { + label: 'Awaiting oracle verification', + description: 'Funded on-chain. Waiting for a signed work attestation.', + tone: 'pending', mayHaveTransaction: false, needsAttention: false, + }, + [PaymentState.ORACLE_VERIFIED]: { + label: 'Work verified', + description: 'The contract accepted the oracle attestation for this payment.', + tone: 'progress', mayHaveTransaction: true, needsAttention: false, + }, + [PaymentState.AWAITING_MANAGER]: { + label: 'Awaiting manager approval', + description: 'Needs the manager’s on-chain signature.', + tone: 'pending', mayHaveTransaction: true, needsAttention: true, + }, + [PaymentState.AWAITING_FINANCE]: { + label: 'Awaiting finance approval', + description: 'Manager approved. Needs the separate finance signature.', + tone: 'pending', mayHaveTransaction: true, needsAttention: true, + }, + [PaymentState.READY_TO_SETTLE]: { + label: 'Ready to settle', + description: 'Both approvals are on-chain. Settlement can be submitted.', + tone: 'progress', mayHaveTransaction: true, needsAttention: true, + }, + [PaymentState.SUBMITTING]: { + label: 'Submitting to Stellar', + description: 'Building and signing the settlement transaction. Not yet paid.', + tone: 'progress', mayHaveTransaction: true, needsAttention: false, + }, + [PaymentState.CONFIRMING]: { + label: 'Confirming on Stellar', + description: 'Submitted to the network. Awaiting ledger confirmation β€” not yet paid.', + tone: 'progress', mayHaveTransaction: true, needsAttention: false, + }, + [PaymentState.PAID]: { + label: 'Paid', + description: 'Settled on-chain and confirmed. Funds reached the recipient.', + tone: 'success', mayHaveTransaction: true, needsAttention: false, + }, + [PaymentState.REJECTED]: { + label: 'Rejected', + description: 'An approver declined this payment.', + tone: 'danger', mayHaveTransaction: false, needsAttention: false, + }, + [PaymentState.CANCELLED]: { + label: 'Cancelled', + description: 'Cancelled before settlement. Escrowed funds were refunded.', + tone: 'neutral', mayHaveTransaction: true, needsAttention: false, + }, + [PaymentState.EXPIRED]: { + label: 'Expired', + description: 'The approval or attestation window lapsed before settlement.', + tone: 'warning', mayHaveTransaction: false, needsAttention: true, + }, + [PaymentState.SUBMISSION_FAILED]: { + label: 'Submission failed', + description: 'The transaction never reached Stellar. Safe to retry.', + tone: 'danger', mayHaveTransaction: false, needsAttention: true, + }, + [PaymentState.SETTLEMENT_FAILED]: { + label: 'Settlement failed', + description: 'The transaction reached Stellar and failed. Needs reconciliation before retry.', + tone: 'danger', mayHaveTransaction: true, needsAttention: true, + }, + [PaymentState.RECONCILIATION_REQUIRED]: { + label: 'Reconciliation required', + description: 'CoreFlow’s records and the chain disagree. An operator must resolve it.', + tone: 'danger', mayHaveTransaction: true, needsAttention: true, + }, +}; + +export function describeState(state: PaymentState): StateDescriptor { + return STATE_DESCRIPTORS[state]; +} diff --git a/src/lib/payroll/__tests__/batches.test.ts b/src/lib/payroll/__tests__/batches.test.ts new file mode 100644 index 0000000..92e9ac5 --- /dev/null +++ b/src/lib/payroll/__tests__/batches.test.ts @@ -0,0 +1,453 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { OrgRole, PaymentState } from '@prisma/client'; +import { createFakeDb, seedOrg, seedMember, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { + createDraftBatch, + deriveBatchPeriod, + findDuplicateUpload, + checksumCsv, + DUPLICATE_UPLOAD_WINDOW_MS, +} from '../batches'; +import { parsePayrollCsv, type ParsedPayrollRow } from '../csv'; +import type { TenantContext } from '@/lib/tenancy/resolve'; + +const ORG = 'org_test'; +const OTHER_ORG = 'org_other'; + +const ASSET = { code: 'USDC', contractId: 'CUSDC', decimals: 7 }; + +function ctxFor(role: OrgRole = OrgRole.ADMIN): TenantContext { + return { + orgId: ORG, + orgName: 'Test Org', + orgSlug: 'test-org', + userId: 'usr_admin', + walletAddress: 'GADMIN', + role, + }; +} + +function addr(tag: string): string { + return ('G' + tag.toUpperCase().replace(/[^A-Z2-7]/g, '')).padEnd(56, 'A'); +} + +/** Rows built the way production builds them: through the real CSV parser. */ +function rowsFrom(...lines: string[]): ParsedPayrollRow[] { + const result = parsePayrollCsv( + ['recipient,amount,asset,hours,rate,period_start,period_end,reference', ...lines].join('\n'), + ); + if (result.issues.length > 0) { + throw new Error('fixture did not parse: ' + JSON.stringify(result.issues)); + } + return result.rows; +} + +const THREE_ROWS = () => + rowsFrom( + `${addr('alice')},1000,USDC,40,25,2026-09-01,2026-09-15,Sprint 14`, + `${addr('bob')},1600,USDC,80,20,2026-09-01,2026-09-30,Sprint 14`, + `${addr('carol')},260,USDC,20,13,2026-08-20,2026-09-10,`, + ); + +describe('deriveBatchPeriod', () => { + it('spans the earliest start and the latest end', () => { + const { periodStart, periodEnd } = deriveBatchPeriod(THREE_ROWS()); + expect(periodStart?.toISOString()).toBe('2026-08-20T00:00:00.000Z'); + expect(periodEnd?.toISOString()).toBe('2026-09-30T00:00:00.000Z'); + }); + + it('is null when a row carries no period', () => { + // Unreachable through the parser now that period_start/period_end are required + // columns, but deriveBatchPeriod is also used on payments loaded from the + // database β€” including drafts created before the period became mandatory. + const rows = THREE_ROWS().map((r) => ({ ...r, periodStart: null, periodEnd: null })); + expect(deriveBatchPeriod(rows)).toEqual({ periodStart: null, periodEnd: null }); + }); +}); + +describe('createDraftBatch', () => { + let db: FakeDb; + + beforeEach(() => { + db = createFakeDb(); + seedOrg(db, ORG); + seedOrg(db, OTHER_ORG); + seedMember(db, ORG, 'usr_admin', 'ADMIN', 'GADMIN'); + }); + + it('creates one DRAFT payment per row, never an aggregate', async () => { + const rows = THREE_ROWS(); + const result = await createDraftBatch(db, ctxFor(), { rows, asset: ASSET }); + + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.created).toBe(true); + expect(result.batch.paymentCount).toBe(3); + + const payments = db.__tables.payment.rows; + expect(payments).toHaveLength(3); + expect(payments.every((p) => p.state === PaymentState.DRAFT)).toBe(true); + expect(payments.every((p) => p.batchId === result.batch.id)).toBe(true); + expect(payments.every((p) => p.orgId === ORG)).toBe(true); + // Each payee keeps their own figure. No row is merged into another. + expect(payments.map((p) => p.amountBaseUnits).sort()).toEqual( + [10_000_000_000n, 16_000_000_000n, 2_600_000_000n].sort(), + ); + expect(result.batch.totalBaseUnits).toBe(28_600_000_000n); + }); + + it('carries each row own period, rate, hours and reference onto its payment', async () => { + const rows = THREE_ROWS(); + await createDraftBatch(db, ctxFor(), { rows, asset: ASSET }); + + const alice = db.__tables.payment.rows.find((p) => p.recipientAddress === addr('alice')); + expect(alice).toBeDefined(); + expect(alice!.rateBaseUnits).toBe(250_000_000n); + expect(alice!.hours).toBe(40n); + expect(alice!.periodEnd.toISOString()).toBe('2026-09-15T00:00:00.000Z'); + expect(alice!.sourceReference).toBe('Sprint 14'); + expect(alice!.assetCode).toBe('USDC'); + expect(alice!.assetContractId).toBe('CUSDC'); + expect(alice!.assetDecimals).toBe(7); + + // An absent reference stays absent rather than becoming an empty string. + const carol = db.__tables.payment.rows.find((p) => p.recipientAddress === addr('carol')); + expect(carol!.sourceReference).toBeNull(); + }); + + it('stores a neutralized reference, so a formula cannot reach a spreadsheet', async () => { + const rows = rowsFrom(`${addr('inj')},100,USDC,10,10,2026-09-01,2026-09-15,"=SUM(A1:A9)"`); + await createDraftBatch(db, ctxFor(), { rows, asset: ASSET }); + expect(db.__tables.payment.rows[0].sourceReference).toBe("'=SUM(A1:A9)"); + }); + + it('refuses an empty batch', async () => { + const result = await createDraftBatch(db, ctxFor(), { rows: [], asset: ASSET }); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(result.status).toBe(400); + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + }); + + it('records provenance and a batch-level audit event with string money', async () => { + const rows = THREE_ROWS(); + const text = 'whatever the file was'; + await createDraftBatch(db, ctxFor(OrgRole.FINANCE), { + rows, + asset: ASSET, + sourceFilename: 'september.csv', + sourceRowCount: 5, + sourceChecksum: checksumCsv(text), + }); + + const batch = db.__tables.payrollBatch.rows[0]; + expect(batch.sourceFilename).toBe('september.csv'); + // Rows SEEN, not payments created: two rows were rejected before this point. + expect(batch.sourceRowCount).toBe(5); + expect(batch.uploadedBy).toBe('usr_admin'); + + const events = db.__tables.auditEvent.rows; + expect(events).toHaveLength(1); + expect(events[0].type).toBe('payroll.batch.created'); + expect(events[0].batchId).toBe(batch.id); + expect(events[0].actorAddress).toBe('GADMIN'); + expect(events[0].metadata.paymentCount).toBe(3); + // Money crosses into JSON as a string. A Number here would be the rounding + // this codebase refuses everywhere else. + expect(events[0].metadata.totalBaseUnits).toBe('28600000000'); + expect(typeof events[0].metadata.totalBaseUnits).toBe('string'); + }); + + describe('references', () => { + it('generates sequential references per organization', async () => { + const a = await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + const b = await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + expect(a.ok && a.batch.reference).toBe('CF-00001'); + expect(b.ok && b.batch.reference).toBe('CF-00002'); + }); + + it('accepts a caller-supplied reference', async () => { + const result = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + reference: 'SEPT-2026/A', + }); + expect(result.ok && result.batch.reference).toBe('SEPT-2026/A'); + }); + + it('trims surrounding whitespace on the reference', async () => { + // A reference is a human-entered LABEL, not a monetary value. Trimming it + // is conventional and loses nothing; the no-silent-mutation rule protects + // amounts, hours and rates, which are never adjusted. + const result = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + reference: ' SEPT-2026 ', + }); + expect(result.ok && result.batch.reference).toBe('SEPT-2026'); + }); + + it.each(['=cmd', 'semi;colon', '-dash-start', 'a'.repeat(70)])( + 'rejects the malformed reference "%s"', + async (reference) => { + const result = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + reference, + }); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(result.code).toBe('INVALID_REFERENCE'); + }, + ); + + it('reports a caller-supplied reference that is already taken', async () => { + await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + reference: 'SEPT-2026', + }); + const again = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + reference: 'SEPT-2026', + }); + expect(again.ok).toBe(false); + if (again.ok) return; + expect(again.status).toBe(409); + expect(again.code).toBe('REFERENCE_TAKEN'); + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + }); + + it('retries past a generated reference that another batch already holds', async () => { + // A batch created by hand occupying the number the counter will propose. + db.__tables.payrollBatch.rows.push({ + id: 'bat_manual', + orgId: ORG, + reference: 'CF-00001', + createdAt: new Date(), + }); + const result = await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + expect(result.ok).toBe(true); + if (!result.ok) return; + // The count is 1, so CF-00002 is proposed first and is free. + expect(result.batch.reference).toBe('CF-00002'); + }); + + it('scopes references to the organization, so two tenants can both use CF-00001', async () => { + await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + const other = await createDraftBatch( + db, + { ...ctxFor(), orgId: OTHER_ORG }, + { rows: THREE_ROWS(), asset: ASSET }, + ); + expect(other.ok && other.batch.reference).toBe('CF-00001'); + expect(db.__tables.payrollBatch.rows).toHaveLength(2); + }); + }); + + describe('idempotency', () => { + it('replays the first outcome instead of creating a second payroll', async () => { + const key = 'idem-september'; + const first = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + idempotencyKey: key, + }); + const second = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + idempotencyKey: key, + }); + + expect(first.ok && first.created).toBe(true); + expect(second.ok && second.created).toBe(false); + expect(first.ok && second.ok && second.batch.id).toBe(first.ok ? first.batch.id : ''); + // The property that matters: three payments exist, not six. + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + expect(db.__tables.payment.rows).toHaveLength(3); + expect(second.ok && second.batch.paymentCount).toBe(3); + expect(second.ok && second.batch.totalBaseUnits).toBe(28_600_000_000n); + }); + + it('survives losing the race, where the pre-check misses and the insert collides', async () => { + const key = 'idem-race'; + await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + idempotencyKey: key, + }); + + // Simulate the race window: the duplicate-check read happens BEFORE the + // competing transaction commits, so it sees nothing and we proceed to + // insert. Only the unique index stops a second payroll. + const realFindFirst = db.payrollBatch.findFirst; + let blinded = true; + db.payrollBatch.findFirst = async (args: any) => { + if (blinded) { + blinded = false; + return null; + } + return realFindFirst(args); + }; + + const second = await createDraftBatch(db, ctxFor(), { + rows: THREE_ROWS(), + asset: ASSET, + idempotencyKey: key, + }); + db.payrollBatch.findFirst = realFindFirst; + + expect(second.ok).toBe(true); + if (!second.ok) return; + expect(second.created).toBe(false); + expect(second.note).toContain('concurrently'); + expect(db.__tables.payrollBatch.rows).toHaveLength(1); + expect(db.__tables.payment.rows).toHaveLength(3); + }); + + it('does not tie unkeyed requests together', async () => { + await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + // NULL idempotency keys are distinct in Postgres, and must stay distinct + // here: two deliberate payrolls are not a double-submit. + expect(db.__tables.payrollBatch.rows).toHaveLength(2); + expect(db.__tables.payment.rows).toHaveLength(6); + }); + + it('scopes the key to the organization', async () => { + const key = 'shared-key'; + await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET, idempotencyKey: key }); + const other = await createDraftBatch( + db, + { ...ctxFor(), orgId: OTHER_ORG }, + { rows: THREE_ROWS(), asset: ASSET, idempotencyKey: key }, + ); + expect(other.ok && other.created).toBe(true); + expect(db.__tables.payrollBatch.rows).toHaveLength(2); + }); + }); + + describe('atomicity', () => { + it('leaves nothing behind when a payment cannot be written', async () => { + // The third payment fails. A partially created payroll would be worse than + // none: the batch would look complete and quietly underpay someone. + db.__failOn('payment', 'create', 1); + + await expect( + createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }), + ).rejects.toThrow(); + + expect(db.__tables.payrollBatch.rows).toHaveLength(0); + expect(db.__tables.payment.rows).toHaveLength(0); + expect(db.__tables.auditEvent.rows).toHaveLength(0); + }); + }); + + describe('worker linking', () => { + it('links payees that already have a worker record and counts those that do not', async () => { + db.__tables.worker.rows.push({ + id: 'wrk_alice', + orgId: ORG, + walletAddress: addr('alice'), + name: 'Alice', + }); + // Same wallet in a DIFFERENT organization must not be linked. + db.__tables.worker.rows.push({ + id: 'wrk_bob_other', + orgId: OTHER_ORG, + walletAddress: addr('bob'), + name: 'Bob elsewhere', + }); + + const result = await createDraftBatch(db, ctxFor(), { rows: THREE_ROWS(), asset: ASSET }); + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.batch.unlinkedRecipients).toBe(2); + + const byAddress = new Map( + db.__tables.payment.rows.map((p) => [p.recipientAddress, p.workerId]), + ); + expect(byAddress.get(addr('alice'))).toBe('wrk_alice'); + expect(byAddress.get(addr('bob'))).toBeNull(); + expect(byAddress.get(addr('carol'))).toBeNull(); + }); + }); +}); + +describe('findDuplicateUpload', () => { + let db: FakeDb; + const checksum = checksumCsv('recipient,amount\nGX,1'); + + beforeEach(() => { + db = createFakeDb(); + seedOrg(db, ORG); + }); + + it('finds a recent batch built from byte-identical input', async () => { + const now = new Date('2026-09-11T12:00:00Z'); + db.__tables.payrollBatch.rows.push({ + id: 'bat_recent', + orgId: ORG, + reference: 'CF-00041', + sourceChecksum: checksum, + createdAt: new Date(now.getTime() - 4 * 60 * 1000), + }); + const found = await findDuplicateUpload(db, ORG, checksum, { now }); + expect(found?.reference).toBe('CF-00041'); + }); + + it('ignores an identical upload from outside the window', async () => { + const now = new Date('2026-09-11T12:00:00Z'); + db.__tables.payrollBatch.rows.push({ + id: 'bat_old', + orgId: ORG, + reference: 'CF-00001', + sourceChecksum: checksum, + createdAt: new Date(now.getTime() - DUPLICATE_UPLOAD_WINDOW_MS - 1000), + }); + // Re-running the same payroll next period is legitimate, not a duplicate. + expect(await findDuplicateUpload(db, ORG, checksum, { now })).toBeNull(); + }); + + it('returns the most recent match when there are several', async () => { + const now = new Date('2026-09-11T12:00:00Z'); + for (const [id, ref, minutesAgo] of [ + ['bat_a', 'CF-00001', 30], + ['bat_b', 'CF-00002', 2], + ['bat_c', 'CF-00003', 10], + ] as const) { + db.__tables.payrollBatch.rows.push({ + id, + orgId: ORG, + reference: ref, + sourceChecksum: checksum, + createdAt: new Date(now.getTime() - minutesAgo * 60 * 1000), + }); + } + const found = await findDuplicateUpload(db, ORG, checksum, { now }); + expect(found?.reference).toBe('CF-00002'); + }); + + it('does not look across organizations', async () => { + db.__tables.payrollBatch.rows.push({ + id: 'bat_other', + orgId: OTHER_ORG, + reference: 'CF-00001', + sourceChecksum: checksum, + createdAt: new Date(), + }); + expect(await findDuplicateUpload(db, ORG, checksum)).toBeNull(); + }); + + it('treats an absent checksum as no evidence, not as a match', async () => { + db.__tables.payrollBatch.rows.push({ + id: 'bat_nochecksum', + orgId: ORG, + reference: 'CF-00001', + sourceChecksum: null, + createdAt: new Date(), + }); + expect(await findDuplicateUpload(db, ORG, '')).toBeNull(); + }); +}); diff --git a/src/lib/payroll/__tests__/csv.test.ts b/src/lib/payroll/__tests__/csv.test.ts new file mode 100644 index 0000000..6ce00d8 --- /dev/null +++ b/src/lib/payroll/__tests__/csv.test.ts @@ -0,0 +1,505 @@ +import { describe, it, expect } from 'vitest'; +import { + parsePayrollCsv, + parseCsvText, + sanitizeForSpreadsheet, + summarizeParse, + MAX_ROWS, + MAX_CSV_BYTES, + MAX_FIELD_LENGTH, + type CsvIssueCode, +} from '../csv'; + +/** + * Build a syntactically valid Stellar address (56 chars, base32 alphabet) with a + * recognizable tag, so a failing assertion names which recipient it meant. + */ +function addr(tag: string): string { + const body = tag.toUpperCase().replace(/[^A-Z2-7]/g, ''); + return ('G' + body).padEnd(56, 'A'); +} + +const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + +/** + * Distinct address for bulk fixtures. Fixed-width base32 so two indices can never + * encode to the same padded address - addr() alone collides, because it strips the + * digits 0, 1, 8 and 9 that are absent from the base32 alphabet. + */ +function addrN(n: number): string { + const body = BASE32[Math.floor(n / 32) % 32] + BASE32[n % 32]; + return ('G' + body).padEnd(56, 'A'); +} + +const HEADER = 'recipient,amount,asset,hours,rate,period_start,period_end'; +const PERIOD = '2026-09-01,2026-09-15'; + +/** + * Build a file from rows, supplying a pay period where the row does not state one. + * + * The period is a REQUIRED column (the oracle attests to it), so most fixtures care + * about the other five fields. A row that already carries seven fields is left + * exactly as written, which keeps the deliberately-malformed fixtures malformed. + */ +function csv(...rows: string[]): string { + const withPeriod = rows.map((r) => { + // Field count via the real parser, not by counting commas: a quoted amount + // like "$1,250.00" contains one, and a naive count silently skipped the period. + const fields = parseCsvText(r)[0]?.length ?? 0; + return fields === 5 ? `${r},${PERIOD}` : r; + }); + return [HEADER, ...withPeriod].join('\n'); +} + +/** Issue codes present, for concise assertions. */ +function codes(issues: { code: CsvIssueCode }[]): CsvIssueCode[] { + return issues.map((i) => i.code); +} + +describe('parseCsvText', () => { + it('parses quoted fields containing commas, quotes and newlines', () => { + const table = parseCsvText('a,b\n"x,1","he said ""hi"""\n"multi\nline",2'); + expect(table).toEqual([ + ['a', 'b'], + ['x,1', 'he said "hi"'], + ['multi\nline', '2'], + ]); + }); + + it('accepts CRLF and a trailing newline without inventing a row', () => { + expect(parseCsvText('a,b\r\n1,2\r\n')).toEqual([ + ['a', 'b'], + ['1', '2'], + ]); + }); + + it('strips a UTF-8 BOM so the first header name stays usable', () => { + const bom = String.fromCharCode(0xfeff); + expect(parseCsvText(bom + 'recipient,amount\nx,1')[0][0]).toBe('recipient'); + }); + + it('preserves empty trailing fields', () => { + expect(parseCsvText('a,b,c\n1,,')).toEqual([ + ['a', 'b', 'c'], + ['1', '', ''], + ]); + }); +}); + +describe('sanitizeForSpreadsheet', () => { + it.each(['=SUM(A1:A9)', '+1+1', '-2+3', '@SUM(1)'])( + 'neutralizes the formula trigger in %s', + (value) => { + expect(sanitizeForSpreadsheet(value)).toBe(`'${value}`); + }, + ); + + it('neutralizes tab- and CR-prefixed payloads', () => { + const tab = String.fromCharCode(9); + const cr = String.fromCharCode(13); + expect(sanitizeForSpreadsheet(tab + '=cmd')).toBe("'" + tab + '=cmd'); + expect(sanitizeForSpreadsheet(cr + '=cmd')).toBe("'" + cr + '=cmd'); + }); + + it('leaves ordinary text and the empty string untouched', () => { + expect(sanitizeForSpreadsheet('March sprint')).toBe('March sprint'); + expect(sanitizeForSpreadsheet('')).toBe(''); + }); +}); + +describe('parsePayrollCsv - golden path', () => { + it('produces one exact row per CSV line', () => { + const result = parsePayrollCsv( + csv( + `${addr('alice')},1000.0000000,USDC,40,25`, + `${addr('bob')},1600,USDC,80,20`, + `${addr('carol')},260,USDC,20,13`, + ), + ); + + expect(result.issues).toEqual([]); + expect(result.rowCount).toBe(3); + expect(result.rows).toHaveLength(3); + expect(result.rows.map((r) => r.line)).toEqual([2, 3, 4]); + expect(result.rows[0].amountBaseUnits).toBe(10_000_000_000n); + expect(result.rows[0].rateBaseUnits).toBe(250_000_000n); + expect(result.rows[0].hours).toBe(40n); + expect(result.totalsByAsset).toEqual({ USDC: 28_600_000_000n }); + }); + + it('keeps the smallest representable unit exactly', () => { + const result = parsePayrollCsv( + csv(`${addr('dust')},0.0000001,USDC,1,0.0000001`), + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].amountBaseUnits).toBe(1n); + }); + + it('accepts spreadsheet presentation: thousands separators, currency, asset suffix', () => { + const result = parsePayrollCsv( + csv(`${addr('pres')},"$1,250.00 USDC",USDC,50,25`), + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].amountBaseUnits).toBe(12_500_000_000n); + }); + + it('reads optional columns and parses ISO periods as UTC', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end,reference\n' + + `${addr('opt')},400,USDC,20,20,2026-09-01,2026-09-15,Sprint 14`, + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].periodStart?.toISOString()).toBe('2026-09-01T00:00:00.000Z'); + expect(result.rows[0].periodEnd?.toISOString()).toBe('2026-09-15T00:00:00.000Z'); + expect(result.rows[0].reference).toBe('Sprint 14'); + }); + + it('is case-insensitive about headers and asset codes', () => { + const result = parsePayrollCsv( + 'Recipient,Amount,ASSET,Hours,Rate,Period_Start,Period_End\n' + + `${addr('case')},100,usdc,10,10,2026-09-01,2026-09-15`, + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].asset).toBe('USDC'); + }); +}); + +describe('parsePayrollCsv - file-level limits', () => { + it('rejects an empty file', () => { + expect(codes(parsePayrollCsv('').issues)).toEqual(['FILE_EMPTY']); + expect(codes(parsePayrollCsv('\n\n \n').issues)).toEqual(['FILE_EMPTY']); + }); + + it('rejects a header with no payroll rows', () => { + expect(codes(parsePayrollCsv(HEADER).issues)).toEqual(['NO_ROWS']); + }); + + it('rejects an oversized file before parsing it', () => { + const big = HEADER + '\n' + 'x'.repeat(MAX_CSV_BYTES + 1); + const result = parsePayrollCsv(big); + expect(codes(result.issues)).toEqual(['FILE_TOO_LARGE']); + expect(result.rows).toEqual([]); + }); + + it('rejects more rows than one batch can settle', () => { + const rows = Array.from({ length: MAX_ROWS + 1 }, (_, i) => + `${addrN(i)},100,USDC,10,10`, + ); + const result = parsePayrollCsv(csv(...rows)); + expect(codes(result.issues)).toEqual(['TOO_MANY_ROWS']); + expect(result.rowCount).toBe(MAX_ROWS + 1); + expect(result.rows).toEqual([]); + }); + + it('accepts exactly the row limit', () => { + const rows = Array.from({ length: MAX_ROWS }, (_, i) => + `${addrN(i)},100,USDC,10,10`, + ); + const result = parsePayrollCsv(csv(...rows)); + expect(result.issues).toEqual([]); + expect(result.rows).toHaveLength(MAX_ROWS); + }); + + it('rejects an over-long field', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end,reference\n' + + `${addr('long')},100,USDC,10,10,2026-09-01,2026-09-15,${'a'.repeat(MAX_FIELD_LENGTH + 1)}`, + ); + expect(codes(result.issues)).toContain('FIELD_TOO_LONG'); + expect(result.rows).toEqual([]); + }); +}); + +describe('parsePayrollCsv - header validation', () => { + it('names every missing required column and stops before row errors', () => { + const result = parsePayrollCsv('recipient,amount\nGXXX,nonsense'); + // asset, hours, rate, period_start, period_end. + expect(codes(result.issues)).toEqual(Array(5).fill('MISSING_COLUMN')); + expect( + result.issues.map((i) => i.column).sort(), + ).toEqual(['asset', 'hours', 'period_end', 'period_start', 'rate']); + expect(result.issues.every((i) => i.line === 1)).toBe(true); + expect(result.rows).toEqual([]); + }); + + it('flags a duplicated column instead of silently picking one', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end,amount\n' + + `${addr('dup')},100,USDC,10,10,2026-09-01,2026-09-15,999`, + ); + expect(codes(result.issues)).toContain('DUPLICATE_COLUMN'); + }); + + it('warns when a row has a different field count than the header', () => { + const result = parsePayrollCsv(csv(`${addr('short')},100,USDC,10`)); + expect(codes(result.warnings)).toContain('WRONG_FIELD_COUNT'); + }); +}); + +describe('parsePayrollCsv - recipient validation', () => { + it.each([ + ['empty', ''], + ['too short', 'GABC'], + ['wrong prefix', 'S'.padEnd(56, 'A')], + ['lowercase', addr('alice').toLowerCase()], + ['invalid base32 digits (0, 1, 8, 9)', 'G' + '0189'.padEnd(55, 'A')], + ['57 characters', addr('alice') + 'A'], + ])('rejects an address that is %s', (_label, recipient) => { + const result = parsePayrollCsv(csv(`${recipient},100,USDC,10,10`)); + expect(codes(result.issues)).toContain('INVALID_ADDRESS'); + expect(result.rows).toEqual([]); + }); + + it('rejects a duplicate recipient and points at the first occurrence', () => { + const a = addr('alice'); + const result = parsePayrollCsv( + csv(`${a},100,USDC,10,10`, `${addr('bob')},100,USDC,10,10`, `${a},200,USDC,20,10`), + ); + const dup = result.issues.find((i) => i.code === 'DUPLICATE_RECIPIENT'); + expect(dup?.line).toBe(4); + expect(dup?.message).toContain('line 2'); + // The first two rows are still usable; only the duplicate is dropped. + expect(result.rows).toHaveLength(2); + }); + + it('allows a repeated recipient when the caller opts in', () => { + const a = addr('alice'); + const result = parsePayrollCsv(csv(`${a},100,USDC,10,10`, `${a},200,USDC,20,10`), { + rejectDuplicateRecipients: false, + }); + expect(result.issues).toEqual([]); + expect(result.rows).toHaveLength(2); + expect(result.totalsByAsset.USDC).toBe(3_000_000_000n); + }); +}); + +describe('parsePayrollCsv - money validation', () => { + it.each(['1e3', '1E3', '2.5e-2'])('rejects scientific notation %s rather than guessing', (amount) => { + const result = parsePayrollCsv(csv(`${addr('sci')},${amount},USDC,10,10`)); + expect(codes(result.issues)).toContain('AMBIGUOUS_NUMBER'); + expect(result.rows).toEqual([]); + }); + + it.each(['0', '0.0000000'])('rejects a zero amount (%s)', (amount) => { + const result = parsePayrollCsv(csv(`${addr('zero')},${amount},USDC,10,10`)); + expect(codes(result.issues)).toContain('AMOUNT_NOT_POSITIVE'); + }); + + it('rejects a negative amount', () => { + const result = parsePayrollCsv(csv(`${addr('neg')},-100,USDC,10,10`)); + expect(codes(result.issues)).toContain('AMOUNT_NOT_POSITIVE'); + }); + + it('rejects an accounting-style negative', () => { + const result = parsePayrollCsv(csv(`${addr('acct')},(100),USDC,10,10`)); + expect(codes(result.issues)).toContain('AMOUNT_NOT_POSITIVE'); + }); + + it('rejects more precision than the asset can hold, instead of truncating', () => { + const result = parsePayrollCsv(csv(`${addr('prec')},1.00000001,USDC,1,1`)); + expect(codes(result.issues)).toContain('AMOUNT_PRECISION'); + expect(result.rows).toEqual([]); + }); + + it.each(['abc', '1.2.3', '--5', '1/2', ''])('rejects the malformed amount "%s"', (amount) => { + const result = parsePayrollCsv(csv(`${addr('bad')},${amount},USDC,10,10`)); + expect(result.issues.length).toBeGreaterThan(0); + expect(result.rows).toEqual([]); + }); + + it('validates the rate with the same rules as the amount', () => { + const result = parsePayrollCsv(csv(`${addr('rate')},100,USDC,10,1e1`)); + const issue = result.issues.find((i) => i.code === 'AMBIGUOUS_NUMBER'); + expect(issue?.column).toBe('rate'); + }); +}); + +describe('parsePayrollCsv - hours and the on-chain invariant', () => { + it('rejects fractional hours and explains why, rather than rounding', () => { + const result = parsePayrollCsv(csv(`${addr('frac')},100,USDC,7.5,13.3333333`)); + const issue = result.issues.find((i) => i.code === 'FRACTIONAL_HOURS'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('will not round'); + expect(result.rows).toEqual([]); + }); + + it.each(['0', '-5', 'forty', ''])('rejects the hours value "%s"', (hours) => { + const result = parsePayrollCsv(csv(`${addr('h')},100,USDC,${hours},10`)); + expect(codes(result.issues)).toContain('INVALID_HOURS'); + }); + + it('rejects a row where amount does not equal hours x rate', () => { + const result = parsePayrollCsv(csv(`${addr('mix')},1000,USDC,40,20`)); + const issue = result.issues.find((i) => i.code === 'HOURS_RATE_MISMATCH'); + expect(issue).toBeDefined(); + // The message must show the arithmetic the contract will perform. + expect(issue?.message).toContain('800'); + expect(result.rows).toEqual([]); + }); + + it('accepts a row where the invariant holds exactly at 7 decimals', () => { + const result = parsePayrollCsv(csv(`${addr('exact')},0.0000030,USDC,3,0.0000010`)); + expect(result.issues).toEqual([]); + expect(result.rows[0].amountBaseUnits).toBe(30n); + }); +}); + +describe('parsePayrollCsv - asset validation', () => { + it.each(['BTC', 'EURC', '', 'USD'])('rejects the unsupported asset "%s"', (asset) => { + const result = parsePayrollCsv(csv(`${addr('asset')},100,${asset},10,10`)); + expect(codes(result.issues)).toContain('UNSUPPORTED_ASSET'); + expect(result.rows).toEqual([]); + }); + + it('honours a narrowed asset list, so an unsettleable code is refused', () => { + const result = parsePayrollCsv(csv(`${addr('x')},100,XLM,10,10`), { + supportedAssets: ['USDC'], + }); + expect(codes(result.issues)).toContain('UNSUPPORTED_ASSET'); + expect(result.issues[0].message).toContain('settles: USDC'); + }); + + it('warns, but does not block, when one batch mixes supported assets', () => { + const result = parsePayrollCsv( + csv(`${addr('a')},100,USDC,10,10`, `${addr('b')},50,XLM,5,10`), + ); + expect(result.issues).toEqual([]); + expect(codes(result.warnings)).toContain('MIXED_ASSETS'); + expect(result.totalsByAsset).toEqual({ USDC: 1_000_000_000n, XLM: 500_000_000n }); + }); +}); + +describe('parsePayrollCsv - period validation', () => { + it.each(['03/04/2026', '2026/09/01', 'Sept 1 2026', '2026-9-1'])( + 'rejects the ambiguous or non-ISO date "%s"', + (date) => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end\n' + + `${addr('d')},100,USDC,10,10,${date},2026-12-31`, + ); + expect(codes(result.issues)).toContain('INVALID_PERIOD'); + }, + ); + + it('rejects a date that is well-formed but not real', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end\n' + + `${addr('d')},100,USDC,10,10,2026-02-30,2026-12-31`, + ); + expect(codes(result.issues)).toContain('INVALID_PERIOD'); + }); + + it('rejects a period that ends before it starts', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end\n' + + `${addr('d')},100,USDC,10,10,2026-09-15,2026-09-01`, + ); + expect(codes(result.issues)).toContain('INVALID_PERIOD'); + expect(result.rows).toEqual([]); + }); + + it('refuses an absent period rather than assuming one', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end\n' + + `${addr('d')},100,USDC,10,10,,`, + ); + // The period is signed by the oracle, so it cannot be supplied on the + // uploader's behalf β€” and discovering that at the wallet prompt, after a + // payroll has been approved, would be far worse than being told here. + expect(codes(result.issues)).toContain('PERIOD_REQUIRED'); + expect(result.issues.some((i) => i.message.includes('oracle attests'))).toBe(true); + expect(result.rows).toEqual([]); + }); + + it('names the period columns as missing when the header omits them', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate\n' + `${addr('d')},100,USDC,10,10`, + ); + const missing = result.issues.filter((i) => i.code === 'MISSING_COLUMN'); + expect(missing.map((i) => i.column).sort()).toEqual(['period_end', 'period_start']); + }); +}); + +describe('parsePayrollCsv - hostile input', () => { + it('neutralizes a formula in a reference before it is ever stored', () => { + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end,reference\n' + + `${addr('inj')},100,USDC,10,10,2026-09-01,2026-09-15,"=HYPERLINK(""http://evil"",""click"")"`, + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].reference?.startsWith("'=")).toBe(true); + }); + + it('strips control characters from fields', () => { + const nul = String.fromCharCode(0); + const bell = String.fromCharCode(7); + const esc = String.fromCharCode(27); + const result = parsePayrollCsv( + 'recipient,amount,asset,hours,rate,period_start,period_end,reference\n' + + `${addr('ctrl')},100,USDC,10,10,2026-09-01,2026-09-15,"Sprint${nul}${bell}${esc}14"`, + ); + expect(result.issues).toEqual([]); + expect(result.rows[0].reference).toBe('Sprint14'); + }); + + it('strips control characters from a header name so the column still resolves', () => { + const bell = String.fromCharCode(7); + const result = parsePayrollCsv( + `recipient${bell},amount,asset,hours,rate,period_start,period_end\n` + + `${addr('hdr')},100,USDC,10,10,2026-09-01,2026-09-15`, + ); + expect(codes(result.issues)).not.toContain('MISSING_COLUMN'); + }); + + it('does not let an injected field smuggle a valid address past validation', () => { + const result = parsePayrollCsv(csv(`"=cmd|' /C calc'!A0",100,USDC,10,10`)); + expect(codes(result.issues)).toContain('INVALID_ADDRESS'); + expect(result.rows).toEqual([]); + }); +}); + +describe('parsePayrollCsv - reporting', () => { + it('reports every bad row in one pass, not just the first', () => { + const result = parsePayrollCsv( + csv( + `${addr('ok')},100,USDC,10,10`, + 'BADADDRESS,100,USDC,10,10', + `${addr('b')},1e3,USDC,10,10`, + `${addr('c')},100,BTC,10,10`, + `${addr('d')},100,USDC,7.5,10`, + ), + ); + expect(codes(result.issues).sort()).toEqual([ + 'AMBIGUOUS_NUMBER', + 'FRACTIONAL_HOURS', + 'INVALID_ADDRESS', + 'UNSUPPORTED_ASSET', + ]); + // Valid rows survive so the uploader can see what did parse. + expect(result.rows).toHaveLength(1); + expect(result.rowCount).toBe(5); + }); + + it('anchors every issue to the line the uploader sees', () => { + const result = parsePayrollCsv(csv(`${addr('ok')},100,USDC,10,10`, 'BAD,100,USDC,10,10')); + expect(result.issues[0].line).toBe(3); + }); + + it('summarizes a clean batch without using floating point', () => { + const summary = summarizeParse( + parsePayrollCsv( + csv(`${addr('a')},1000,USDC,40,25`, `${addr('b')},1600,USDC,80,20`), + ), + ); + expect(summary.recipientCount).toBe(2); + expect(summary.totalHours).toBe(120n); + expect(summary.totals).toEqual([{ asset: 'USDC', amount: '2600.00' }]); + expect(summary.hasBlockingIssues).toBe(false); + }); + + it('flags blocking issues in the summary', () => { + const summary = summarizeParse(parsePayrollCsv(csv('BAD,100,USDC,10,10'))); + expect(summary.hasBlockingIssues).toBe(true); + expect(summary.recipientCount).toBe(0); + }); +}); diff --git a/src/lib/payroll/api.ts b/src/lib/payroll/api.ts new file mode 100644 index 0000000..9636431 --- /dev/null +++ b/src/lib/payroll/api.ts @@ -0,0 +1,620 @@ +/** + * Orchestration for the Bulk Pay API. + * + * Routes are thin on purpose: they decode the request, call one function here, + * and render the result. Business rules live in the domain services this module + * composes β€” the CSV parser, the settlement asset registry, the batch service, + * the payment actions and the state machine. Logic duplicated in a route handler + * eventually disagrees with the service it shadows, and the route is the copy + * nobody tests against the database. + * + * Nothing here can move a payment toward settlement on its own. Approval is + * delegated to `approvePayment`, which derives the approver's role from + * membership and records the audit trail; state changes are the state machine's. + */ + +import { createHash } from 'node:crypto'; +import { OrgRole, PaymentState } from '@prisma/client'; +import { ApiError } from '@/lib/api/errors'; +import { formatAmount, formatAmountWithSeparators, SAC_DECIMALS } from '@/lib/money'; +import { approvePayment } from '@/lib/payments/actions'; +import { describeState } from '@/lib/payments/state-machine'; +import { rollupBatch } from '@/lib/payments/service'; +import type { TenantContext } from '@/lib/tenancy/resolve'; +import { parsePayrollCsv, summarizeParse, type CsvIssue, type CsvParseResult } from './csv'; +import { settlementAsset, settleableAssetCodes, type SettlementAsset } from './assets'; +import { + checksumCsv, + createDraftBatch, + findDuplicateUpload, + type CreateDraftBatchResult, +} from './batches'; +import type { FieldIssue } from './schemas'; + +// --------------------------------------------------------------------------- +// Validation +// --------------------------------------------------------------------------- + +export interface ValidationReport { + valid: boolean; + /** Blocking problems. Nothing is created while any remain. */ + errors: FieldIssue[]; + /** Worth reading, but not blocking. */ + warnings: FieldIssue[]; + summary: { + rowsSeen: number; + paymentsToCreate: number; + recipientCount: number; + totalHours: string; + totals: { asset: string; amount: string }[]; + }; + asset: { code: string; contractId: string | null; configured: boolean }; +} + +/** CSV issues carry a line number; request issues carry a field path. */ +function toFieldIssue(issue: CsvIssue): FieldIssue { + return { + ...(issue.line > 0 ? { row: issue.line } : {}), + ...(issue.column ? { field: issue.column } : {}), + code: issue.code, + message: issue.message, + }; +} + +/** + * Parse and validate an uploaded file against this deployment's settlement asset. + * + * Pure with respect to the database: it reads configuration and the file, and + * writes nothing. Safe to call on every keystroke of a preview. + */ +export function validateCsv( + csv: string, + opts: { rejectDuplicateRecipients?: boolean } = {}, +): { report: ValidationReport; parsed: CsvParseResult; asset: SettlementAsset } { + const asset = settlementAsset(); + const parsed = parsePayrollCsv(csv, { + supportedAssets: settleableAssetCodes(), + rejectDuplicateRecipients: opts.rejectDuplicateRecipients, + }); + const summary = summarizeParse(parsed); + + return { + parsed, + asset, + report: { + valid: parsed.issues.length === 0 && parsed.rows.length > 0, + errors: parsed.issues.map(toFieldIssue), + warnings: parsed.warnings.map(toFieldIssue), + summary: { + rowsSeen: parsed.rowCount, + paymentsToCreate: parsed.rows.length, + recipientCount: summary.recipientCount, + totalHours: summary.totalHours.toString(), + totals: summary.totals, + }, + asset: { + code: asset.code, + contractId: asset.contractId, + // Surfaced rather than fatal: a batch can be prepared and reviewed before + // the operator has finished wiring the asset. Funding is where it becomes + // a hard requirement. + configured: asset.contractId !== null, + }, + }, + }; +} + +// --------------------------------------------------------------------------- +// Creation +// --------------------------------------------------------------------------- + +/** + * Fingerprint the parts of a creation request that change WHAT gets created. + * + * Deliberately excludes the filename: re-uploading identical rows from + * `september-final.csv` instead of `september.csv` is the same payroll, and a + * retry should not be rejected over a renamed file. Includes the asset code, + * because the same rows settled in a different asset is a different payroll. + */ +export function fingerprintCreateRequest(input: { + csv: string; + reference?: string | null; + projectId?: string | null; + assetCode: string; + rejectDuplicateRecipients: boolean; +}): string { + return createHash('sha256') + .update( + JSON.stringify([ + checksumCsv(input.csv), + input.reference ?? null, + input.projectId ?? null, + input.assetCode, + input.rejectDuplicateRecipients, + ]), + ) + .digest('hex'); +} + +export interface CreateBatchOutcome { + created: boolean; + batch: { + id: string; + reference: string; + paymentCount: number; + periodStart: string | null; + periodEnd: string | null; + total: string; + totalBaseUnits: string; + asset: string; + unlinkedRecipients: number; + }; + warnings: FieldIssue[]; + note?: string; + /** A recent byte-identical upload, so the client can ask rather than assume. */ + possibleDuplicateOf?: { id: string; reference: string; createdAt: string }; +} + +/** + * Validate, then create a draft batch with one payment per valid row. + * + * Validation failures raise a 422 carrying every row issue, because a finance user + * fixing a 40-row file one error per upload cannot work. Nothing is written until + * the file is wholly valid. + */ +export async function createBatch( + db: any, + ctx: TenantContext, + input: { + csv: string; + filename?: string | null; + reference?: string | null; + projectId?: string | null; + rejectDuplicateRecipients?: boolean; + idempotencyKey?: string | null; + }, +): Promise { + const rejectDuplicateRecipients = input.rejectDuplicateRecipients ?? true; + const { report, parsed, asset } = validateCsv(input.csv, { rejectDuplicateRecipients }); + + if (!report.valid) { + throw new ApiError( + 422, + parsed.rows.length === 0 && parsed.issues.length === 0 ? 'VALIDATION_FAILED' : 'CSV_INVALID', + parsed.issues.length === 1 + ? 'The payroll file has a problem that must be fixed before a batch can be created.' + : `The payroll file has ${parsed.issues.length} problems that must be fixed before a batch can be created.`, + { errors: report.errors, warnings: report.warnings, summary: report.summary }, + ); + } + + const checksum = checksumCsv(input.csv); + const fingerprint = fingerprintCreateRequest({ + csv: input.csv, + reference: input.reference ?? null, + projectId: input.projectId ?? null, + assetCode: asset.code, + rejectDuplicateRecipients, + }); + + const result: CreateDraftBatchResult = await createDraftBatch(db, ctx, { + rows: parsed.rows, + asset, + reference: input.reference ?? null, + projectId: input.projectId ?? null, + sourceFilename: input.filename ?? null, + sourceRowCount: parsed.rowCount, + sourceChecksum: checksum, + idempotencyKey: input.idempotencyKey ?? null, + idempotencyFingerprint: fingerprint, + }); + + if (!result.ok) { + throw new ApiError( + result.status === 400 ? 422 : 409, + (result.code as any) ?? 'STATE_CONFLICT', + result.message, + ); + } + + // Only worth mentioning on a NEW batch. On a replay the client already has its + // answer, and "this looks like a duplicate" would describe the batch itself. + let possibleDuplicateOf: CreateBatchOutcome['possibleDuplicateOf']; + if (result.created) { + const dup = await findDuplicateUpload(db, ctx.orgId, checksum, { + excludeId: result.batch.id, + }); + if (dup) { + possibleDuplicateOf = { + id: dup.id, + reference: dup.reference, + createdAt: new Date(dup.createdAt).toISOString(), + }; + } + } + + return { + created: result.created, + batch: { + id: result.batch.id, + reference: result.batch.reference, + paymentCount: result.batch.paymentCount, + periodStart: result.batch.periodStart?.toISOString() ?? null, + periodEnd: result.batch.periodEnd?.toISOString() ?? null, + total: formatAmountWithSeparators(result.batch.totalBaseUnits, asset.decimals), + // Exact value alongside the display string, so a client never has to parse + // a formatted number back into money. + totalBaseUnits: result.batch.totalBaseUnits.toString(), + asset: asset.code, + unlinkedRecipients: result.batch.unlinkedRecipients, + }, + warnings: report.warnings, + ...(result.created ? {} : { note: result.note }), + ...(possibleDuplicateOf ? { possibleDuplicateOf } : {}), + }; +} + +// --------------------------------------------------------------------------- +// Re-validation of an existing draft +// --------------------------------------------------------------------------- + +/** + * The payment fields re-validation reads. + * + * Declared explicitly rather than taking `any`, because TypeScript types `any * + * any` as NUMBER β€” so an untyped payment would have silently turned the + * hours x rate invariant into floating-point arithmetic, in the one check whose + * entire purpose is exactness. + */ +export interface RevalidationPayment { + recipientAddress: string; + assetCode: string; + assetDecimals: number; + amountBaseUnits: bigint; + rateBaseUnits: bigint; + hours: bigint; +} + +export interface RevalidationReport { + valid: boolean; + batch: { id: string; reference: string; paymentCount: number }; + errors: FieldIssue[]; + asset: { code: string; contractId: string | null; configured: boolean }; +} + +/** + * Re-check a draft batch's payments against CURRENT configuration. + * + * A batch can be created, reviewed for a day, and funded later β€” by which time + * the configured settlement asset may have changed. This catches that before a + * wallet is opened. Read-only: it reports, and changes nothing. + */ +export async function revalidateBatch( + db: any, + ctx: TenantContext, + batch: { id: string; reference: string; payments: readonly RevalidationPayment[] }, +): Promise { + const asset = settlementAsset(); + const errors: FieldIssue[] = []; + + for (const p of batch.payments) { + if (p.assetCode !== asset.code) { + errors.push({ + field: 'asset', + code: 'ASSET_NOT_SETTLEABLE', + message: + `Payment to ${p.recipientAddress.slice(0, 8)}… is denominated in ` + + `${p.assetCode}, but this deployment now settles ${asset.code}. ` + + 'An escrow holds one asset, so this batch cannot settle as it stands.', + }); + } + // The contract enforces hours * rate == amount and refuses anything else, so + // a drifted row would fund custody that can never be released. + if (p.hours * p.rateBaseUnits !== p.amountBaseUnits) { + errors.push({ + field: 'amount', + code: 'HOURS_RATE_MISMATCH', + message: + `Payment to ${p.recipientAddress.slice(0, 8)}… has an amount of ` + + `${formatAmount(p.amountBaseUnits, p.assetDecimals)} but ${p.hours} hours ` + + `at ${formatAmount(p.rateBaseUnits, p.assetDecimals)} is ` + + `${formatAmount(p.hours * p.rateBaseUnits, p.assetDecimals)}.`, + }); + } + if (p.amountBaseUnits <= 0n) { + errors.push({ + field: 'amount', + code: 'AMOUNT_NOT_POSITIVE', + message: `Payment to ${p.recipientAddress.slice(0, 8)}… has a non-positive amount.`, + }); + } + } + + if (asset.contractId === null) { + errors.push({ + field: 'asset', + code: 'SETTLEMENT_ASSET_UNCONFIGURED', + message: + `No Stellar Asset Contract is configured for ${asset.code}, so this batch ` + + 'cannot be funded yet. CoreFlow will not infer a contract address from an ' + + 'asset symbol.', + }); + } + + return { + valid: errors.length === 0, + batch: { id: batch.id, reference: batch.reference, paymentCount: batch.payments.length }, + errors, + asset: { code: asset.code, contractId: asset.contractId, configured: asset.contractId !== null }, + }; +} + +// --------------------------------------------------------------------------- +// Batch approval +// --------------------------------------------------------------------------- + +/** States in which a payment is still waiting for an off-chain approval decision. */ +const APPROVABLE_STATES: readonly PaymentState[] = [ + PaymentState.AWAITING_MANAGER, + PaymentState.AWAITING_FINANCE, + PaymentState.ORACLE_VERIFIED, + PaymentState.DRAFT, +]; + +export interface BatchApprovalOutcome { + batchId: string; + /** Which half of the gate the caller exercised, derived from membership. */ + approvalRole: OrgRole; + recorded: number; + alreadyRecorded: number; + failed: number; + results: { + paymentId: string; + ok: boolean; + recorded: boolean; + state: PaymentState; + stateLabel: string; + message?: string; + code?: string; + }[]; + /** True only when this caller's half is now present on every payment. */ + completeForRole: boolean; +} + +/** + * Record the caller's approval across a batch. + * + * Fans out to `approvePayment` per payment rather than reimplementing approval, + * so separation of duties, duplicate detection and the audit trail all come from + * the one implementation that is already tested. + * + * Per-payment outcomes are reported individually and a failure on one payment does + * not abort the rest. "11 approved, 1 needs attention" is the normal result of a + * real batch, and collapsing it into a single success or failure would either hide + * the exception or discard eleven valid approvals. + */ +export async function approveBatch( + db: any, + ctx: TenantContext, + batch: { id: string; payments: { id: string; state: PaymentState }[] }, + opts: { paymentIds?: string[]; reason?: string; idempotencyKey?: string } = {}, +): Promise { + // Restrict to the named payments where given, intersected with the batch. A + // payment id from another batch or tenant simply is not in this list, so it + // cannot be reached by naming it. + const named = opts.paymentIds ? new Set(opts.paymentIds) : null; + const candidates = batch.payments.filter( + (p) => (named === null || named.has(p.id)) && APPROVABLE_STATES.includes(p.state), + ); + + if (named !== null) { + const missing = [...named].filter((id) => !batch.payments.some((p) => p.id === id)); + if (missing.length > 0) { + throw new ApiError( + 404, + 'NOT_FOUND', + 'One or more of the named payments is not part of this batch.', + ); + } + } + + if (candidates.length === 0) { + throw new ApiError( + 409, + 'STATE_CONFLICT', + 'No payment in this batch is awaiting an approval decision.', + ); + } + + const results: BatchApprovalOutcome['results'] = []; + let recorded = 0; + let alreadyRecorded = 0; + let failed = 0; + let approvalRole: OrgRole | null = null; + + for (const payment of candidates) { + const result = await approvePayment({ + db, + membership: ctx, + paymentId: payment.id, + reason: opts.reason, + // Scoped per payment, so one batch-level key cannot collapse twelve + // distinct approvals into one recorded decision. + idempotencyKey: opts.idempotencyKey ? `${opts.idempotencyKey}:${payment.id}` : undefined, + }); + + if (result.ok) { + const changed = result.body.changed !== false || result.status === 201; + const isNew = result.status === 201; + if (isNew) recorded++; + else alreadyRecorded++; + if (typeof result.body.approvalRole === 'string') { + approvalRole = result.body.approvalRole as OrgRole; + } + const state = (result.body.state as PaymentState) ?? payment.state; + results.push({ + paymentId: payment.id, + ok: true, + recorded: isNew, + state, + stateLabel: describeState(state).label, + ...(changed || isNew ? {} : { message: result.body.note as string }), + ...(isNew ? {} : { message: (result.body.note as string) ?? 'Already recorded.' }), + }); + } else { + failed++; + results.push({ + paymentId: payment.id, + ok: false, + recorded: false, + state: payment.state, + stateLabel: describeState(payment.state).label, + message: result.message, + ...(result.code ? { code: result.code } : {}), + }); + } + } + + return { + batchId: batch.id, + approvalRole: approvalRole ?? inferRole(ctx.role), + recorded, + alreadyRecorded, + failed, + results, + completeForRole: failed === 0, + }; +} + +/** + * Which half of the gate a role exercises, for reporting only. + * + * The authoritative decision is `approvePayment`'s; this is the fallback used when + * every payment was already recorded and no action returned a role. + */ +function inferRole(role: OrgRole): OrgRole { + return role === OrgRole.FINANCE ? OrgRole.FINANCE : OrgRole.MANAGER; +} + +// --------------------------------------------------------------------------- +// Reads +// --------------------------------------------------------------------------- + +/** + * Audit events for a batch, newest last, as the activity timeline. + * + * Rendered from real `AuditEvent` rows and nothing else. The timeline will look + * sparse early in a batch's life β€” created, funded, approved β€” and that is correct. + * Padding it with plausible-sounding entries nobody recorded would make the one + * screen whose job is to show what actually happened the least trustworthy in the + * product. + */ +export function presentActivity(events: readonly any[]) { + return events.map((e) => ({ + id: e.id, + type: e.type, + at: e.createdAt?.toISOString() ?? null, + actor: e.actorAddress + ? { kind: 'user' as const, address: e.actorAddress } + : e.actorSystem + ? { kind: 'system' as const, system: e.actorSystem } + : null, + previousState: e.previousState ?? null, + newState: e.newState ?? null, + txHash: e.txHash ?? null, + paymentId: e.paymentId ?? null, + // Metadata is operator-facing detail, already free of secrets by construction: + // every writer passes explicit fields, never a whole request or config object. + metadata: (e.metadata ?? null) as Record | null, + })); +} + +/** Open reconciliation findings for a batch's payments. */ +export function presentFindings(findings: readonly any[]) { + return findings.map((f) => ({ + id: f.id, + kind: f.kind, + severity: f.severity, + status: f.status, + detail: f.detail, + paymentId: f.paymentId ?? null, + firstDetectedAt: f.detectedAt?.toISOString() ?? null, + lastObservedAt: f.lastObservedAt?.toISOString() ?? null, + observationCount: f.observationCount ?? null, + remediation: f.remediation ?? null, + })); +} + +/** Shape a batch for the detail view, including its derived standing. */ +export function presentBatch(batch: any) { + const payments: any[] = batch.payments ?? []; + const decimals = payments[0]?.assetDecimals ?? SAC_DECIMALS; + // The batch's standing is DERIVED from its payments on every read. There is no + // stored status column, because a stored rollup is a second copy of mutable + // truth and will eventually disagree with the payments it claims to summarize. + const rollup = rollupBatch(payments); + const totalBaseUnits = rollup.totalAmountBaseUnits; + + return { + id: batch.id, + reference: batch.reference, + projectId: batch.projectId ?? null, + periodStart: batch.periodStart?.toISOString() ?? null, + periodEnd: batch.periodEnd?.toISOString() ?? null, + createdAt: batch.createdAt?.toISOString() ?? null, + source: { + filename: batch.sourceFilename ?? null, + rowsSeen: batch.sourceRowCount ?? null, + checksum: batch.sourceChecksum ?? null, + uploadedBy: batch.uploadedBy ?? null, + }, + total: formatAmountWithSeparators(totalBaseUnits, decimals), + totalBaseUnits: totalBaseUnits.toString(), + asset: payments[0]?.assetCode ?? null, + paymentCount: payments.length, + standing: { + headline: rollup.headline, + byState: rollup.byState, + needsAttention: rollup.needsAttention, + // Exact, as strings: JSON has no bigint, and a Number would reintroduce + // the rounding this codebase refuses everywhere else. + totalAmountBaseUnits: rollup.totalAmountBaseUnits.toString(), + paidAmountBaseUnits: rollup.paidAmountBaseUnits.toString(), + paid: formatAmountWithSeparators(rollup.paidAmountBaseUnits, decimals), + }, + payments: payments.map((p: any) => { + const d = describeState(p.state); + return { + id: p.id, + recipient: p.recipientAddress, + amount: formatAmountWithSeparators(p.amountBaseUnits, p.assetDecimals), + amountBaseUnits: p.amountBaseUnits.toString(), + // Formatted server-side for the same reason the amount is: a rate rendered + // from base units in the browser either reads as 250000000 or requires the + // browser to divide, and neither belongs on a payroll screen. + rate: formatAmountWithSeparators(p.rateBaseUnits, p.assetDecimals), + rateBaseUnits: p.rateBaseUnits.toString(), + hours: p.hours.toString(), + asset: p.assetCode, + state: p.state, + stateLabel: d.label, + tone: d.tone, + needsAttention: d.needsAttention, + stateReason: p.stateReason ?? null, + reference: p.sourceReference ?? null, + // A transaction link is surfaced only where one can exist. Showing an + // explorer URL for an unsubmitted payment invites the reader to believe + // something settled. + transactionHash: d.mayHaveTransaction ? (p.settlementTxHash ?? null) : null, + settledAt: p.settledAt?.toISOString() ?? null, + onChainPaymentIndex: p.onChainPaymentIndex ?? null, + approvals: (p.approvals ?? []).map((a: any) => ({ + role: a.role, + decision: a.decision, + actorAddress: a.actorAddress, + createdAt: a.createdAt?.toISOString() ?? null, + })), + }; + }), + }; +} diff --git a/src/lib/payroll/assets.ts b/src/lib/payroll/assets.ts new file mode 100644 index 0000000..5934371 --- /dev/null +++ b/src/lib/payroll/assets.ts @@ -0,0 +1,67 @@ +/** + * Which asset this deployment can actually settle. + * + * An escrow in the CoreFlow contract holds ONE Stellar Asset Contract. So the + * question "is USDC supported?" has no global answer β€” it depends on the SAC this + * deployment was configured with. Accepting a currency the settlement path cannot + * honour produces a batch that validates, funds nothing, and fails at the wallet. + * + * Nothing here guesses a contract address. A missing SAC is reported as missing. + */ + +import { SAC_DECIMALS } from '@/lib/money'; + +export interface SettlementAsset { + /** Display code, e.g. USDC. */ + code: string; + /** The Stellar Asset Contract address, or null when unconfigured. */ + contractId: string | null; + /** SAC decimals. Always 7 for a Stellar Asset Contract. */ + decimals: number; +} + +/** Default when the operator has not said otherwise. */ +const DEFAULT_CODE = 'USDC'; + +/** + * The configured settlement asset. + * + * Read on each call rather than captured at module load, so a test or a server + * restart sees the current environment instead of whatever was set when the + * module first happened to be imported. + */ +export function settlementAsset(): SettlementAsset { + const code = (process.env.NEXT_PUBLIC_SETTLEMENT_ASSET_CODE || DEFAULT_CODE).trim().toUpperCase(); + const contractId = (process.env.NEXT_PUBLIC_STELLAR_TOKEN_ID || '').trim(); + return { code, contractId: contractId.length > 0 ? contractId : null, decimals: SAC_DECIMALS }; +} + +/** + * Asset codes a payroll CSV may use here. + * + * Exactly one, because one escrow holds one SAC. A multi-asset payroll needs one + * escrow per asset, which is a product decision and not something to paper over + * by quietly accepting a code that cannot be paid. + */ +export function settleableAssetCodes(): readonly string[] { + return [settlementAsset().code]; +} + +/** + * The settlement SAC address, or a clear failure. + * + * Used where an address is genuinely required β€” funding custody, verifying a + * transfer. Draft creation deliberately does NOT call this: a batch can be + * prepared and reviewed before the operator has finished wiring the asset, and + * refusing the upload for that reason would be unhelpful. + */ +export function requireSettlementContractId(): string { + const { code, contractId } = settlementAsset(); + if (!contractId) { + throw new Error( + `NEXT_PUBLIC_STELLAR_TOKEN_ID is not set, so CoreFlow does not know which ` + + `contract issues ${code}. It will not guess a Stellar Asset Contract address.`, + ); + } + return contractId; +} diff --git a/src/lib/payroll/batches.ts b/src/lib/payroll/batches.ts new file mode 100644 index 0000000..4b7f550 --- /dev/null +++ b/src/lib/payroll/batches.ts @@ -0,0 +1,416 @@ +/** + * Creating a payroll batch from validated CSV rows. + * + * Two properties carry most of the weight here: + * + * 1. ONE PAYMENT PER ROW. A three-payee payroll is three Payment rows, each with + * its own state, approvals, attestation and settlement evidence. Collapsing it + * into one aggregate would make "11 paid, 1 needs attention" unrepresentable, + * and that is the normal outcome of a real batch, not an edge case. + * + * 2. CREATION IS IDEMPOTENT. A double-click, a refresh after a gateway timeout, a + * second tab, or a client retry must not produce a second payroll. The + * guarantee is a UNIQUE INDEX on (orgId, idempotencyKey), not a read-then-write + * in this file: check-then-insert loses exactly the race it is meant to cover. + * + * Everything created here starts in DRAFT. Nothing in this module can move a + * payment toward settlement, and nothing here touches the chain. + */ + +import { createHash } from 'node:crypto'; +import { PaymentState } from '@prisma/client'; +import { recordAuditEvent } from '@/lib/payments/service'; +import { sumAmounts } from '@/lib/money'; +import type { TenantContext } from '@/lib/tenancy/resolve'; +import type { ParsedPayrollRow } from './csv'; +import type { SettlementAsset } from './assets'; + +/** + * How recently an identical file counts as "probably a re-submit". + * + * Only ever used to WARN. Uploading the same figures next period is legitimate + * payroll, so this must not block: a system that refuses a valid second payroll + * is worse than one that asks. + */ +export const DUPLICATE_UPLOAD_WINDOW_MS = 60 * 60 * 1000; + +/** Longest a generated reference may collide before we stop retrying. */ +const MAX_REFERENCE_ATTEMPTS = 5; + +const REFERENCE_FORMAT = /^[A-Za-z0-9][A-Za-z0-9._\-/ ]{0,62}$/; + +/** SHA-256 of the uploaded bytes, for "have I already uploaded this?" */ +export function checksumCsv(text: string): string { + return createHash('sha256').update(text, 'utf8').digest('hex'); +} + +export interface CreateDraftBatchInput { + rows: readonly ParsedPayrollRow[]; + /** Asset every row settles in. Narrowed by the caller from configuration. */ + asset: SettlementAsset; + /** Client-chosen label. Generated when absent. */ + reference?: string | null; + projectId?: string | null; + sourceFilename?: string | null; + /** Rows SEEN in the file, including rejected ones. Provenance, not a count of payments. */ + sourceRowCount?: number | null; + sourceChecksum?: string | null; + idempotencyKey?: string | null; + /** + * Hash of the semantically meaningful request, paired with `idempotencyKey`. + * + * Lets a genuine retry (same key, same payload) be told apart from a key + * collision (same key, different payload). Without it, a client that reused a + * key for a different file would be handed the FIRST batch and told it + * succeeded β€” the wrong payroll, reported as the right one. + */ + idempotencyFingerprint?: string | null; +} + +export interface CreatedBatch { + id: string; + reference: string; + paymentCount: number; + periodStart: Date | null; + periodEnd: Date | null; + totalBaseUnits: bigint; + /** Payees with no Worker record in this organization yet. */ + unlinkedRecipients: number; +} + +export type CreateDraftBatchResult = + | { + ok: true; + /** False when an earlier identical request already created this batch. */ + created: boolean; + batch: CreatedBatch; + note?: string; + } + | { ok: false; status: 400 | 409; message: string; code?: string }; + +/** Fields the replay paths need from an existing batch. */ +const REPLAY_SELECT = { + id: true, + reference: true, + periodStart: true, + periodEnd: true, + idempotencyFingerprint: true, +} as const; + +/** Earliest start and latest end across the rows, for the batch header. */ +export function deriveBatchPeriod(rows: readonly ParsedPayrollRow[]): { + periodStart: Date | null; + periodEnd: Date | null; +} { + let start: Date | null = null; + let end: Date | null = null; + for (const r of rows) { + if (r.periodStart && (start === null || r.periodStart < start)) start = r.periodStart; + if (r.periodEnd && (end === null || r.periodEnd > end)) end = r.periodEnd; + } + return { periodStart: start, periodEnd: end }; +} + +/** + * Next sequential reference for this organization, e.g. CF-00042. + * + * Derived from a count, so two concurrent uploads can propose the same one. That + * is handled by the unique index and a retry rather than by locking: a contended + * human-facing label is not worth serializing payroll creation over. + */ +async function generateReference(db: any, orgId: string, attempt: number): Promise { + const existing = await db.payrollBatch.count({ where: { orgId } }); + return `CF-${String(existing + 1 + attempt).padStart(5, '0')}`; +} + +/** + * A recent batch built from byte-identical input, if there is one. + * + * `excludeId` is the batch just created. It is excluded IN THE QUERY rather than + * filtered out of the result: two batches created in the same millisecond order + * arbitrarily, so taking the newest match first and discarding it afterwards + * returns nothing exactly when a duplicate is most likely β€” a genuine + * double-submit seconds apart. + */ +export async function findDuplicateUpload( + db: any, + orgId: string, + checksum: string, + opts: { now?: Date; excludeId?: string } = {}, +): Promise<{ id: string; reference: string; createdAt: Date } | null> { + if (!checksum) return null; + const now = opts.now ?? new Date(); + const since = new Date(now.getTime() - DUPLICATE_UPLOAD_WINDOW_MS); + const found = await db.payrollBatch.findFirst({ + where: { + orgId, + sourceChecksum: checksum, + createdAt: { gte: since }, + ...(opts.excludeId ? { id: { not: opts.excludeId } } : {}), + }, + orderBy: { createdAt: 'desc' }, + select: { id: true, reference: true, createdAt: true }, + }); + return found ?? null; +} + +/** Shape an existing batch into the success result, for the idempotent replay path. */ +async function describeExisting( + db: any, + orgId: string, + batch: { id: string; reference: string; periodStart: Date | null; periodEnd: Date | null }, +): Promise { + const payments = await db.payment.findMany({ + where: { orgId, batchId: batch.id }, + select: { amountBaseUnits: true, workerId: true }, + }); + return { + id: batch.id, + reference: batch.reference, + paymentCount: payments.length, + periodStart: batch.periodStart ?? null, + periodEnd: batch.periodEnd ?? null, + totalBaseUnits: sumAmounts(payments.map((p: any) => p.amountBaseUnits)), + unlinkedRecipients: payments.filter((p: any) => p.workerId === null).length, + }; +} + +/** + * Is an existing batch under this key a replay of THIS request? + * + * Treated as a replay only when the fingerprints agree. A stored batch with no + * fingerprint (created before fingerprinting, or without a key) is accepted as a + * replay rather than refused: refusing would strand a client that is legitimately + * retrying, and the unique index has already guaranteed there is only one batch. + */ +function replayVerdict( + prior: { idempotencyFingerprint?: string | null }, + fingerprint: string | null, +): 'replay' | 'conflict' { + if (fingerprint === null) return 'replay'; + const stored = prior.idempotencyFingerprint ?? null; + if (stored === null) return 'replay'; + return stored === fingerprint ? 'replay' : 'conflict'; +} + +const KEY_REUSED: CreateDraftBatchResult = { + ok: false, + status: 409, + code: 'IDEMPOTENCY_KEY_REUSED', + message: + 'This idempotency key was already used for a different payroll. Retrying a ' + + 'request must send the same file and options; a new payroll needs a new key.', +}; + +/** + * Create a DRAFT batch and one DRAFT payment per row. + * + * The caller is responsible for authorization (`withTenant` + `payroll:create`) + * and for having parsed and validated the rows. This function does not re-validate + * the money: `parsePayrollCsv` already refused anything ambiguous, and a second, + * subtly different set of rules in a second place is how the two drift apart. + */ +export async function createDraftBatch( + db: any, + ctx: TenantContext, + input: CreateDraftBatchInput, +): Promise { + const rows = input.rows; + if (rows.length === 0) { + return { ok: false, status: 400, message: 'A batch needs at least one payroll row.' }; + } + + const suppliedReference = input.reference?.trim() || null; + if (suppliedReference !== null && !REFERENCE_FORMAT.test(suppliedReference)) { + return { + ok: false, + status: 400, + message: + 'A batch reference may use letters, digits, spaces and . _ - / only, ' + + 'and must start with a letter or digit.', + code: 'INVALID_REFERENCE', + }; + } + + const idempotencyKey = input.idempotencyKey?.trim() || null; + const fingerprint = input.idempotencyFingerprint?.trim() || null; + + // Fast path: this exact request already succeeded. Checked before doing any + // work, but NOT relied upon for correctness β€” the unique index below is. + if (idempotencyKey !== null) { + const prior = await db.payrollBatch.findFirst({ + where: { orgId: ctx.orgId, idempotencyKey }, + select: REPLAY_SELECT, + }); + if (prior) { + if (replayVerdict(prior, fingerprint) === 'conflict') return KEY_REUSED; + return { + ok: true, + created: false, + batch: await describeExisting(db, ctx.orgId, prior), + note: 'This batch was already created by an earlier request with the same idempotency key.', + }; + } + } + + const { periodStart, periodEnd } = deriveBatchPeriod(rows); + + // Link payees to existing Worker records where one exists. A payee without a + // Worker row is reported, not invented: creating personnel records as a side + // effect of a file upload is a surprise, and the count lets the UI offer it as + // a choice instead. + const recipients = Array.from(new Set(rows.map((r) => r.recipient))); + const workers = await db.worker.findMany({ + where: { orgId: ctx.orgId, walletAddress: { in: recipients } }, + select: { id: true, walletAddress: true }, + }); + const workerByAddress = new Map( + workers.map((w: any) => [w.walletAddress, w.id]), + ); + + const totalBaseUnits = sumAmounts(rows.map((r) => r.amountBaseUnits)); + + for (let attempt = 0; attempt < MAX_REFERENCE_ATTEMPTS; attempt++) { + const reference = suppliedReference ?? (await generateReference(db, ctx.orgId, attempt)); + + try { + const batchId: string = await db.$transaction(async (tx: any) => { + const batch = await tx.payrollBatch.create({ + data: { + orgId: ctx.orgId, + projectId: input.projectId ?? null, + reference, + periodStart, + periodEnd, + sourceFilename: input.sourceFilename ?? null, + sourceRowCount: input.sourceRowCount ?? rows.length, + sourceChecksum: input.sourceChecksum ?? null, + idempotencyKey, + idempotencyFingerprint: fingerprint, + uploadedBy: ctx.userId, + }, + select: { id: true }, + }); + + // One row in, one payment out. Created individually rather than with + // createMany so each carries its own period and worker link, and so the + // composite (orgId, batchId) foreign key is exercised per row. + for (const row of rows) { + await tx.payment.create({ + data: { + orgId: ctx.orgId, + batchId: batch.id, + projectId: input.projectId ?? null, + workerId: workerByAddress.get(row.recipient) ?? null, + recipientAddress: row.recipient, + assetCode: input.asset.code, + assetContractId: input.asset.contractId, + assetDecimals: input.asset.decimals, + amountBaseUnits: row.amountBaseUnits, + rateBaseUnits: row.rateBaseUnits, + hours: row.hours, + periodStart: row.periodStart, + periodEnd: row.periodEnd, + sourceReference: row.reference, + // Explicit, though it is also the column default: a payment's + // starting state is a decision, not an accident of schema. + state: PaymentState.DRAFT, + }, + select: { id: true }, + }); + } + + await recordAuditEvent(tx, { + orgId: ctx.orgId, + type: 'payroll.batch.created', + actor: { kind: 'user', role: ctx.role, address: ctx.walletAddress }, + batchId: batch.id, + metadata: { + reference, + paymentCount: rows.length, + // Strings: JSON cannot carry a bigint, and a Number would be the + // very rounding this codebase refuses everywhere else. + totalBaseUnits: totalBaseUnits.toString(), + asset: input.asset.code, + assetContractId: input.asset.contractId, + sourceFilename: input.sourceFilename ?? null, + sourceRowCount: input.sourceRowCount ?? rows.length, + sourceChecksum: input.sourceChecksum ?? null, + uploadedByUserId: ctx.userId, + idempotencyKey, + }, + }); + + return batch.id; + }); + + return { + ok: true, + created: true, + batch: { + id: batchId, + reference, + paymentCount: rows.length, + periodStart, + periodEnd, + totalBaseUnits, + unlinkedRecipients: recipients.filter((r) => !workerByAddress.has(r)).length, + }, + }; + } catch (e: any) { + if (e?.code !== 'P2002') throw e; + const target: string[] = Array.isArray(e?.meta?.target) + ? e.meta.target + : typeof e?.meta?.target === 'string' + ? [e.meta.target] + : []; + const hit = (col: string) => target.some((t) => t.includes(col)); + + // Lost the idempotency race: a concurrent identical request committed + // first. Its batch is the answer, so report that rather than an error β€” + // the caller asked for this batch to exist, and it does. + if (hit('idempotencyKey') && idempotencyKey !== null) { + const prior = await db.payrollBatch.findFirst({ + where: { orgId: ctx.orgId, idempotencyKey }, + select: REPLAY_SELECT, + }); + if (prior) { + if (replayVerdict(prior, fingerprint) === 'conflict') return KEY_REUSED; + return { + ok: true, + created: false, + batch: await describeExisting(db, ctx.orgId, prior), + note: + 'An identical request created this batch concurrently. No second ' + + 'payroll was created.', + }; + } + } + + if (hit('reference')) { + // A reference the CALLER chose is their decision to fix; a generated one + // is ours, so try the next number. + if (suppliedReference !== null) { + return { + ok: false, + status: 409, + message: `This organization already has a batch referenced "${suppliedReference}".`, + code: 'REFERENCE_TAKEN', + }; + } + continue; + } + + throw e; + } + } + + return { + ok: false, + status: 409, + message: + 'Could not allocate a batch reference after several attempts. Retry, or ' + + 'supply a reference explicitly.', + code: 'REFERENCE_EXHAUSTED', + }; +} diff --git a/src/lib/payroll/csv.ts b/src/lib/payroll/csv.ts new file mode 100644 index 0000000..e3293eb --- /dev/null +++ b/src/lib/payroll/csv.ts @@ -0,0 +1,665 @@ +/** + * Payroll CSV parsing and validation. + * + * Design rules: + * 1. NEVER silently mutate input. A row that does not say what the uploader meant + * is an error they must see, not something to round, coerce or guess. Quietly + * "fixing" a payroll figure is the worst possible kind of helpfulness. + * 2. Money is parsed from its decimal STRING into `bigint` base units. A JS + * `number` anywhere in this path reintroduces the stroops class of bug. + * 3. Every rejection names the line and says what to do about it. + * 4. Text that will ever be re-displayed or re-exported is treated as hostile + * input, not as data that happens to live in a file. + */ + +import { + parseAmount, + formatAmount, + hoursForAmount, + MoneyParseError, + SAC_DECIMALS, +} from '@/lib/money'; + +/** Hard limits. A payroll file is small; anything large is a mistake or an attack. */ +export const MAX_CSV_BYTES = 1_000_000; // 1 MB +/** Matches the contract's MAX_BATCH_SIZE, so a file that validates is always settleable. */ +export const MAX_ROWS = 100; +export const MAX_FIELD_LENGTH = 256; + +/** + * Columns every payroll file must carry. + * + * `period_start` and `period_end` are REQUIRED, not optional. The pay period is a + * signed field of the CFWP-v2 oracle attestation and the contract refuses an escrow + * whose `end_date <= start_date`, so a row without a period can be drafted but can + * never be funded. CoreFlow will not supply one β€” defaulting to today, last month, + * or the upload date would mean attesting to a pay period nobody stated. + * + * This moved here from the funding check deliberately: discovering it at the wallet + * prompt, after a payroll has been prepared and approved, is far worse than being + * told at row 8. + */ +export const REQUIRED_COLUMNS = [ + 'recipient', + 'amount', + 'asset', + 'hours', + 'rate', + 'period_start', + 'period_end', +] as const; +export const OPTIONAL_COLUMNS = ['reference'] as const; + +const STELLAR_ADDRESS = /^G[A-Z2-7]{55}$/; + +/** Assets this deployment can settle. Anything else is refused, never assumed. */ +export const SUPPORTED_ASSETS = ['USDC', 'XLM'] as const; +export type SupportedAsset = (typeof SUPPORTED_ASSETS)[number]; + +export type CsvIssueCode = + | 'FILE_EMPTY' + | 'FILE_TOO_LARGE' + | 'TOO_MANY_ROWS' + | 'MISSING_COLUMN' + | 'DUPLICATE_COLUMN' + | 'FIELD_TOO_LONG' + | 'WRONG_FIELD_COUNT' + | 'INVALID_ADDRESS' + | 'INVALID_AMOUNT' + | 'AMOUNT_NOT_POSITIVE' + | 'AMOUNT_PRECISION' + | 'AMBIGUOUS_NUMBER' + | 'INVALID_HOURS' + | 'HOURS_RATE_MISMATCH' + | 'FRACTIONAL_HOURS' + | 'UNSUPPORTED_ASSET' + | 'MIXED_ASSETS' + | 'DUPLICATE_RECIPIENT' + | 'INVALID_PERIOD' + | 'PERIOD_REQUIRED' + | 'NO_ROWS'; + +export interface CsvIssue { + /** 1-based line number in the uploaded file, as the user sees it. 0 = whole file. */ + line: number; + column?: string; + /** What is wrong, in the uploader's terms. */ + message: string; + /** Machine code, for tests and for grouping in the UI. */ + code: CsvIssueCode; +} + +export interface ParsedPayrollRow { + line: number; + recipient: string; + asset: SupportedAsset; + /** Base units. Exact. */ + amountBaseUnits: bigint; + rateBaseUnits: bigint; + hours: bigint; + periodStart: Date | null; + periodEnd: Date | null; + /** Free text from the uploader, already neutralized for re-display and re-export. */ + reference: string | null; +} + +export interface CsvParseResult { + rows: ParsedPayrollRow[]; + /** Blocking. Nothing is created while any of these stand. */ + issues: CsvIssue[]; + /** Non-blocking observations the uploader should still read. */ + warnings: CsvIssue[]; + totalsByAsset: Record; + /** Data rows seen in the file, including rejected ones. */ + rowCount: number; +} + +// --- Sanitization ----------------------------------------------------------- + +/** + * Neutralize spreadsheet formula injection. + * + * A field beginning `=`, `+`, `-`, `@`, tab or CR is executed as a formula when a + * CSV is reopened in Excel, Sheets or Numbers. CoreFlow re-displays and can + * re-export uploader-supplied text, so a crafted cell becomes code running inside + * a finance team's spreadsheet. Prefixing an apostrophe is the standard + * neutralization and preserves the visible value. + * + * Applied where text is STORED, so every later render and export inherits it + * rather than each one having to remember. + */ +export function sanitizeForSpreadsheet(value: string): string { + if (value.length === 0) return value; + return /^[=+\-@\t\r]/.test(value) ? `'${value}` : value; +} + +/** + * Control characters that have no place in payroll text: everything below 0x20 + * except tab, newline and carriage return, plus DEL. + * + * Defined once, here, and reused by the request schemas. Two copies of a + * character class eventually disagree, and the one that matters is whichever is + * checked last. + */ +const CONTROL_CHARS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/; + +/** True when a value carries a control character. */ +export function containsControlChars(value: string): boolean { + return CONTROL_CHARS.test(value); +} + +/** Strip control characters that would corrupt logs, terminals or CSV exports. */ +function stripControlChars(value: string): string { + return value.replace(new RegExp(CONTROL_CHARS.source, 'g'), ''); +} + +// --- Parsing ---------------------------------------------------------------- + +/** + * RFC 4180-ish CSV reader: quoted fields, escaped quotes, CRLF or LF. + * + * Hand-written rather than a dependency because the grammar is small, and a CSV + * parser is the one place a supply-chain compromise would see every payroll file. + */ +export function parseCsvText(text: string): string[][] { + const rows: string[][] = []; + let row: string[] = []; + let field = ''; + let quoted = false; + let sawField = false; + + const endField = () => { + row.push(field); + field = ''; + sawField = false; + }; + const endRow = () => { + if (sawField || field.length > 0 || row.length > 0) { + row.push(field); + field = ''; + } + if (row.length > 0) rows.push(row); + row = []; + sawField = false; + }; + + // Strip a UTF-8 BOM: Excel writes one, and it would corrupt the first header name. + const source = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text; + + for (let i = 0; i < source.length; i++) { + const c = source[i]; + if (quoted) { + if (c === '"') { + if (source[i + 1] === '"') { + field += '"'; + i++; + } else { + quoted = false; + } + } else { + field += c; + } + continue; + } + if (c === '"') { + quoted = true; + sawField = true; + } else if (c === ',') { + endField(); + } else if (c === '\n') { + endRow(); + } else if (c === '\r') { + if (source[i + 1] === '\n') i++; + endRow(); + } else { + field += c; + sawField = true; + } + } + if (sawField || field.length > 0 || row.length > 0) endRow(); + + // Drop wholly blank lines: trailing newlines are normal in hand-edited files. + return rows.filter((r) => r.some((f) => f.trim().length > 0)); +} + +/** + * Parse an amount string into base units, refusing anything ambiguous. + * + * Scientific notation is rejected rather than interpreted: `1e3` means 1000 to a + * developer and is a typo to everybody else, and a payroll system must not pick. + * Thousands separators ARE accepted, because spreadsheets emit them. + */ +function parseMoneyField( + raw: string, + line: number, + column: string, + issues: CsvIssue[], +): bigint | null { + const value = raw.trim(); + if (value.length === 0) { + issues.push({ line, column, code: 'INVALID_AMOUNT', message: `${column} is required.` }); + return null; + } + if (/[eE]/.test(value)) { + issues.push({ + line, + column, + code: 'AMBIGUOUS_NUMBER', + message: + `${column} "${value}" uses scientific notation, which is ambiguous. ` + + 'Write the number out in full.', + }); + return null; + } + if (value.startsWith('(') || value.endsWith(')')) { + // Accounting-style negative, e.g. (500). + issues.push({ + line, + column, + code: 'AMOUNT_NOT_POSITIVE', + message: `${column} "${value}" reads as a negative amount. Payroll amounts must be positive.`, + }); + return null; + } + + // Strip presentation only: currency marks, spaces and an asset suffix. + // Digits, the decimal point and the sign are left exactly as written. + const cleaned = value.replace(/[$\s]|USDC|XLM/gi, ''); + try { + const units = parseAmount(cleaned, SAC_DECIMALS); + if (units <= 0n) { + issues.push({ + line, + column, + code: 'AMOUNT_NOT_POSITIVE', + message: `${column} must be greater than zero.`, + }); + return null; + } + return units; + } catch (e) { + const precision = e instanceof MoneyParseError && /decimal place/i.test(e.message); + issues.push({ + line, + column, + code: precision ? 'AMOUNT_PRECISION' : 'INVALID_AMOUNT', + message: + e instanceof MoneyParseError + ? `${column}: ${e.message}` + : `${column} "${value}" is not a valid amount.`, + }); + return null; + } +} + +function parseDateField( + raw: string, + line: number, + column: string, + issues: CsvIssue[], +): Date | null { + const value = raw.trim(); + if (value.length === 0) { + issues.push({ + line, + column, + code: 'PERIOD_REQUIRED', + message: + `${column} is required. CoreFlow records a pay period because the period is ` + + 'part of what the oracle attests to, and the contract refuses an escrow ' + + 'without one. It cannot be assumed on your behalf.', + }); + return null; + } + // ISO only. Locale forms like 03/04/2026 are genuinely ambiguous between March + // and April, and guessing which pay period was meant is not acceptable. + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { + issues.push({ + line, + column, + code: 'INVALID_PERIOD', + message: `${column} "${value}" must be a date in YYYY-MM-DD form.`, + }); + return null; + } + const date = new Date(`${value}T00:00:00.000Z`); + if (Number.isNaN(date.getTime()) || !date.toISOString().startsWith(value)) { + issues.push({ + line, + column, + code: 'INVALID_PERIOD', + message: `${column} "${value}" is not a real date.`, + }); + return null; + } + return date; +} + +export interface ParseOptions { + /** Reject a recipient appearing twice. Default true: it is usually a paste slip. */ + rejectDuplicateRecipients?: boolean; + /** + * Asset codes this caller can actually settle, narrowing SUPPORTED_ASSETS. + * + * An escrow holds ONE Stellar Asset Contract, so a deployment configured for + * USDC cannot pay an XLM row. Accepting such a row here would let it validate + * and then fail at funding time, which is exactly the kind of late surprise + * this layer exists to prevent. Defaults to every recognized code so the pure + * parser stays testable without configuration. + */ + supportedAssets?: readonly string[]; +} + +/** + * Parse and validate a payroll CSV. + * + * Reports every issue found, not just the first: a finance user fixing a 40-row + * file one error per upload is an unusable product. + */ +export function parsePayrollCsv(text: string, opts: ParseOptions = {}): CsvParseResult { + const rejectDuplicates = opts.rejectDuplicateRecipients ?? true; + const allowedAssets = (opts.supportedAssets ?? SUPPORTED_ASSETS).map((a) => a.toUpperCase()); + const issues: CsvIssue[] = []; + const warnings: CsvIssue[] = []; + const rows: ParsedPayrollRow[] = []; + const totalsByAsset: Record = {}; + + const bytes = Buffer.byteLength(text, 'utf8'); + if (bytes > MAX_CSV_BYTES) { + return { + rows: [], + warnings, + totalsByAsset, + rowCount: 0, + issues: [ + { + line: 0, + code: 'FILE_TOO_LARGE', + message: `File is ${Math.round(bytes / 1024)} KB; the limit is ${MAX_CSV_BYTES / 1024} KB.`, + }, + ], + }; + } + + const table = parseCsvText(text); + if (table.length === 0) { + return { + rows: [], + warnings, + totalsByAsset, + rowCount: 0, + issues: [{ line: 0, code: 'FILE_EMPTY', message: 'The file is empty.' }], + }; + } + + // --- Header --- + const header = table[0].map((h) => stripControlChars(h).trim().toLowerCase()); + const seen = new Set(); + for (const [i, col] of header.entries()) { + if (col.length > 0 && seen.has(col)) { + issues.push({ + line: 1, + column: col, + code: 'DUPLICATE_COLUMN', + message: `Column "${col}" appears more than once (position ${i + 1}).`, + }); + } + seen.add(col); + } + + const index: Record = {}; + for (const col of [...REQUIRED_COLUMNS, ...OPTIONAL_COLUMNS]) { + index[col] = header.indexOf(col); + } + for (const col of REQUIRED_COLUMNS) { + if (index[col] < 0) { + issues.push({ + line: 1, + column: col, + code: 'MISSING_COLUMN', + message: `Required column "${col}" is missing. Expected: ${REQUIRED_COLUMNS.join(', ')}.`, + }); + } + } + // Without the required columns there is nothing to validate against. Stop here so + // the uploader fixes the structure instead of reading 40 derived row errors. + if (issues.some((i) => i.code === 'MISSING_COLUMN')) { + return { rows: [], issues, warnings, totalsByAsset, rowCount: 0 }; + } + + const dataRows = table.slice(1); + if (dataRows.length === 0) { + issues.push({ + line: 1, + code: 'NO_ROWS', + message: 'The file has a header but no payroll rows.', + }); + return { rows: [], issues, warnings, totalsByAsset, rowCount: 0 }; + } + if (dataRows.length > MAX_ROWS) { + issues.push({ + line: 0, + code: 'TOO_MANY_ROWS', + message: + `${dataRows.length} rows exceeds the ${MAX_ROWS}-row limit for one batch. ` + + 'Split the payroll into smaller batches.', + }); + return { rows: [], issues, warnings, totalsByAsset, rowCount: dataRows.length }; + } + + const recipientLines = new Map(); + + for (const [n, raw] of dataRows.entries()) { + const line = n + 2; // 1-based, accounting for the header row + const cell = (col: string): string => { + const i = index[col]; + const value = i >= 0 && i < raw.length ? raw[i] : ''; + return stripControlChars(value ?? ''); + }; + + if (raw.length !== header.length) { + warnings.push({ + line, + code: 'WRONG_FIELD_COUNT', + message: + `Row has ${raw.length} fields but the header has ${header.length}. ` + + 'Any missing fields were read as empty.', + }); + } + + let rowOk = true; + const fail = () => { + rowOk = false; + }; + + for (const col of [...REQUIRED_COLUMNS, ...OPTIONAL_COLUMNS]) { + if (cell(col).length > MAX_FIELD_LENGTH) { + issues.push({ + line, + column: col, + code: 'FIELD_TOO_LONG', + message: `${col} is longer than ${MAX_FIELD_LENGTH} characters.`, + }); + fail(); + } + } + + // --- Recipient --- + const recipient = cell('recipient').trim(); + if (!STELLAR_ADDRESS.test(recipient)) { + issues.push({ + line, + column: 'recipient', + code: 'INVALID_ADDRESS', + message: + `"${recipient || '(empty)'}" is not a valid Stellar address. ` + + 'Expected 56 characters beginning with G.', + }); + fail(); + } else if (rejectDuplicates && recipientLines.has(recipient)) { + issues.push({ + line, + column: 'recipient', + code: 'DUPLICATE_RECIPIENT', + message: + `This recipient already appears on line ${recipientLines.get(recipient)}. ` + + 'Combine the rows, or remove the duplicate.', + }); + fail(); + } else { + recipientLines.set(recipient, line); + } + + // --- Asset --- + const assetRaw = cell('asset').trim().toUpperCase(); + const asset = allowedAssets.includes(assetRaw) ? (assetRaw as SupportedAsset) : null; + if (asset === null) { + issues.push({ + line, + column: 'asset', + code: 'UNSUPPORTED_ASSET', + message: + `Asset "${assetRaw || '(empty)'}" cannot be settled here. ` + + `This deployment settles: ${allowedAssets.join(', ')}.`, + }); + fail(); + } + + // --- Money --- + const amount = parseMoneyField(cell('amount'), line, 'amount', issues); + if (amount === null) fail(); + const rate = parseMoneyField(cell('rate'), line, 'rate', issues); + if (rate === null) fail(); + + // --- Hours --- + const hoursRaw = cell('hours').trim(); + let hours: bigint | null = null; + if (hoursRaw.length === 0) { + issues.push({ line, column: 'hours', code: 'INVALID_HOURS', message: 'hours is required.' }); + fail(); + } else if (/[.,]/.test(hoursRaw)) { + // v2 attests whole hours. Rounding here would change what the oracle signs + // and what the contract checks, so it is refused with the reason stated. + issues.push({ + line, + column: 'hours', + code: 'FRACTIONAL_HOURS', + message: + `hours "${hoursRaw}" is fractional. CoreFlow v2 records whole hours and ` + + 'will not round a payroll figure. Use whole hours, or split the row.', + }); + fail(); + } else if (!/^\d+$/.test(hoursRaw)) { + issues.push({ + line, + column: 'hours', + code: 'INVALID_HOURS', + message: `hours "${hoursRaw}" is not a whole number.`, + }); + fail(); + } else { + hours = BigInt(hoursRaw); + if (hours <= 0n) { + issues.push({ + line, + column: 'hours', + code: 'INVALID_HOURS', + message: 'hours must be greater than zero.', + }); + fail(); + } + } + + // --- The contract's invariant, checked before anything is funded --- + // submit_hours_proof enforces hours * rate == amount on-chain (error #17). + // Catching it here means a batch never reaches a wallet only to revert. + if (amount !== null && rate !== null && hours !== null) { + if (hours * rate !== amount) { + const expected = formatAmount(hours * rate, SAC_DECIMALS); + issues.push({ + line, + code: 'HOURS_RATE_MISMATCH', + message: + `amount (${formatAmount(amount, SAC_DECIMALS)}) does not equal hours x rate ` + + `(${hours} x ${formatAmount(rate, SAC_DECIMALS)} = ${expected}). ` + + 'CoreFlow settles only what the verified hours justify.', + }); + fail(); + } else if (hoursForAmount(amount, rate) === null) { + issues.push({ + line, + code: 'HOURS_RATE_MISMATCH', + message: 'amount is not a whole multiple of rate.', + }); + fail(); + } + } + + // --- Period --- + const periodStart = parseDateField(cell('period_start'), line, 'period_start', issues); + const periodEnd = parseDateField(cell('period_end'), line, 'period_end', issues); + if (periodStart === null || periodEnd === null) { + // parseDateField has already explained which one and why. + fail(); + } else if (periodEnd.getTime() <= periodStart.getTime()) { + issues.push({ + line, + code: 'INVALID_PERIOD', + message: 'period_end must be after period_start.', + }); + fail(); + } + + const reference = cell('reference').trim(); + + if (!rowOk) continue; + + rows.push({ + line, + recipient, + asset: asset as SupportedAsset, + amountBaseUnits: amount as bigint, + rateBaseUnits: rate as bigint, + hours: hours as bigint, + periodStart, + periodEnd, + // Neutralized at the boundary, so every later render and export inherits it. + reference: reference.length > 0 ? sanitizeForSpreadsheet(reference) : null, + }); + + const key = asset as SupportedAsset; + totalsByAsset[key] = (totalsByAsset[key] ?? 0n) + (amount as bigint); + } + + // A mixed-asset batch settles correctly but is usually an accident in a + // hand-edited file, so it is surfaced rather than blocked. + if (Object.keys(totalsByAsset).length > 1) { + warnings.push({ + line: 0, + code: 'MIXED_ASSETS', + message: + `This batch pays in ${Object.keys(totalsByAsset).join(' and ')}. ` + + 'That is supported, but confirm it is intended.', + }); + } + + return { rows, issues, warnings, totalsByAsset, rowCount: dataRows.length }; +} + +/** Human-facing summary for the preview step. */ +export function summarizeParse(result: CsvParseResult): { + recipientCount: number; + totalHours: bigint; + totals: { asset: string; amount: string }[]; + hasBlockingIssues: boolean; +} { + return { + recipientCount: result.rows.length, + totalHours: result.rows.reduce((sum, r) => sum + r.hours, 0n), + totals: Object.entries(result.totalsByAsset).map(([asset, units]) => ({ + asset, + amount: formatAmount(units, SAC_DECIMALS), + })), + hasBlockingIssues: result.issues.length > 0, + }; +} diff --git a/src/lib/payroll/schemas.ts b/src/lib/payroll/schemas.ts new file mode 100644 index 0000000..c008549 --- /dev/null +++ b/src/lib/payroll/schemas.ts @@ -0,0 +1,231 @@ +/** + * Request schemas for the Bulk Pay API. + * + * Every field a client can send is declared here, with a length or range, and + * `.strict()` rejects anything undeclared. An unknown field is reported rather + * than ignored: silently dropping `{ state: "PAID" }` teaches a client that it + * worked, and the next reader of that code assumes the field is honoured. + * + * What is deliberately ABSENT matters as much as what is present. No schema here + * accepts a role, a payment state, a settlement status, an approval identity, or + * a monetary amount in any form other than the uploaded file. Those are derived + * from the authenticated session, from membership, and from server-side state. A + * field that cannot be sent cannot be forged. + * + * `orgId` is the one exception, and it is not a grant: it merely NAMES which of + * the caller's organizations to act in. `withTenant` reads the membership from + * the database on every request, so naming an organization the caller does not + * belong to produces the same non-enumerating miss as naming one that does not + * exist. + */ + +import { z } from 'zod'; +import { MAX_CSV_BYTES, MAX_ROWS, containsControlChars } from './csv'; + +/** cuid()s are what Prisma generates; bound both length and alphabet. */ +const id = (label: string) => + z + .string({ + required_error: `${label} is required.`, + invalid_type_error: `${label} must be a string.`, + }) + .min(1, `${label} is required.`) + .max(64, `${label} is not a valid identifier.`) + .regex(/^[A-Za-z0-9_-]+$/, `${label} is not a valid identifier.`); + +/** + * Free text a human typed, which will be stored and re-displayed. + * + * Control characters are REFUSED, not stripped. This is not a payroll figure, but + * it is still the caller's words: quietly altering them while reporting success + * means the stored value is not what was sent. + */ +const shortText = (label: string, max: number) => + z + .string() + .max(max, `${label} must be ${max} characters or fewer.`) + .refine((v) => !containsControlChars(v), { + message: `${label} contains control characters.`, + }); + +/** Naming the organization to act in. Never a claim of membership or role. */ +const orgId = id('orgId').optional(); + +/** + * The uploaded file, as text. + * + * Capped here as well as in the parser. The parser's cap protects the parser; + * this one refuses the request before a megabyte of attacker-chosen text is held + * in memory and walked character by character. + */ +const csvText = z + .string({ + required_error: 'A CSV file is required.', + invalid_type_error: 'csv must be a string.', + }) + .min(1, 'The CSV file is empty.') + .max(MAX_CSV_BYTES, `The CSV file exceeds the ${Math.round(MAX_CSV_BYTES / 1024)} KB limit.`); + +const filename = shortText('filename', 255).optional(); + +/** + * Client-supplied retry key. + * + * Opaque to the server β€” compared, never interpreted. Bounded so it cannot become + * a channel for storing arbitrary data on the batch row. + */ +const idempotencyKey = z + .string() + .min(8, 'An idempotency key must be at least 8 characters.') + .max(128, 'An idempotency key must be 128 characters or fewer.') + .regex(/^[A-Za-z0-9._:-]+$/, 'An idempotency key may use letters, digits and . _ : - only.') + .optional(); + +/** Stateless CSV validation. Writes nothing, so it takes no reference or project. */ +export const validateCsvRequest = z + .object({ + csv: csvText, + filename, + orgId, + /** + * Whether a repeated payee is an error. Default true. Exposed because a + * legitimate payroll can pay one wallet for two projects, and the uploader is + * the only one who knows which case this is. + */ + rejectDuplicateRecipients: z.boolean().optional(), + }) + .strict(); + +export type ValidateCsvRequest = z.infer; + +/** Create a draft batch and one payment per valid row. */ +export const createBatchRequest = z + .object({ + csv: csvText, + filename, + orgId, + /** Human-facing label. Generated sequentially when omitted. */ + reference: shortText('reference', 64).optional(), + projectId: id('projectId').optional(), + /** Also accepted as the `Idempotency-Key` header, which takes precedence. */ + idempotencyKey, + rejectDuplicateRecipients: z.boolean().optional(), + }) + .strict(); + +export type CreateBatchRequest = z.infer; + +/** Re-validate an existing draft batch. Writes nothing. */ +export const revalidateBatchRequest = z.object({ orgId }).strict(); + +/** + * Record the caller's approval across a batch. + * + * There is no `role` field. Which half of the dual-approval gate this exercises + * is derived from the caller's membership β€” taking it from the body would let one + * manager send `{"role":"FINANCE"}` and satisfy both halves alone, which is + * exactly what the contract refuses with SignersNotDistinct. + * + * There is no payment-state field either. The state machine owns state. + */ +export const approveBatchRequest = z + .object({ + orgId, + reason: shortText('reason', 500).optional(), + /** + * Payments to act on. Omitted means every payment in the batch awaiting this + * caller's approval. Naming them lets a reviewer approve part of a batch after + * querying a row, and caps the blast radius of a mis-click. + */ + paymentIds: z.array(id('paymentId')).min(1).max(MAX_ROWS).optional(), + idempotencyKey, + }) + .strict(); + +export type ApproveBatchRequest = z.infer; + +/** Listing filters. Query-string sourced, so every value arrives as a string. */ +export const listBatchesQuery = z + .object({ + limit: z.coerce.number().int().min(1).max(100).optional(), + cursor: id('cursor').optional(), + projectId: id('projectId').optional(), + }) + .strict(); + +export interface FieldIssue { + /** 1-based CSV line, where the issue came from a file row. */ + row?: number; + /** Request field path, e.g. `reference`, or a CSV column name. */ + field?: string; + code: string; + message: string; +} + +/** + * Render a Zod failure as field issues. + * + * Every path and message derives from the caller's own input and from the schema, + * never from server state, so all of it is safe to return. + */ +export function zodIssues(error: z.ZodError): FieldIssue[] { + return error.issues.map((i) => ({ + field: i.path.length > 0 ? i.path.join('.') : undefined, + code: i.code === 'unrecognized_keys' ? 'UNKNOWN_FIELD' : 'INVALID_FIELD', + message: i.message, + })); +} + +// ── Funding ───────────────────────────────────────────────────────────────── +// +// None of these carries an amount, a recipient, an asset, a manager or a finance +// approver. All of that comes from the plan the server froze when the intent was +// opened. A client that could restate them could fund something other than what +// was reviewed. + +/** Open (or recover) the funding intent for a batch. */ +export const fundingIntentRequest = z.object({ orgId }).strict(); + +/** A Stellar transaction hash: 32 bytes, lower-case hex. */ +const transactionHash = z + .string() + .regex(/^[0-9a-f]{64}$/, 'A transaction hash must be 64 lower-case hexadecimal characters.'); + +/** Record that a signed transaction reached the network. */ +export const fundingSubmittedRequest = z + .object({ + orgId, + attemptId: id('attemptId'), + transactionHash, + }) + .strict(); + +/** Verify a submitted transaction against the stored plan. */ +export const fundingConfirmRequest = z + .object({ + orgId, + attemptId: id('attemptId'), + /** + * The escrow id the client believes the contract returned. OPTIONAL and + * advisory. + * + * The server resolves the escrow from the transaction hash itself, so + * confirmation works when the client could not parse the return value β€” nobody + * has to sign a second funding transaction to discover the id. When supplied it + * is only cross-checked: a value that disagrees with the transaction's own + * event is a MISMATCH, so transaction A cannot adopt an escrow from B. + */ + onChainEscrowId: z.coerce.number().int().positive().max(2_147_483_647).optional(), + }) + .strict(); + +/** Abandon an attempt that never reached the network. */ +export const fundingAbandonRequest = z + .object({ + orgId, + attemptId: id('attemptId'), + reason: shortText('reason', 500), + /** True when the signer declined in their wallet. */ + userRejected: z.boolean().optional(), + }) + .strict(); diff --git a/src/lib/reconciliation/__tests__/live-reconciliation.test.ts b/src/lib/reconciliation/__tests__/live-reconciliation.test.ts new file mode 100644 index 0000000..f8706c2 --- /dev/null +++ b/src/lib/reconciliation/__tests__/live-reconciliation.test.ts @@ -0,0 +1,279 @@ +// @vitest-environment node +/** + * Live reconciliation validation against the deployed v2 Testnet contract. + * + * OPT-IN (COREFLOW_LIVE_TESTNET=1): needs real Soroban RPC and real Postgres. + * + * Covers, on real chain data: + * 1. successful reconciliation of a settled multi-payee batch + * 2. repeated runs are idempotent + * 3. interrupted indexing, then reconciliation recovers the projection + * 4. an unattributed escrow is reported, never auto-attached + * 5. a DATABASE_AHEAD mismatch is detected and NOT reverted + * + * Case 5 uses a synthetic payment inside a throwaway test organization, pointing + * at a real escrow slot that was never settled. Nothing is written through a + * production settlement path, and the organization is deleted afterwards. + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { + PaymentState, OrgRole, MembershipStatus, + FindingKind, FindingSeverity, FindingStatus, RunStatus, +} from '@prisma/client'; + +const LIVE = process.env.COREFLOW_LIVE_TESTNET === '1'; +const SLUG = 'live-reconcile-test'; + +describe.skipIf(!LIVE)('reconciliation β€” live v2 Testnet', () => { + let prisma: any; + let verifier: any; + let orgId: string; + let contractId: string; + let network: string; + let settledEscrowOnChainId: number; + + beforeAll(async () => { + prisma = (await import('@/lib/db/prisma')).default; + const { createRpcVerifier } = await import('../chain-verifier'); + const { STELLAR_CONFIG } = await import('@/lib/config'); + verifier = createRpcVerifier(); + contractId = STELLAR_CONFIG.contract.id; + network = STELLAR_CONFIG.contract.network; + + const org = await prisma.organization.upsert({ + where: { slug: SLUG }, + create: { name: 'Live Reconciliation Test', slug: SLUG }, + update: {}, + }); + orgId = org.id; + const user = await prisma.user.upsert({ + where: { walletAddress: 'GLIVERECON' }, + create: { walletAddress: 'GLIVERECON' }, + update: {}, + }); + await prisma.orgMember.upsert({ + where: { orgId_userId: { orgId, userId: user.id } }, + create: { orgId, userId: user.id, role: OrgRole.OWNER, status: MembershipStatus.ACTIVE }, + update: { status: MembershipStatus.ACTIVE }, + }); + }); + + afterAll(async () => { + if (prisma && orgId) { + await prisma.organization.delete({ where: { id: orgId } }).catch(() => {}); + } + }); + + it('indexes a settled batch, then reconciles it as AGREED', async () => { + const { runIndexerFromRpc } = await import('@/lib/indexer/run'); + const { runReconciliation } = await import('../scheduler'); + + // Fresh read of the whole retained window. + await prisma.chainEvent.deleteMany({}); + await prisma.indexerCursor.deleteMany({}); + + // Discover a settled escrow from the log, then claim it for the test tenant. + const firstPass = await runIndexerFromRpc(); + console.log('INDEX PASS 1:', JSON.stringify(firstPass)); + + const paidEvent = await prisma.chainEvent.findFirst({ + where: { type: 'payment_paid' }, + orderBy: { ledger: 'desc' }, + }); + expect(paidEvent, 'expected a settled escrow in the retained log').toBeTruthy(); + settledEscrowOnChainId = paidEvent.escrowOnChainId; + + await prisma.escrow.create({ + data: { + orgId, onChainId: settledEscrowOnChainId, contractId, network, + managerAddress: 'GLIVERECON', financeApproverAddress: 'GLIVEFIN', + assetDecimals: 7, + }, + }); + + const secondPass = await runIndexerFromRpc(); + console.log('INDEX PASS 2 (after claim):', JSON.stringify(secondPass)); + + const payments = await prisma.payment.findMany({ + where: { orgId }, orderBy: { onChainPaymentIndex: 'asc' }, + }); + expect(payments.length).toBeGreaterThanOrEqual(3); + expect(payments.every((p: any) => p.state === PaymentState.PAID)).toBe(true); + + // Reconcile: the TOKEN's own transfer events must corroborate every payment. + const run = await runReconciliation(prisma, orgId, { + verifier, contractId, network, maxEscrows: 10, + }); + if ('skipped' in run) throw new Error('unexpected skip'); + console.log('RECONCILE:', JSON.stringify(run)); + + expect(run.status).toBe(RunStatus.COMPLETED); + expect(run.paymentsExamined).toBeGreaterThanOrEqual(3); + expect(run.agreed).toBeGreaterThanOrEqual(3); + expect(run.databaseAhead).toBe(0); + expect(run.mismatched).toBe(0); + + const findings = await prisma.reconciliationFinding.findMany({ where: { orgId } }); + console.log(`findings: ${findings.length}`); + for (const f of findings) console.log(` ${f.severity} ${f.kind}: ${f.detail}`); + // Any finding other than an unattributed-escrow report would be a real problem. + const unexpected = findings.filter( + (f: any) => f.kind !== FindingKind.UNKNOWN_ON_CHAIN_OBJECT + ); + expect(unexpected).toHaveLength(0); + }, 600_000); + + it('is idempotent across repeated runs', async () => { + const { runReconciliation } = await import('../scheduler'); + + const before = await prisma.reconciliationFinding.count({ where: { orgId } }); + const run = await runReconciliation(prisma, orgId, { + verifier, contractId, network, maxEscrows: 10, + }); + if ('skipped' in run) throw new Error('unexpected skip'); + + expect(run.correctionsApplied).toBe(0); + expect(await prisma.reconciliationFinding.count({ where: { orgId } })).toBe(before); + + // Two runs exist, both completed: the lock was released, not leaked. + const runs = await prisma.reconciliationRun.findMany({ where: { orgId } }); + expect(runs.length).toBeGreaterThanOrEqual(2); + expect(runs.every((r: any) => r.status === RunStatus.COMPLETED)).toBe(true); + }, 300_000); + + it('recovers a projection left behind by an interrupted indexer', async () => { + const { runReconciliation } = await import('../scheduler'); + + // Simulate the indexer having missed the settlement events: roll one payment + // back to CONFIRMING, as if confirmation was never observed. + const target = await prisma.payment.findFirst({ + where: { orgId, state: PaymentState.PAID }, + orderBy: { onChainPaymentIndex: 'asc' }, + }); + expect(target).toBeTruthy(); + await prisma.payment.update({ + where: { id: target.id }, + data: { state: PaymentState.CONFIRMING, settledAt: null, settlementTxHash: null }, + }); + + const run = await runReconciliation(prisma, orgId, { + verifier, contractId, network, maxEscrows: 10, + }); + if ('skipped' in run) throw new Error('unexpected skip'); + console.log('RECOVERY RUN:', JSON.stringify(run)); + + expect(run.chainAhead).toBe(1); + expect(run.correctionsApplied).toBe(1); + + const recovered = await prisma.payment.findUnique({ where: { id: target.id } }); + expect(recovered.state).toBe(PaymentState.PAID); + // Evidence, not a guess: the hash comes from the observed SAC transfer. + expect(recovered.settlementTxHash).toBeTruthy(); + expect(recovered.settledAt).toBeTruthy(); + + const audit = await prisma.auditEvent.findFirst({ + where: { paymentId: target.id, newState: PaymentState.PAID }, + orderBy: { createdAt: 'desc' }, + }); + expect(audit.actorSystem).toBe('reconciler'); + expect((audit.metadata as any).verifiedBy).toBe('sac-transfer-event'); + }, 300_000); + + it('reports an unattributed escrow without attaching it to any tenant', async () => { + const { reportUnattributedEscrows } = await import('../reconciler'); + + const unattributed = await prisma.chainEvent.count({ + where: { attributed: false, contractId, network }, + }); + expect(unattributed, 'expected unattributed events from other escrows').toBeGreaterThan(0); + + const run = await prisma.reconciliationRun.create({ + data: { + orgId, correlationId: `rec_live_${Date.now()}`, scope: 'orphan-check', + status: RunStatus.RUNNING, contractId, network, + }, + }); + const r = await reportUnattributedEscrows(prisma, orgId, { + id: run.id, correlationId: run.correlationId, + }, { contractId, network }); + + expect(r.unknown).toBeGreaterThan(0); + const f = await prisma.reconciliationFinding.findFirst({ + where: { orgId, kind: FindingKind.UNKNOWN_ON_CHAIN_OBJECT }, + }); + expect(f).toBeTruthy(); + expect(f.severity).toBe(FindingSeverity.LOW); + expect(f.remediation).toMatch(/will not guess an owner/i); + + // Nothing was attached: no escrow row was created for the unknown id. + const claimed = await prisma.escrow.count({ + where: { orgId, onChainId: f.escrowOnChainId }, + }); + expect(claimed).toBe(0); + + await prisma.reconciliationRun.update({ + where: { id: run.id }, data: { status: RunStatus.COMPLETED, completedAt: new Date() }, + }); + }, 300_000); + + it('detects a DATABASE_AHEAD mismatch and does NOT revert it', async () => { + const { runReconciliation } = await import('../scheduler'); + + // A synthetic payment in the throwaway test organization, pointing at a real + // escrow slot that was never settled. Written directly, NOT through any + // settlement path, and labelled so it cannot be mistaken for real payroll. + const escrow = await prisma.escrow.findFirst({ where: { orgId } }); + const batch = await prisma.payrollBatch.findFirst({ where: { orgId } }); + const fakeIndex = 97; + + const synthetic = await prisma.payment.create({ + data: { + orgId, + batchId: batch.id, + escrowId: escrow.id, + recipientAddress: 'GSYNTHETICTESTRECIPIENT' + 'X'.repeat(33), + onChainPaymentIndex: fakeIndex, + assetContractId: escrow.tokenAddress, + assetDecimals: 7, + amountBaseUnits: 12_345_000_000n, + rateBaseUnits: 1n, + hours: 12_345_000_000n, + // The mismatch under test: claims settled, chain has no such payment. + state: PaymentState.PAID, + settledAt: new Date(), + settlementTxHash: 'SYNTHETIC_TEST_HASH_NOT_A_REAL_TRANSACTION', + stateReason: 'SYNTHETIC TEST ROW β€” reconciliation mismatch validation', + }, + }); + + const run = await runReconciliation(prisma, orgId, { + verifier, contractId, network, maxEscrows: 10, + }); + if ('skipped' in run) throw new Error('unexpected skip'); + console.log('MISMATCH RUN:', JSON.stringify(run)); + + // The slot does not exist on-chain, so this surfaces as MISSING_ON_CHAIN. + const f = await prisma.reconciliationFinding.findFirst({ + where: { orgId, paymentId: synthetic.id, status: { not: FindingStatus.RESOLVED } }, + }); + expect(f, 'expected a finding for the synthetic payment').toBeTruthy(); + console.log(` detected: ${f.severity} ${f.kind} β€” ${f.detail}`); + expect([ + FindingKind.MISSING_ON_CHAIN, + FindingKind.DB_PAID_CHAIN_NOT, + ]).toContain(f.kind); + expect(f.remediation).toBeTruthy(); + + // PAID was NOT reverted: the finding is the record, and a terminal financial + // state is never rewritten to make the database look tidy. + const after = await prisma.payment.findUnique({ where: { id: synthetic.id } }); + expect(after.state).toBe(PaymentState.PAID); + expect(after.settlementTxHash).toBe('SYNTHETIC_TEST_HASH_NOT_A_REAL_TRANSACTION'); + + // And the real payments were unaffected by the synthetic one. + const real = await prisma.payment.findMany({ + where: { orgId, onChainPaymentIndex: { lt: 10 } }, + }); + expect(real.every((p: any) => p.state === PaymentState.PAID)).toBe(true); + }, 300_000); +}); diff --git a/src/lib/reconciliation/__tests__/reconciler.test.ts b/src/lib/reconciliation/__tests__/reconciler.test.ts new file mode 100644 index 0000000..66a45b5 --- /dev/null +++ b/src/lib/reconciliation/__tests__/reconciler.test.ts @@ -0,0 +1,656 @@ +// @vitest-environment node +/** + * Reconciliation tests. + * + * The property under test is restraint plus independence. Verification comes from + * the TOKEN contract's own transfer events β€” a source CoreFlow did not author β€” so + * a bug in how CoreFlow emits or parses its own events cannot validate itself. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { PaymentState, FindingKind, FindingSeverity, FindingStatus } from '@prisma/client'; +import { createFakeDb, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { + reconcileOrganization, reconcileFailedTransactions, + reportUnattributedEscrows, policyFor, +} from '../reconciler'; +import { + CHAIN_STATUS, matchSettlementTransfer, findAmountMismatchedTransfers, + type ChainVerifier, type ObservedTransfer, type ChainEscrowFacts, +} from '../chain-verifier'; + +const ORG = 'orgA'; +const ESCROW_CONTRACT = 'C' + 'E'.repeat(55); +const TOKEN = 'C' + 'T'.repeat(55); +const W1 = 'G' + '1'.repeat(55); +const W2 = 'G' + '2'.repeat(55); +const W3 = 'G' + '3'.repeat(55); + +let db: FakeDb; +const RUN = { id: 'run1', correlationId: 'rec_test' }; + +/** `pay_batch` settles every payee of an escrow in ONE transaction. */ +const SETTLEMENT_TX = 'TX_SETTLEMENT'; + +/** Golden-path figures: 1000 / 960 / 900 USDC at 7 decimals. */ +const P = [ + { index: 0, worker: W1, amount: 10_000_000_000n }, + { index: 1, worker: W2, amount: 9_600_000_000n }, + { index: 2, worker: W3, amount: 9_000_000_000n }, +]; + +function seedEscrow( + onChainId: number, + payments: { index: number; worker: string; amount: bigint; state: PaymentState }[] +) { + db.__tables.escrow.rows.push({ + id: `esc_${onChainId}`, orgId: ORG, onChainId, + contractId: ESCROW_CONTRACT, network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', + assetDecimals: 7, totalAmountBaseUnits: 0n, + managerApproved: true, financeApproved: true, cancelled: false, + createdAt: new Date(), + }); + db.__tables.payrollBatch.rows.push({ id: `bat_${onChainId}`, orgId: ORG, reference: `R${onChainId}` }); + for (const p of payments) { + db.__tables.payment.rows.push({ + id: `pay_${onChainId}_${p.index}`, orgId: ORG, + batchId: `bat_${onChainId}`, escrowId: `esc_${onChainId}`, + recipientAddress: p.worker, onChainPaymentIndex: p.index, + assetContractId: TOKEN, assetCode: 'USDC', assetDecimals: 7, + amountBaseUnits: p.amount, rateBaseUnits: 1n, hours: p.amount, + state: p.state, stateUpdatedAt: new Date(), createdAt: new Date(), + // A settled payment records the transaction that settled it. Recording a + // hash with no corresponding transfer is a separate scenario, tested below. + settlementTxHash: p.state === PaymentState.PAID ? SETTLEMENT_TX : null, + }); + } +} + +function chainEscrow( + onChainId: number, + payments: { index: number; worker: string; amount: bigint; status: number }[] +): ChainEscrowFacts { + return { + onChainId, manager: 'GM', financeApprover: 'GF', + managerApproved: true, financeApproved: true, cancelled: false, + payments: payments.map((p) => ({ + index: p.index, worker: p.worker, token: TOKEN, + amountBaseUnits: p.amount, hours: p.amount, + proofVerified: true, status: p.status, + })), + }; +} + +/** + * Settlement transfers as the TOKEN contract would report them. + * + * All transfers default to ONE transaction hash, because `pay_batch` settles every + * payee of an escrow in a single transaction. Giving each its own hash would be + * unrealistic and would hide the transaction-scoped matching this verifies. + */ +function transfers( + items: { to: string; amount: bigint; from?: string; tx?: string }[] +): ObservedTransfer[] { + return items.map((i, n) => ({ + from: i.from ?? ESCROW_CONTRACT, + to: i.to, + assetContractId: TOKEN, + amountBaseUnits: i.amount, + ledger: 1000 + n, + txHash: i.tx ?? SETTLEMENT_TX, + })); +} + +/** Transfers from an EARLIER, identical pay period β€” same payees, same amounts. */ +function priorPeriodTransfers(): ObservedTransfer[] { + return P.map((p, n) => ({ + from: ESCROW_CONTRACT, + to: p.worker, + assetContractId: TOKEN, + amountBaseUnits: p.amount, + ledger: 500 + n, + txHash: 'TX_LAST_MONTH', + })); +} + +function verifier(opts: { + escrow?: ChainEscrowFacts | 'unreadable' | 'notfound'; + transfers?: ObservedTransfer[] | 'unreadable'; + txSucceeded?: boolean | 'unreadable' | 'notfound'; +}): ChainVerifier { + return { + async latestLedger() { return { ok: true, value: 9999 }; }, + async readEscrow() { + if (opts.escrow === 'unreadable') { + return { ok: false, error: { kind: 'UNREADABLE', reason: 'rpc timeout' } }; + } + if (opts.escrow === 'notfound' || !opts.escrow) { + return { ok: false, error: { kind: 'NOT_FOUND', reason: 'no such escrow' } }; + } + return { ok: true, value: opts.escrow }; + }, + async readTransfers() { + if (opts.transfers === 'unreadable') { + return { ok: false, error: { kind: 'UNREADABLE', reason: 'rpc timeout' } }; + } + return { ok: true, value: opts.transfers ?? [] }; + }, + async readTransactionSucceeded() { + if (opts.txSucceeded === 'unreadable') { + return { ok: false, error: { kind: 'UNREADABLE', reason: 'rpc down' } }; + } + if (opts.txSucceeded === 'notfound') { + return { ok: false, error: { kind: 'NOT_FOUND', reason: 'tx not found' } }; + } + return { ok: true, value: opts.txSucceeded ?? false }; + }, + }; +} + +const findings = () => db.__tables.reconciliationFinding.rows; +const payment = (id: string) => db.__tables.payment.rows.find((p) => p.id === id)!; +const kinds = () => findings().map((f) => f.kind); + +beforeEach(() => { + db = createFakeDb(); + db.__tables.organization.rows.push({ id: ORG, name: 'A', slug: 'a' }); + db.__tables.reconciliationRun.rows.push({ + id: RUN.id, orgId: ORG, correlationId: RUN.correlationId, scope: 'organization', status: 'RUNNING', + }); +}); + +describe('AGREED', () => { + it('opens no findings when chain, transfers and projection all agree', async () => { + seedEscrow(1, P.map((p) => ({ ...p, state: PaymentState.PAID }))); + const v = verifier({ + escrow: chainEscrow(1, P.map((p) => ({ ...p, status: CHAIN_STATUS.FINALIZED }))), + transfers: transfers(P.map((p) => ({ to: p.worker, amount: p.amount }))), + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.paymentsExamined).toBe(3); + expect(t.agreed).toBe(3); + expect(t.mismatched).toBe(0); + expect(t.findingsOpened).toBe(0); + expect(findings()).toHaveLength(0); + }); +}); + +describe('independence from CoreFlow’s own events', () => { + it('refuses to confirm settlement the contract claims but no transfer supports', async () => { + // This is the whole point. The contract says FINALIZED β€” which is exactly what + // the indexer would have trusted β€” but the TOKEN reports no matching transfer. + // A reconciler reusing the indexer's source would have agreed. + seedEscrow(2, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ + escrow: chainEscrow(2, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: [], // no asset movement + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.mismatched).toBe(1); + expect(kinds()).toContain(FindingKind.MISSING_PAYMENT_EVENT); + // Crucially: the projection was NOT advanced on contract state alone. + expect(payment('pay_2_0').state).toBe(PaymentState.CONFIRMING); + expect(t.correctionsApplied).toBe(0); + }); + + it('matches a transfer only on from, to, asset AND exact amount', () => { + const all = [ + ...transfers([{ to: W1, amount: 10_000_000_000n }]), + ...transfers([{ to: W1, amount: 9_999_999_999n }]), // wrong amount + ...transfers([{ to: W2, amount: 10_000_000_000n }]), // wrong recipient + ...transfers([{ to: W1, amount: 10_000_000_000n, from: 'GSOMEONE' }]), // wrong source + ]; + const matched = matchSettlementTransfer(all, { + escrowContractId: ESCROW_CONTRACT, recipient: W1, + assetContractId: TOKEN, amountBaseUnits: 10_000_000_000n, + }); + expect(matched).toHaveLength(1); + }); + + it('reports a wrong-amount transfer as a mismatch, not as absence', () => { + // "Money went to the right person in the wrong quantity" is a louder fact + // than "no settlement found". + const all = transfers([{ to: W1, amount: 8_600_000_000n }]); + expect( + findAmountMismatchedTransfers(all, { + escrowContractId: ESCROW_CONTRACT, recipient: W1, + assetContractId: TOKEN, amountBaseUnits: 10_000_000_000n, + }) + ).toHaveLength(1); + }); +}); + +describe('CHAIN_AHEAD recovery', () => { + it('advances the projection when a transfer independently confirms settlement', async () => { + seedEscrow(3, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ + escrow: chainEscrow(3, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: transfers([{ to: W1, amount: P[0].amount, tx: 'TX_REAL' }]), + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.chainAhead).toBe(1); + expect(t.correctionsApplied).toBe(1); + const p = payment('pay_3_0'); + expect(p.state).toBe(PaymentState.PAID); + expect(p.settlementTxHash).toBe('TX_REAL'); + expect(p.settledAt).toBeTruthy(); + }); + + it('attributes the correction to the reconciler and records the evidence', async () => { + seedEscrow(4, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ + escrow: chainEscrow(4, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: transfers([{ to: W1, amount: P[0].amount }]), + }); + await reconcileOrganization(db, v, ORG, RUN); + + const e = db.__tables.auditEvent.rows.find((x) => x.newState === PaymentState.PAID); + expect(e.actorSystem).toBe('reconciler'); + expect(e.actorAddress).toBeNull(); + expect(e.metadata.verifiedBy).toBe('sac-transfer-event'); + expect(e.metadata.correlationId).toBe(RUN.correlationId); + }); + + it('creates no second payment when correcting', async () => { + seedEscrow(5, P.map((p) => ({ ...p, state: PaymentState.CONFIRMING }))); + const v = verifier({ + escrow: chainEscrow(5, P.map((p) => ({ ...p, status: CHAIN_STATUS.FINALIZED }))), + transfers: transfers(P.map((p) => ({ to: p.worker, amount: p.amount }))), + }); + await reconcileOrganization(db, v, ORG, RUN); + expect(db.__tables.payment.rows).toHaveLength(3); + }); + + it('is idempotent across repeated runs', async () => { + seedEscrow(6, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ + escrow: chainEscrow(6, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: transfers([{ to: W1, amount: P[0].amount }]), + }); + + const first = await reconcileOrganization(db, v, ORG, RUN); + const second = await reconcileOrganization(db, v, ORG, RUN); + + expect(first.correctionsApplied).toBe(1); + expect(second.correctionsApplied).toBe(0); + expect(second.agreed).toBe(1); + expect(findings()).toHaveLength(0); + const transitions = db.__tables.auditEvent.rows.filter( + (e) => e.newState === PaymentState.PAID + ); + expect(transitions).toHaveLength(1); + }); +}); + +describe('DATABASE_AHEAD is never reverted', () => { + it('records a CRITICAL finding and leaves PAID in place', async () => { + // The worst finding in the system: we are telling a finance team money moved. + // Reverting it would destroy the evidence of our own worst bug. + seedEscrow(7, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(7, [{ ...P[0], status: CHAIN_STATUS.PENDING }]), + transfers: [], + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.databaseAhead).toBe(1); + const f = findings().find((x) => x.kind === FindingKind.DB_PAID_CHAIN_NOT); + expect(f).toBeDefined(); + expect(f.severity).toBe(FindingSeverity.CRITICAL); + expect(f.dbState).toBe('PAID'); + expect(f.remediation).toMatch(/Do not rely on the payment record/i); + // Unchanged. + expect(payment('pay_7_0').state).toBe(PaymentState.PAID); + expect(t.correctionsApplied).toBe(0); + }); + + it('does not fabricate a transaction hash', async () => { + seedEscrow(8, [{ ...P[0], state: PaymentState.PAID }]); + const before = payment('pay_8_0').settlementTxHash; + const v = verifier({ + escrow: chainEscrow(8, [{ ...P[0], status: CHAIN_STATUS.PENDING }]), + transfers: [], + }); + await reconcileOrganization(db, v, ORG, RUN); + expect(payment('pay_8_0').settlementTxHash).toBe(before); + }); +}); + +describe('CHAIN_UNREADABLE is not agreement', () => { + it('records unreadable rather than agreeing when the escrow cannot be read', async () => { + seedEscrow(9, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ escrow: 'unreadable' }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.unreadable).toBe(1); + expect(t.agreed).toBe(0); + expect(t.mismatched).toBe(0); + expect(kinds()).toContain(FindingKind.CHAIN_UNREADABLE); + expect(payment('pay_9_0').state).toBe(PaymentState.PAID); + }); + + it('does not advance a payment when transfers cannot be read', async () => { + // Advancing on contract state alone would defeat the independent check. + seedEscrow(10, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ + escrow: chainEscrow(10, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: 'unreadable', + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.unreadable).toBe(1); + expect(t.chainAhead).toBe(0); + expect(payment('pay_10_0').state).toBe(PaymentState.CONFIRMING); + const f = findings().find((x) => x.kind === FindingKind.CHAIN_UNREADABLE); + expect(f.detail).toMatch(/NOT advanced/i); + }); + + it('does not mark anything failed because of a timeout', async () => { + seedEscrow(11, [{ ...P[0], state: PaymentState.CONFIRMING }]); + const v = verifier({ escrow: 'unreadable' }); + await reconcileOrganization(db, v, ORG, RUN); + expect(payment('pay_11_0').state).toBe(PaymentState.CONFIRMING); + expect(payment('pay_11_0').state).not.toBe(PaymentState.SETTLEMENT_FAILED); + }); + + it('distinguishes an absent escrow from an unreadable one', async () => { + seedEscrow(12, [{ ...P[0], state: PaymentState.READY_TO_SETTLE }]); + const v = verifier({ escrow: 'notfound' }); + await reconcileOrganization(db, v, ORG, RUN); + expect(kinds()).toContain(FindingKind.MISSING_ON_CHAIN); + expect(kinds()).not.toContain(FindingKind.CHAIN_UNREADABLE); + }); +}); + +describe('batch-level verification does not hide payment-level errors', () => { + it('catches individual mismatches even when the batch total matches', async () => { + // The brief's case: 1000/960/900 recorded, 1000/860/1000 settled. Total is + // 2860 either way. Aggregate equality would report everything fine. + seedEscrow(13, P.map((p) => ({ ...p, state: PaymentState.PAID }))); + const settledWrong = [ + { index: 0, worker: W1, amount: 10_000_000_000n, status: CHAIN_STATUS.FINALIZED }, + { index: 1, worker: W2, amount: 8_600_000_000n, status: CHAIN_STATUS.FINALIZED }, + { index: 2, worker: W3, amount: 10_000_000_000n, status: CHAIN_STATUS.FINALIZED }, + ]; + const dbTotal = P.reduce((a, p) => a + p.amount, 0n); + const chainTotal = settledWrong.reduce((a, p) => a + p.amount, 0n); + expect(chainTotal).toBe(dbTotal); // totals agree + + const v = verifier({ + escrow: chainEscrow(13, settledWrong), + transfers: transfers(settledWrong.map((p) => ({ to: p.worker, amount: p.amount }))), + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + // Two individual payments disagree and both are reported. + const amountFindings = findings().filter((f) => f.kind === FindingKind.AMOUNT_MISMATCH); + expect(amountFindings.length).toBeGreaterThanOrEqual(2); + expect(t.mismatched).toBeGreaterThanOrEqual(2); + // And a mismatched payment is NOT also counted as agreed. Reporting + // "3 of 3 agreed" for a batch with two wrong amounts would be worse than + // reporting nothing. + expect(t.agreed).toBe(1); + expect(t.agreed + t.mismatched).toBeLessThanOrEqual(3 + amountFindings.length); + }); +}); + +describe('identity mismatches', () => { + it('flags a recipient mismatch', async () => { + seedEscrow(14, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(14, [{ index: 0, worker: W2, amount: P[0].amount, status: CHAIN_STATUS.FINALIZED }]), + transfers: transfers([{ to: W2, amount: P[0].amount }]), + }); + await reconcileOrganization(db, v, ORG, RUN); + const f = findings().find((x) => x.kind === FindingKind.RECIPIENT_MISMATCH); + expect(f.dbState).toBe(W1); + expect(f.chainState).toBe(W2); + expect(f.severity).toBe(FindingSeverity.HIGH); + }); + + it('flags an asset mismatch', async () => { + seedEscrow(15, [{ ...P[0], state: PaymentState.PAID }]); + const other = 'C' + 'X'.repeat(55); + const chain = chainEscrow(15, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]); + chain.payments[0].token = other; + const v = verifier({ escrow: chain, transfers: [] }); + await reconcileOrganization(db, v, ORG, RUN); + expect(kinds()).toContain(FindingKind.ASSET_MISMATCH); + }); + + it('flags duplicate transfers as possible double payment', async () => { + seedEscrow(16, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(16, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + // Two transfers for one payee inside ONE pay_batch β€” the genuine + // double-payment condition, as opposed to the same payroll run twice. + transfers: transfers([ + { to: W1, amount: P[0].amount, tx: SETTLEMENT_TX }, + { to: W1, amount: P[0].amount, tx: SETTLEMENT_TX }, + ]), + }); + await reconcileOrganization(db, v, ORG, RUN); + const f = findings().find((x) => x.kind === FindingKind.DUPLICATE_PAYMENT_EVENT); + expect(f.detail).toMatch(/paid twice/i); + expect(f.severity).toBe(FindingSeverity.HIGH); + }); +}); + +describe('recurring payroll is not a duplicate payment', () => { + it('does not flag a repeat of an identical pay period as a double payment', async () => { + // THE LIVE-CAUGHT BUG. The tuple (escrow contract, recipient, asset, amount) + // repeats every pay period, so an unscoped match found one transfer per + // historical settlement and reported duplicate payments that never happened. + seedEscrow(30, P.map((p) => ({ ...p, state: PaymentState.PAID }))); + const v = verifier({ + escrow: chainEscrow(30, P.map((p) => ({ ...p, status: CHAIN_STATUS.FINALIZED }))), + transfers: [ + ...priorPeriodTransfers(), // last month + ...priorPeriodTransfers().map((t) => ({ ...t, txHash: 'TX_TWO_MONTHS_AGO', ledger: 200 })), + ...transfers(P.map((p) => ({ to: p.worker, amount: p.amount }))), // this month + ], + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(kinds()).not.toContain(FindingKind.DUPLICATE_PAYMENT_EVENT); + expect(t.agreed).toBe(3); + expect(t.mismatched).toBe(0); + }); + + it('still confirms settlement when the payment already records its transaction', async () => { + seedEscrow(31, P.map((p) => ({ ...p, state: PaymentState.PAID }))); + // Payments carry the settlement tx from seedEscrow; the observed transfers + // for this period are under that same hash, and an earlier period is not. + const v = verifier({ + escrow: chainEscrow(31, P.map((p) => ({ ...p, status: CHAIN_STATUS.FINALIZED }))), + transfers: [ + ...priorPeriodTransfers(), + ...transfers(P.map((p) => ({ to: p.worker, amount: p.amount, tx: SETTLEMENT_TX }))), + ], + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(t.agreed).toBe(3); + expect(kinds()).not.toContain(FindingKind.DUPLICATE_PAYMENT_EVENT); + }); +}); + +describe('a recorded transaction with no matching transfer', () => { + it('flags a payment whose recorded hash has no corresponding transfer', async () => { + // The DB names a settlement transaction; the token reports no such movement. + // Scoping to that transaction is what makes this detectable at all. + seedEscrow(32, [{ ...P[0], state: PaymentState.PAID }]); + db.__tables.payment.rows.find((p) => p.id === 'pay_32_0')!.settlementTxHash = 'HASH_NOT_ON_CHAIN'; + + const v = verifier({ + escrow: chainEscrow(32, [{ ...P[0], status: CHAIN_STATUS.FINALIZED }]), + transfers: transfers([{ to: W1, amount: P[0].amount }]), // under a different tx + }); + + const t = await reconcileOrganization(db, v, ORG, RUN); + + expect(kinds()).toContain(FindingKind.MISSING_PAYMENT_EVENT); + expect(t.agreed).toBe(0); + // PAID is not reverted; the finding is the record. + expect(payment('pay_32_0').state).toBe(PaymentState.PAID); + }); +}); + +describe('orphans and missing slots', () => { + it('flags an on-chain payment with no database row', async () => { + seedEscrow(17, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(17, P.map((p) => ({ ...p, status: CHAIN_STATUS.FINALIZED }))), + transfers: transfers(P.map((p) => ({ to: p.worker, amount: p.amount }))), + }); + await reconcileOrganization(db, v, ORG, RUN); + expect(findings().filter((f) => f.kind === FindingKind.ORPHAN_ON_CHAIN)).toHaveLength(2); + }); + + it('reports unattributed escrows without attaching them to a tenant', async () => { + // Preserves the P2 #2 decision: never invent an owner. + db.__tables.chainEvent.rows.push( + { id: 'c1', contractId: ESCROW_CONTRACT, network: 'testnet', type: 'created', ledger: 10, escrowOnChainId: 77, attributed: false }, + { id: 'c2', contractId: ESCROW_CONTRACT, network: 'testnet', type: 'payment_added', ledger: 11, escrowOnChainId: 77, attributed: false } + ); + + const r = await reportUnattributedEscrows(db, ORG, RUN, { + contractId: ESCROW_CONTRACT, network: 'testnet', + }); + + // One finding for the escrow, not one per event. + expect(r.findingsOpened).toBe(1); + const f = findings()[0]; + expect(f.kind).toBe(FindingKind.UNKNOWN_ON_CHAIN_OBJECT); + expect(f.severity).toBe(FindingSeverity.LOW); + expect(f.remediation).toMatch(/will not guess an owner/i); + // No escrow, payment or organization was created. + expect(db.__tables.escrow.rows).toHaveLength(0); + expect(db.__tables.organization.rows).toHaveLength(1); + }); +}); + +describe('falsely-failed transactions', () => { + beforeEach(() => { + db.__tables.payment.rows.push({ + id: 'pay_x', orgId: ORG, batchId: 'b', escrowId: 'e', + recipientAddress: W1, onChainPaymentIndex: 0, + amountBaseUnits: 100n, rateBaseUnits: 1n, hours: 100n, + assetDecimals: 7, assetCode: 'USDC', + state: PaymentState.SETTLEMENT_FAILED, stateUpdatedAt: new Date(), createdAt: new Date(), + }); + db.__tables.blockchainTransaction.rows.push({ + id: 'btx1', orgId: ORG, paymentId: 'pay_x', kind: 'PAY_BATCH', + status: 'FAILED', idempotencyKey: 'k1', attempt: 1, hash: 'HASH_OK', + }); + }); + + it('detects a false failure and says DO NOT RETRY', async () => { + const r = await reconcileFailedTransactions(db, verifier({ txSucceeded: true }), ORG, RUN); + expect(r.falselyFailed).toBe(1); + const f = findings().find((x) => x.kind === FindingKind.FAILED_TX_ACTUALLY_SUCCEEDED); + expect(f.severity).toBe(FindingSeverity.CRITICAL); + expect(f.remediation).toMatch(/DO NOT RETRY/i); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('CONFIRMED'); + }); + + it('leaves a genuinely failed transaction alone', async () => { + const r = await reconcileFailedTransactions(db, verifier({ txSucceeded: false }), ORG, RUN); + expect(r.falselyFailed).toBe(0); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('FAILED'); + }); + + it.each(['unreadable', 'notfound'] as const)('leaves it alone when the chain says %s', async (mode) => { + const r = await reconcileFailedTransactions(db, verifier({ txSucceeded: mode }), ORG, RUN); + expect(r.falselyFailed).toBe(0); + expect(db.__tables.blockchainTransaction.rows[0].status).toBe('FAILED'); + }); +}); + +describe('finding deduplication', () => { + it('re-observes rather than duplicating an unchanged discrepancy', async () => { + // A queue that grows by a row per run per problem becomes noise, and noise + // gets ignored β€” the same as having no queue. + seedEscrow(18, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(18, [{ ...P[0], status: CHAIN_STATUS.PENDING }]), + transfers: [], + }); + + await reconcileOrganization(db, v, ORG, RUN); + const afterFirst = findings().length; + const second = await reconcileOrganization(db, v, ORG, RUN); + + expect(second.findingsOpened).toBe(0); + expect(findings()).toHaveLength(afterFirst); + const f = findings()[0]; + expect(f.observationCount).toBe(2); + expect(f.lastObservedAt).toBeInstanceOf(Date); + }); + + it('does not reset an acknowledgement when re-observing', async () => { + seedEscrow(19, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(19, [{ ...P[0], status: CHAIN_STATUS.PENDING }]), + transfers: [], + }); + await reconcileOrganization(db, v, ORG, RUN); + + const f = findings()[0]; + f.status = FindingStatus.ACKNOWLEDGED; + f.acknowledgedBy = 'GOPERATOR'; + + await reconcileOrganization(db, v, ORG, RUN); + + // Acknowledging a persistent finding must stay acknowledged, or it is + // impossible to acknowledge anything that recurs. + expect(findings()[0].status).toBe(FindingStatus.ACKNOWLEDGED); + expect(findings()[0].acknowledgedBy).toBe('GOPERATOR'); + }); + + it('opens a new finding once the previous one is resolved', async () => { + seedEscrow(20, [{ ...P[0], state: PaymentState.PAID }]); + const v = verifier({ + escrow: chainEscrow(20, [{ ...P[0], status: CHAIN_STATUS.PENDING }]), + transfers: [], + }); + await reconcileOrganization(db, v, ORG, RUN); + findings()[0].status = FindingStatus.RESOLVED; + findings()[0].resolvedAt = new Date(); + + const again = await reconcileOrganization(db, v, ORG, RUN); + + expect(again.findingsOpened).toBe(1); + expect(findings()).toHaveLength(2); + }); +}); + +describe('finding policy', () => { + it('reserves CRITICAL for false statements about money', () => { + expect(policyFor(FindingKind.DB_PAID_CHAIN_NOT).severity).toBe(FindingSeverity.CRITICAL); + expect(policyFor(FindingKind.FAILED_TX_ACTUALLY_SUCCEEDED).severity).toBe(FindingSeverity.CRITICAL); + // A lag is not a crisis. + expect(policyFor(FindingKind.CHAIN_PAID_DB_NOT).severity).not.toBe(FindingSeverity.CRITICAL); + expect(policyFor(FindingKind.CHAIN_UNREADABLE).severity).toBe(FindingSeverity.LOW); + }); + + it('gives every finding kind actionable remediation', () => { + for (const kind of Object.values(FindingKind)) { + const p = policyFor(kind); + expect(p.remediation.length, kind).toBeGreaterThan(30); + expect(p.severity, kind).toBeTruthy(); + } + }); +}); diff --git a/src/lib/reconciliation/__tests__/scheduler.test.ts b/src/lib/reconciliation/__tests__/scheduler.test.ts new file mode 100644 index 0000000..2454b1e --- /dev/null +++ b/src/lib/reconciliation/__tests__/scheduler.test.ts @@ -0,0 +1,246 @@ +// @vitest-environment node +/** + * Scheduler tests: locking, heartbeats, run records, health. + * + * The lock is also enforced by a PostgreSQL partial unique index + * (`ReconciliationRun_one_running_per_org`), verified directly against the + * database β€” see docs/evidence/REVIEWER_EVIDENCE.md. These tests cover the + * application half: reclaiming an abandoned lock, and always closing out a run + * record even when the pass throws. + */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { RunStatus, FindingSeverity, FindingStatus, FindingKind, PaymentState } from '@prisma/client'; +import { createFakeDb, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { + startRun, heartbeat, runReconciliation, reconciliationHealth, + STALE_RUN_AFTER_MS, +} from '../scheduler'; +import type { ChainVerifier } from '../chain-verifier'; + +const ORG = 'orgA'; +let db: FakeDb; + +const okVerifier: ChainVerifier = { + async latestLedger() { return { ok: true, value: 100 }; }, + async readEscrow() { return { ok: false, error: { kind: 'NOT_FOUND', reason: 'none' } }; }, + async readTransfers() { return { ok: true, value: [] }; }, + async readTransactionSucceeded() { return { ok: true, value: false }; }, +}; + +const runs = () => db.__tables.reconciliationRun.rows; + +beforeEach(() => { + db = createFakeDb(); + db.__tables.organization.rows.push({ id: ORG, name: 'A', slug: 'a' }); +}); + +describe('lock acquisition', () => { + it('starts a run and records a correlation id', async () => { + const r = await startRun(db, ORG, 'organization'); + expect(r.started).toBe(true); + if (r.started) { + expect(r.correlationId).toMatch(/^rec_/); + expect(runs()[0].status).toBe(RunStatus.RUNNING); + } + }); + + it('refuses a second run while one is live', async () => { + await startRun(db, ORG, 'organization'); + const second = await startRun(db, ORG, 'organization'); + expect(second.started).toBe(false); + if (!second.started) expect(second.reason).toBe('ALREADY_RUNNING'); + expect(runs().filter((r) => r.status === RunStatus.RUNNING)).toHaveLength(1); + }); + + it('allows a run once the previous completed', async () => { + const first = await startRun(db, ORG, 'organization'); + if (first.started) { + runs().find((r) => r.id === first.runId)!.status = RunStatus.COMPLETED; + } + expect((await startRun(db, ORG, 'organization')).started).toBe(true); + }); + + it('does not block a different organization', async () => { + db.__tables.organization.rows.push({ id: 'orgB', name: 'B', slug: 'b' }); + await startRun(db, ORG, 'organization'); + expect((await startRun(db, 'orgB', 'organization')).started).toBe(true); + }); +}); + +describe('stale lock reclaim', () => { + it('reclaims a lock whose holder stopped reporting', async () => { + // Otherwise one crashed worker blocks reconciliation forever, and the absence + // of findings reads as health. + const first = await startRun(db, ORG, 'organization'); + expect(first.started).toBe(true); + const row = runs()[0]; + row.heartbeatAt = new Date(Date.now() - STALE_RUN_AFTER_MS - 1000); + + const second = await startRun(db, ORG, 'organization'); + + expect(second.started).toBe(true); + // The dead run is marked STALE, not deleted: that a run died is evidence. + expect(row.status).toBe(RunStatus.STALE); + expect(row.errorMessage).toMatch(/stopped reporting a heartbeat/i); + expect(row.completedAt).toBeTruthy(); + }); + + it('does not steal a lock from a run that is still reporting', async () => { + const first = await startRun(db, ORG, 'organization'); + if (first.started) await heartbeat(db, first.runId); + const second = await startRun(db, ORG, 'organization'); + expect(second.started).toBe(false); + }); + + it('refreshes the heartbeat only for RUNNING runs', async () => { + const r = await startRun(db, ORG, 'organization'); + if (!r.started) throw new Error('expected start'); + runs()[0].status = RunStatus.COMPLETED; + const before = runs()[0].heartbeatAt; + await heartbeat(db, r.runId); + expect(runs()[0].heartbeatAt).toBe(before); + }); +}); + +describe('run lifecycle', () => { + it('completes the run record with counters', async () => { + const result = await runReconciliation(db, ORG, { verifier: okVerifier }); + expect('runId' in result).toBe(true); + if (!('runId' in result)) return; + expect(result.status).toBe(RunStatus.COMPLETED); + const row = runs()[0]; + expect(row.status).toBe(RunStatus.COMPLETED); + expect(row.completedAt).toBeTruthy(); + }); + + it('skips rather than overlapping when a run is in progress', async () => { + await startRun(db, ORG, 'organization'); + const result = await runReconciliation(db, ORG, { verifier: okVerifier }); + expect('skipped' in result).toBe(true); + if ('skipped' in result) expect(result.reason).toBe('ALREADY_RUNNING'); + }); + + it('closes out the run as FAILED when the pass throws', async () => { + // A run that simply stops existing is indistinguishable from one that never + // started, and "no findings" would then read as health. + const exploding: ChainVerifier = { + ...okVerifier, + async readEscrow() { throw new Error('rpc exploded'); }, + }; + db.__tables.escrow.rows.push({ + id: 'e1', orgId: ORG, onChainId: 1, contractId: 'C', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', assetDecimals: 7, + createdAt: new Date(), + }); + + const result = await runReconciliation(db, ORG, { verifier: exploding }); + + if (!('runId' in result)) throw new Error('expected a run'); + expect(result.status).toBe(RunStatus.FAILED); + expect(result.errorMessage).toMatch(/rpc exploded/); + const row = runs()[0]; + expect(row.status).toBe(RunStatus.FAILED); + expect(row.completedAt).toBeTruthy(); + expect(row.errorMessage).toMatch(/rpc exploded/); + }); + + it('releases the lock after a failure so the next run can proceed', async () => { + const exploding: ChainVerifier = { + ...okVerifier, + async readEscrow() { throw new Error('boom'); }, + }; + db.__tables.escrow.rows.push({ + id: 'e1', orgId: ORG, onChainId: 1, contractId: 'C', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', assetDecimals: 7, + createdAt: new Date(), + }); + await runReconciliation(db, ORG, { verifier: exploding }); + const next = await runReconciliation(db, ORG, { verifier: okVerifier }); + expect('runId' in next).toBe(true); + }); + + it('threads one correlation id through the whole run', async () => { + const result = await runReconciliation(db, ORG, { verifier: okVerifier }); + if (!('runId' in result)) throw new Error('expected a run'); + expect(runs()[0].correlationId).toBe(result.correlationId); + }); +}); + +describe('health reporting', () => { + function seedFinding(overrides: Record = {}) { + db.__tables.reconciliationFinding.rows.push({ + id: `f${db.__tables.reconciliationFinding.rows.length}`, + orgId: ORG, kind: FindingKind.DB_PAID_CHAIN_NOT, + status: FindingStatus.OPEN, severity: FindingSeverity.CRITICAL, + detectedAt: new Date(), lastObservedAt: new Date(), observationCount: 1, + ...overrides, + }); + } + + it('reports degraded when reconciliation has never run', async () => { + // Silence is not health. + const h = await reconciliationHealth(db, ORG); + expect(h.lastRun).toBeNull(); + expect(h.degraded).toBe(true); + expect(h.degradedReason).toMatch(/never run/i); + }); + + it('reports degraded after a failed run', async () => { + await runReconciliation(db, ORG, { + verifier: { ...okVerifier, async readEscrow() { throw new Error('x'); } }, + }); + db.__tables.escrow.rows.push({ + id: 'e1', orgId: ORG, onChainId: 1, contractId: 'C', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', assetDecimals: 7, createdAt: new Date(), + }); + runs()[0].status = RunStatus.FAILED; + runs()[0].errorMessage = 'x'; + + const h = await reconciliationHealth(db, ORG); + expect(h.degraded).toBe(true); + expect(h.degradedReason).toMatch(/failed/i); + }); + + it('reports degraded when a run appears to have stopped responding', async () => { + await startRun(db, ORG, 'organization'); + runs()[0].heartbeatAt = new Date(Date.now() - STALE_RUN_AFTER_MS - 1); + const h = await reconciliationHealth(db, ORG); + expect(h.degraded).toBe(true); + expect(h.degradedReason).toMatch(/stopped responding/i); + }); + + it('is not degraded after a clean completed run', async () => { + await runReconciliation(db, ORG, { verifier: okVerifier }); + const h = await reconciliationHealth(db, ORG); + expect(h.degraded).toBe(false); + expect(h.lastRun?.status).toBe(RunStatus.COMPLETED); + }); + + it('counts open and critical findings separately', async () => { + seedFinding(); + seedFinding({ severity: FindingSeverity.MEDIUM, kind: FindingKind.CHAIN_PAID_DB_NOT }); + seedFinding({ status: FindingStatus.RESOLVED, resolvedAt: new Date() }); + + const h = await reconciliationHealth(db, ORG); + expect(h.openFindings).toBe(2); + expect(h.criticalFindings).toBe(1); + }); + + it('reports how long the oldest unresolved finding has been open', async () => { + seedFinding({ detectedAt: new Date(Date.now() - 5 * 3_600_000) }); + const h = await reconciliationHealth(db, ORG); + expect(h.oldestUnresolvedHours).toBeGreaterThanOrEqual(4); + }); + + it('does not count another organization’s findings', async () => { + db.__tables.organization.rows.push({ id: 'orgB', name: 'B', slug: 'b' }); + db.__tables.reconciliationFinding.rows.push({ + id: 'fB', orgId: 'orgB', kind: FindingKind.DB_PAID_CHAIN_NOT, + status: FindingStatus.OPEN, severity: FindingSeverity.CRITICAL, + detectedAt: new Date(), lastObservedAt: new Date(), observationCount: 1, + }); + const h = await reconciliationHealth(db, ORG); + expect(h.openFindings).toBe(0); + expect(h.criticalFindings).toBe(0); + }); +}); diff --git a/src/lib/reconciliation/chain-verifier.ts b/src/lib/reconciliation/chain-verifier.ts new file mode 100644 index 0000000..fbe095d --- /dev/null +++ b/src/lib/reconciliation/chain-verifier.ts @@ -0,0 +1,438 @@ +/** + * Independent on-chain verification. + * + * ── Why this does not reuse the indexer ────────────────────────────────────── + * The indexer trusts CoreFlow's OWN events (`payment/paid`). A reconciler that + * re-read those same events, through the same parser, into the same projection + * would verify nothing: a bug in how CoreFlow emits or decodes its events would + * validate itself. That is the same trap the oracle preimage tests avoid by + * keeping a second, longhand implementation. + * + * So verification is derived from sources CoreFlow did not author: + * + * 1. **Stellar Asset Contract `transfer` events.** The TOKEN contract emits + * these β€” `transfer / from / to / asset β†’ amount`. They are the actual + * movement of value, independent of anything CoreFlow says about it. If + * CoreFlow claims a payment settled and no SAC transfer to that recipient + * for that amount exists, the claim is false regardless of what our own + * event log says. + * + * 2. **Contract storage via `get_escrow`.** A read of current state, not of the + * event stream. A projection built from events and a read of storage are two + * different derivations of the same truth. + * + * 3. **Transaction results.** Whether a specific hash actually succeeded. + * + * A discrepancy between (1) and CoreFlow's projection is therefore meaningful + * evidence, not a tautology. + */ + +import { STELLAR_CONFIG } from '@/lib/config'; + +/** One asset movement, as reported by the token contract itself. */ +export interface ObservedTransfer { + from: string; + to: string; + /** SAC contract address of the asset moved. */ + assetContractId: string; + amountBaseUnits: bigint; + ledger: number; + txHash?: string; +} + +export interface ChainPaymentFacts { + index: number; + worker: string; + token: string; + amountBaseUnits: bigint; + hours: bigint; + proofVerified: boolean; + /** PaymentStatus discriminant from the contract's #[repr(u32)] enum. */ + status: number; +} + +export interface ChainEscrowFacts { + onChainId: number; + manager: string; + financeApprover: string; + managerApproved: boolean; + financeApproved: boolean; + cancelled: boolean; + payments: ChainPaymentFacts[]; +} + +/** On-chain PaymentStatus discriminants. */ +export const CHAIN_STATUS = { + PENDING: 0, + MANAGER_APPROVED: 1, + FINANCE_APPROVED: 2, + FINALIZED: 3, + CANCELLED: 4, +} as const; + +/** + * Why a read failed. + * + * `UNREADABLE` is emphatically not `DISAGREES`. Treating a timeout as a mismatch + * would mark healthy payments as broken; treating it as agreement would let real + * drift accumulate unseen. The distinction is the whole point. + */ +export type VerificationError = + | { kind: 'UNREADABLE'; reason: string } + | { kind: 'NOT_FOUND'; reason: string }; + +export type Verified = { ok: true; value: T } | { ok: false; error: VerificationError }; + +export interface ChainVerifier { + /** Current contract storage for an escrow. */ + readEscrow(onChainId: number): Promise>; + /** + * Asset movements observed from the TOKEN contract's own events. + * + * `ledgerWindow` bounds the search. Soroban RPC retains only a limited event + * history, so a payment older than retention is UNREADABLE rather than absent β€” + * reporting "no transfer found" for data the node no longer holds would + * manufacture a false DB_PAID_CHAIN_NOT on every historical payment. + */ + readTransfers( + assetContractId: string, + opts: { fromLedger?: number; limit?: number } + ): Promise>; + /** Whether a specific transaction succeeded. */ + readTransactionSucceeded(hash: string): Promise>; + /** + * Escrow ids created by a specific transaction, from the contract's own + * `escrow/created` events. + * + * The recovery anchor for funding. A client that submits + * `initialize_multi_sig_escrow` and then fails to parse the return value still + * knows the transaction hash, and the hash is enough to learn which escrow that + * transaction created β€” so nobody has to sign a second one to find out. + * + * Returns every match so the caller can refuse ambiguity rather than pick. + */ + findEscrowsCreatedByTransaction( + hash: string, + opts: { fromLedger?: number } + ): Promise>; + /** Current ledger, for bounding windows. */ + latestLedger(): Promise>; +} + +/** How far back to look for transfers when no explicit window is given. */ +export const DEFAULT_TRANSFER_LOOKBACK_LEDGERS = 16_000; // ~22 hours at 5s/ledger + +/** Live verifier backed by Soroban RPC. */ +export function createRpcVerifier(): ChainVerifier { + const loadSdk = () => import('@stellar/stellar-sdk'); + + const unreadable = (e: unknown): VerificationError => ({ + kind: 'UNREADABLE', + reason: e instanceof Error ? e.message : String(e), + }); + + return { + async latestLedger() { + try { + const sdk: any = await loadSdk(); + const rpc = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + const latest = await rpc.getLatestLedger(); + return { ok: true, value: latest.sequence }; + } catch (e) { + return { ok: false, error: unreadable(e) }; + } + }, + + async readEscrow(onChainId: number) { + try { + const { CoreFlowClient } = await import('@/lib/contracts'); + const e = await new CoreFlowClient().getEscrow(onChainId); + return { + ok: true, + value: { + onChainId, + manager: e.manager, + financeApprover: e.finance_approver, + managerApproved: e.manager_approved, + financeApproved: e.finance_approved, + cancelled: e.cancelled, + payments: e.payments.map((p) => ({ + index: Number(p.id) - 1 >= 0 ? Number(p.id) - 1 : 0, + worker: p.worker, + token: p.token, + amountBaseUnits: p.amount, + hours: p.hours_logged, + proofVerified: p.proof_verified, + status: p.status, + })), + }, + }; + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + // The contract's own "no such escrow" is a genuine absence; anything else + // is a failure to read, and the two must not be conflated. + if (/InvalidPaymentId|Error\(Contract, #4\)/.test(msg)) { + return { ok: false, error: { kind: 'NOT_FOUND', reason: msg } }; + } + return { ok: false, error: unreadable(e) }; + } + }, + + async findEscrowsCreatedByTransaction(hash, opts) { + try { + const sdk: any = await loadSdk(); + const rpc = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + const contractId = STELLAR_CONFIG.requireContractId(); + + let startLedger = opts.fromLedger; + if (startLedger === undefined) { + const latest = await rpc.getLatestLedger(); + startLedger = Math.max(1, latest.sequence - DEFAULT_TRANSFER_LOOKBACK_LEDGERS); + } + + const found: number[] = []; + let cursor: string | undefined; + + for (let page = 0; page < 20; page++) { + const res = await rpc.getEvents({ + ...(cursor ? { cursor } : { startLedger }), + filters: [{ type: 'contract', contractIds: [contractId] }], + limit: 200, + }); + const events = res.events ?? []; + for (const ev of events) { + if (ev.txHash !== hash) continue; + const topics: string[] = (ev.topic ?? []).map((t: any) => { + try { + return String(sdk.scValToNative(t)); + } catch { + return ''; + } + }); + if (topics[0] !== 'escrow' || topics[1] !== 'created') continue; + try { + // (escrow_id, manager, total_amount) + const value = sdk.scValToNative(ev.value); + const id = Number(Array.isArray(value) ? value[0] : value); + if (Number.isInteger(id) && id > 0 && !found.includes(id)) found.push(id); + } catch { + // An undecodable event is not evidence; skip it rather than guess. + } + } + cursor = res.cursor; + if (!cursor || events.length === 0) break; + } + + return { ok: true, value: found }; + } catch (e) { + return { ok: false, error: unreadable(e) }; + } + }, + + async readTransfers(assetContractId, opts) { + try { + const sdk: any = await loadSdk(); + const rpc = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + + let startLedger = opts.fromLedger; + if (startLedger === undefined) { + const latest = await rpc.getLatestLedger(); + startLedger = Math.max(1, latest.sequence - DEFAULT_TRANSFER_LOOKBACK_LEDGERS); + } + + const out: ObservedTransfer[] = []; + let cursor: string | undefined; + + // Page until exhausted or capped: a settlement's transfers can be spread + // across pages, and stopping early would look like a missing transfer. + for (let page = 0; page < 20; page++) { + const res = await rpc.getEvents({ + ...(cursor ? { cursor } : { startLedger }), + filters: [{ type: 'contract', contractIds: [assetContractId] }], + limit: opts.limit ?? 200, + }); + + for (const ev of res.events ?? []) { + const topics = (ev.topic ?? []).map((t: any) => { + try { return sdk.scValToNative(t); } catch { return null; } + }); + if (String(topics[0]) !== 'transfer') continue; + + let amount: bigint; + try { + const raw = sdk.scValToNative(ev.value); + amount = typeof raw === 'bigint' ? raw : BigInt(String(raw)); + } catch { + continue; + } + + out.push({ + from: String(topics[1]), + to: String(topics[2]), + assetContractId, + amountBaseUnits: amount, + ledger: ev.ledger, + txHash: ev.txHash ?? ev.transactionHash ?? undefined, + }); + } + + if (!res.cursor || (res.events ?? []).length === 0) break; + cursor = res.cursor; + } + + return { ok: true, value: out }; + } catch (e) { + return { ok: false, error: unreadable(e) }; + } + }, + + async readTransactionSucceeded(hash: string) { + try { + const sdk: any = await loadSdk(); + const rpc = new sdk.rpc.Server(STELLAR_CONFIG.getRpcUrl()); + const tx = await rpc.getTransaction(hash); + if (tx.status === 'NOT_FOUND') { + // Beyond retention, or never submitted. Not the same as "failed": a + // transaction the node has forgotten may well have succeeded. + return { ok: false, error: { kind: 'NOT_FOUND', reason: `tx ${hash} not found` } }; + } + return { ok: true, value: tx.status === 'SUCCESS' }; + } catch (e) { + return { ok: false, error: unreadable(e) }; + } + }, + }; +} + +export interface SettlementExpectation { + escrowContractId: string; + recipient: string; + assetContractId: string; + amountBaseUnits: bigint; + /** + * The settlement transaction, when known. Supplying it makes the match exact. + * + * WITHOUT it the match is ambiguous across settlements, because the tuple + * (escrow contract, recipient, asset, amount) is NOT unique: the same contract + * pays the same contractor the same rate every pay period. Seven runs of an + * identical payroll produce seven identical transfers, and treating those as + * seven matches for one payment reported a duplicate payment that never + * happened. Scoping to one transaction is what makes the check precise. + */ + txHash?: string | null; +} + +/** + * Find the transfer(s) that settle a payment. + * + * Matched on (from = escrow contract, to = recipient, asset, exact amount), and β€” + * when a transaction is known β€” within that transaction only. All of those must + * hold: a transfer of the right amount to the wrong address, or the wrong amount + * to the right address, is not this payment settling. + */ +export function matchSettlementTransfer( + transfers: readonly ObservedTransfer[], + expect: SettlementExpectation +): ObservedTransfer[] { + const candidates = transfers.filter( + (t) => + t.from === expect.escrowContractId && + t.to === expect.recipient && + t.assetContractId === expect.assetContractId && + t.amountBaseUnits === expect.amountBaseUnits + ); + + if (expect.txHash) { + return candidates.filter((t) => t.txHash === expect.txHash); + } + return candidates; +} + +/** + * Whether a payment was settled more than once. + * + * Counted WITHIN a single transaction. Two identical transfers in different + * transactions are two different settlements of two different escrows β€” the normal + * shape of recurring payroll. Two in the SAME transaction would mean one + * `pay_batch` paid the same payee twice, which is the actual double-payment + * condition worth alarming on. + */ +export function countDuplicateSettlementsInSameTransaction( + matches: readonly ObservedTransfer[] +): { duplicated: boolean; txHash?: string; count: number } { + const byTx = new Map(); + for (const t of matches) { + if (!t.txHash) continue; + byTx.set(t.txHash, (byTx.get(t.txHash) ?? 0) + 1); + } + for (const [txHash, count] of byTx) { + if (count > 1) return { duplicated: true, txHash, count }; + } + return { duplicated: false, count: matches.length }; +} + +/** + * Pick the transaction that settled this escrow, when the payment does not + * already record one. + * + * A `pay_batch` transaction contains one transfer per payee, so the settling + * transaction is the one whose transfers cover EVERY expected payment of the + * escrow. Choosing by "contains this payment's transfer" alone would pick an + * arbitrary earlier period's settlement. + */ +export function inferSettlementTransaction( + transfers: readonly ObservedTransfer[], + escrowContractId: string, + expectedPayments: readonly { recipient: string; amountBaseUnits: bigint; assetContractId: string }[] +): string | null { + if (expectedPayments.length === 0) return null; + + const byTx = new Map(); + for (const t of transfers) { + if (t.from !== escrowContractId || !t.txHash) continue; + const list = byTx.get(t.txHash) ?? []; + list.push(t); + byTx.set(t.txHash, list); + } + + let best: { txHash: string; ledger: number } | null = null; + for (const [txHash, group] of byTx) { + const coversAll = expectedPayments.every((e) => + group.some( + (t) => + t.to === e.recipient && + t.assetContractId === e.assetContractId && + t.amountBaseUnits === e.amountBaseUnits + ) + ); + if (!coversAll) continue; + const ledger = Math.max(...group.map((t) => t.ledger)); + // Most recent covering transaction: a re-settlement attempt would be later. + if (!best || ledger > best.ledger) best = { txHash, ledger }; + } + return best?.txHash ?? null; +} + +/** + * Transfers to the recipient for the right asset but the WRONG amount. + * + * Reported separately because it is a materially different finding: money moved + * to the right person, in the wrong quantity. Matching only on exact amount would + * classify that as "no settlement found", which is both wrong and less alarming + * than the truth. + */ +export function findAmountMismatchedTransfers( + transfers: readonly ObservedTransfer[], + expect: SettlementExpectation +): ObservedTransfer[] { + const candidates = transfers.filter( + (t) => + t.from === expect.escrowContractId && + t.to === expect.recipient && + t.assetContractId === expect.assetContractId && + t.amountBaseUnits !== expect.amountBaseUnits + ); + return expect.txHash + ? candidates.filter((t) => t.txHash === expect.txHash) + : candidates; +} diff --git a/src/lib/reconciliation/reconciler.ts b/src/lib/reconciliation/reconciler.ts new file mode 100644 index 0000000..a82b727 --- /dev/null +++ b/src/lib/reconciliation/reconciler.ts @@ -0,0 +1,709 @@ +/** + * Reconciliation: an independent correctness check on the projection. + * + * ── Authority ──────────────────────────────────────────────────────────────── + * The chain is authoritative for settlement. This database is authoritative for + * workflow, tenancy and presentation. Where they disagree, the disagreement is + * RECORDED; the losing side is not quietly rewritten, because overwriting it + * destroys the only evidence the two ever diverged. + * + * ── Two corrections, and only two ──────────────────────────────────────────── + * CHAIN_AHEAD chain proves settlement, projection is behind β†’ advance to PAID. + * The money moved regardless of what we recorded. + * CANCELLED chain reports the escrow cancelled β†’ mark cancelled. + * + * Everything else is reported and left alone. In particular DATABASE_AHEAD β€” a + * payment we call PAID that the chain does not support β€” is NEVER reverted. PAID + * is terminal in the state machine and stays so; the finding is the durable record + * and the state machine is not weakened to permit an exit. A system that silently + * un-pays a payment to look tidy has destroyed the evidence of its own worst bug. + * + * ── Independence ───────────────────────────────────────────────────────────── + * Verification comes from `chain-verifier.ts`, which reads the TOKEN contract's + * own transfer events and contract storage β€” not CoreFlow's events and not the + * indexer's parser. See that file's header. + */ + +import { PaymentState, FindingKind, FindingSeverity, FindingStatus, RunStatus } from '@prisma/client'; +import { applyTransition } from '@/lib/payments/service'; +import { + CHAIN_STATUS, + matchSettlementTransfer, + findAmountMismatchedTransfers, + countDuplicateSettlementsInSameTransaction, + inferSettlementTransaction, + type ChainVerifier, + type ObservedTransfer, +} from './chain-verifier'; + +const RECONCILER = { kind: 'reconciler' as const, system: 'reconciler' }; + +/** Per-object outcome, for run counters. */ +export type Outcome = + | 'AGREED' + | 'CHAIN_AHEAD' + | 'DATABASE_AHEAD' + | 'CHAIN_UNREADABLE' + | 'MISMATCHED' + | 'UNKNOWN_ON_CHAIN_OBJECT' + | 'ORPHANED_DATABASE_OBJECT'; + +export interface ReconcileOptions { + contractId?: string; + network?: string; + /** Cap on escrows examined in one run, so a run is bounded. */ + maxEscrows?: number; + /** Oldest ledger to search for transfers. Bounds RPC work. */ + fromLedger?: number; +} + +export interface RunSummary { + runId: string; + correlationId: string; + status: RunStatus; + escrowsExamined: number; + paymentsExamined: number; + agreed: number; + mismatched: number; + unreadable: number; + chainAhead: number; + databaseAhead: number; + findingsOpened: number; + correctionsApplied: number; + errorMessage?: string; +} + +/** Severity and remediation per finding kind, declared once. */ +const FINDING_POLICY: Record< + FindingKind, + { severity: FindingSeverity; remediation: string } +> = { + [FindingKind.DB_PAID_CHAIN_NOT]: { + severity: FindingSeverity.CRITICAL, + remediation: + 'CoreFlow is presenting this payment as settled and the chain does not ' + + 'support that. Do not rely on the payment record. Verify the transaction ' + + 'on the explorer, then either confirm settlement or treat the payment as ' + + 'unsettled and re-issue it.', + }, + [FindingKind.FAILED_TX_ACTUALLY_SUCCEEDED]: { + severity: FindingSeverity.CRITICAL, + remediation: + 'A transaction recorded as failed actually succeeded. DO NOT RETRY this ' + + 'payment β€” doing so would pay twice. Confirm the settlement and correct the ' + + 'record instead.', + }, + [FindingKind.AMOUNT_MISMATCH]: { + severity: FindingSeverity.HIGH, + remediation: + 'The amount that moved on-chain differs from the recorded amount. Establish ' + + 'which is correct from the transaction, then correct the payroll record and ' + + 'settle or recover the difference.', + }, + [FindingKind.RECIPIENT_MISMATCH]: { + severity: FindingSeverity.HIGH, + remediation: + 'Funds reached a different address than recorded. Confirm the intended ' + + 'recipient before any further payment to this worker.', + }, + [FindingKind.ASSET_MISMATCH]: { + severity: FindingSeverity.HIGH, + remediation: 'The settled asset differs from the recorded asset. Verify the transaction.', + }, + [FindingKind.DUPLICATE_PAYMENT_EVENT]: { + severity: FindingSeverity.HIGH, + remediation: + 'More asset transfers were observed than this payment expects. Check whether ' + + 'the worker was paid twice before issuing anything further.', + }, + [FindingKind.MISSING_PAYMENT_EVENT]: { + severity: FindingSeverity.HIGH, + remediation: + 'The contract reports this payment settled but no matching asset transfer ' + + 'was observed. Confirm on the explorer whether funds moved.', + }, + [FindingKind.CHAIN_PAID_DB_NOT]: { + severity: FindingSeverity.MEDIUM, + remediation: + 'The chain settled this payment and the projection had not caught up. ' + + 'Usually self-correcting; if it persists, check the indexer is running.', + }, + [FindingKind.MISSING_ON_CHAIN]: { + severity: FindingSeverity.MEDIUM, + remediation: + 'A recorded payment has no on-chain slot. Confirm the escrow was created ' + + 'as expected, then correct or remove the record.', + }, + [FindingKind.ORPHAN_ON_CHAIN]: { + severity: FindingSeverity.MEDIUM, + remediation: + 'An on-chain payment has no database row. Re-run the indexer; if it stays ' + + 'orphaned, the escrow may have been created outside CoreFlow.', + }, + [FindingKind.UNKNOWN_ON_CHAIN_OBJECT]: { + severity: FindingSeverity.LOW, + remediation: + 'An on-chain escrow belongs to no organization. CoreFlow will not guess an ' + + 'owner. If it is yours, claim it via the escrow claim endpoint using the ' + + 'wallet that created it.', + }, + [FindingKind.CHAIN_UNREADABLE]: { + severity: FindingSeverity.LOW, + remediation: + 'Chain state could not be read, so nothing was verified. This is NOT a ' + + 'mismatch. It clears on the next successful run; investigate RPC health if ' + + 'it persists.', + }, + [FindingKind.OTHER]: { + severity: FindingSeverity.MEDIUM, + remediation: + 'This discrepancy does not match any known category, so no automated ' + + 'guidance applies. Compare the payment record against the transaction on ' + + 'the explorer and escalate to engineering with the finding id β€” an ' + + 'unclassified finding usually means the taxonomy needs a new entry.', + }, +}; + +export function policyFor(kind: FindingKind) { + return FINDING_POLICY[kind] ?? FINDING_POLICY[FindingKind.OTHER]; +} + +/** + * Record a finding, or re-observe an existing unresolved one. + * + * Re-observing updates `lastObservedAt` and increments a counter rather than + * inserting a duplicate. A queue that grows by one row per run per problem becomes + * noise, and a noisy queue gets ignored β€” which is the same as having none. + */ +async function upsertFinding( + db: any, + input: { + orgId: string; + runId: string; + kind: FindingKind; + paymentId?: string | null; + dbState?: string | null; + chainState?: string | null; + detail: string; + txHash?: string | null; + escrowOnChainId?: number | null; + paymentIndex?: number | null; + metadata?: Record; + } +): Promise<{ opened: boolean }> { + const policy = policyFor(input.kind); + + // The identity of a finding is (kind, payment, escrow, SLOT). Omitting the slot + // made every orphaned payment in one escrow collapse into a single finding, so a + // batch with three unprojected payees reported one problem instead of three. + const existing = await db.reconciliationFinding.findFirst({ + where: { + orgId: input.orgId, + kind: input.kind, + paymentId: input.paymentId ?? null, + escrowOnChainId: input.escrowOnChainId ?? null, + paymentIndex: input.paymentIndex ?? null, + status: { not: FindingStatus.RESOLVED }, + }, + }); + + if (existing) { + await db.reconciliationFinding.update({ + where: { id: existing.id }, + data: { + runId: input.runId, + lastObservedAt: new Date(), + observationCount: { increment: 1 }, + // Evidence is refreshed; the operator's acknowledgement is not reset, + // or acknowledging a persistent finding would be impossible. + dbState: input.dbState ?? existing.dbState, + chainState: input.chainState ?? existing.chainState, + detail: input.detail, + }, + }); + return { opened: false }; + } + + await db.reconciliationFinding.create({ + data: { + orgId: input.orgId, + runId: input.runId, + kind: input.kind, + status: FindingStatus.OPEN, + severity: policy.severity, + remediation: policy.remediation, + paymentId: input.paymentId ?? null, + dbState: input.dbState ?? null, + chainState: input.chainState ?? null, + detail: input.detail, + txHash: input.txHash ?? null, + escrowOnChainId: input.escrowOnChainId ?? null, + paymentIndex: input.paymentIndex ?? null, + // Set explicitly rather than leaning on the column default: this counter is + // incremented by `upsertFinding`, and code that mutates a value should not + // also depend on something else to initialise it. + observationCount: 1, + lastObservedAt: new Date(), + metadata: (input.metadata ?? {}) as any, + }, + }); + return { opened: true }; +} + +/** + * Reconcile one organization. + * + * Scope is bounded (`maxEscrows`) so a run cannot grow unboundedly with the + * tenant, and the run record is updated with a heartbeat so a crashed worker's + * lock can be reclaimed rather than blocking reconciliation forever. + */ +export async function reconcileOrganization( + db: any, + verifier: ChainVerifier, + orgId: string, + run: { id: string; correlationId: string }, + opts: ReconcileOptions = {} +): Promise> { + const tally = { + escrowsExamined: 0, paymentsExamined: 0, + agreed: 0, mismatched: 0, unreadable: 0, + chainAhead: 0, databaseAhead: 0, + findingsOpened: 0, correctionsApplied: 0, + }; + + const log = (msg: string) => console.info(`[reconcile ${run.correlationId}] ${msg}`); + + const escrows = await db.escrow.findMany({ + where: { + orgId, + onChainId: { not: null }, + ...(opts.contractId ? { contractId: opts.contractId } : {}), + ...(opts.network ? { network: opts.network } : {}), + }, + include: { payments: { orderBy: { onChainPaymentIndex: 'asc' } } }, + orderBy: { createdAt: 'desc' }, + take: opts.maxEscrows ?? 100, + }); + + /** Transfer events per asset, fetched once per run and reused. */ + const transferCache = new Map(); + async function transfersFor(assetContractId: string): Promise { + if (transferCache.has(assetContractId)) return transferCache.get(assetContractId)!; + const res = await verifier.readTransfers(assetContractId, { fromLedger: opts.fromLedger }); + const value = res.ok ? res.value : null; + transferCache.set(assetContractId, value); + if (!res.ok) log(`transfers unreadable for ${assetContractId}: ${res.error.reason}`); + return value; + } + + for (const escrow of escrows) { + tally.escrowsExamined++; + + const chain = await verifier.readEscrow(escrow.onChainId); + + if (!chain.ok) { + // Could not check. Explicitly NOT agreement, and explicitly not a mismatch. + tally.unreadable++; + const kind = + chain.error.kind === 'NOT_FOUND' + ? FindingKind.MISSING_ON_CHAIN + : FindingKind.CHAIN_UNREADABLE; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind, + escrowOnChainId: escrow.onChainId, + dbState: `${escrow.payments.length} payment(s) recorded`, + chainState: chain.error.kind === 'NOT_FOUND' ? 'escrow absent' : 'unreadable', + detail: + chain.error.kind === 'NOT_FOUND' + ? `Escrow ${escrow.onChainId} does not exist on ${escrow.network}.` + : `Escrow ${escrow.onChainId} could not be read: ${chain.error.reason}. ` + + 'Nothing was verified for its payments.', + metadata: { escrowId: escrow.id, reason: chain.error.reason }, + }); + if (r.opened) tally.findingsOpened++; + continue; + } + + const byIndex = new Map(chain.value.payments.map((p) => [p.index, p])); + + // On-chain slots with no database row: invisible in the product. + for (const cp of chain.value.payments) { + if (escrow.payments.some((p: any) => p.onChainPaymentIndex === cp.index)) continue; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.ORPHAN_ON_CHAIN, + escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + chainState: `status=${cp.status}`, + detail: + `Escrow ${escrow.onChainId} payment ${cp.index} exists on-chain but has ` + + 'no database row.', + metadata: { worker: cp.worker, amountBaseUnits: cp.amountBaseUnits.toString() }, + }); + if (r.opened) tally.findingsOpened++; + tally.mismatched++; + } + + for (const p of escrow.payments) { + tally.paymentsExamined++; + const cp = p.onChainPaymentIndex === null ? undefined : byIndex.get(p.onChainPaymentIndex); + + if (!cp) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.MISSING_ON_CHAIN, + paymentId: p.id, escrowOnChainId: escrow.onChainId, + paymentIndex: p.onChainPaymentIndex, + dbState: p.state, + detail: + `Payment references escrow ${escrow.onChainId} slot ` + + `${p.onChainPaymentIndex}, which does not exist on-chain.`, + }); + if (r.opened) tally.findingsOpened++; + tally.mismatched++; + continue; + } + + // ── Identity checks, independent of state ── + let identityMismatch = false; + if (p.recipientAddress !== cp.worker) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.RECIPIENT_MISMATCH, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.recipientAddress, chainState: cp.worker, + detail: 'Recorded recipient differs from the on-chain recipient.', + }); + if (r.opened) tally.findingsOpened++; + identityMismatch = true; + } + if (p.amountBaseUnits !== cp.amountBaseUnits) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.AMOUNT_MISMATCH, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.amountBaseUnits.toString(), chainState: cp.amountBaseUnits.toString(), + detail: 'Recorded amount differs from the on-chain amount.', + }); + if (r.opened) tally.findingsOpened++; + identityMismatch = true; + } + if (p.assetContractId && p.assetContractId !== cp.token) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.ASSET_MISMATCH, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.assetContractId, chainState: cp.token, + detail: 'Recorded asset differs from the on-chain asset.', + }); + if (r.opened) tally.findingsOpened++; + identityMismatch = true; + } + if (identityMismatch) tally.mismatched++; + + const chainSettled = cp.status === CHAIN_STATUS.FINALIZED; + const chainCancelled = cp.status === CHAIN_STATUS.CANCELLED; + const dbSettled = p.state === PaymentState.PAID; + + // ── The independent transfer check ── + // Only meaningful where one side claims settlement. Asking "did value move" + // for a payment nobody claims settled would produce noise. + let transferVerdict: 'CONFIRMED' | 'ABSENT' | 'UNREADABLE' | 'NOT_APPLICABLE' = + 'NOT_APPLICABLE'; + let settlementTx: string | null = p.settlementTxHash ?? null; + + if (chainSettled || dbSettled) { + const asset = p.assetContractId ?? cp.token; + const transfers = await transfersFor(asset); + + if (transfers === null) { + transferVerdict = 'UNREADABLE'; + } else { + // Scope the search to ONE transaction. The tuple (escrow contract, + // recipient, asset, amount) repeats every pay period, so an unscoped + // match finds one transfer per historical settlement and looks like a + // duplicate payment that never happened. + const txScope = + p.settlementTxHash ?? + inferSettlementTransaction( + transfers, + escrow.contractId, + chain.value.payments.map((q) => ({ + recipient: q.worker, + amountBaseUnits: q.amountBaseUnits, + assetContractId: q.token, + })) + ); + + const expectation = { + escrowContractId: escrow.contractId, + recipient: cp.worker, + assetContractId: asset, + amountBaseUnits: cp.amountBaseUnits, + txHash: txScope, + }; + const matched = matchSettlementTransfer(transfers, expectation); + const dup = countDuplicateSettlementsInSameTransaction(matched); + + if (dup.duplicated) { + // Two transfers for one payee inside ONE pay_batch: the genuine + // double-payment condition. + transferVerdict = 'CONFIRMED'; + settlementTx = dup.txHash ?? matched[0]?.txHash ?? settlementTx; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.DUPLICATE_PAYMENT_EVENT, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + chainState: `${dup.count} transfers in transaction ${dup.txHash}`, + txHash: dup.txHash ?? null, + detail: + `${dup.count} asset transfers for this payee were observed inside a ` + + 'single settlement transaction. The worker may have been paid twice.', + }); + if (r.opened) tally.findingsOpened++; + tally.mismatched++; + } else if (matched.length >= 1) { + transferVerdict = 'CONFIRMED'; + settlementTx = matched[0].txHash ?? settlementTx; + } else { + transferVerdict = 'ABSENT'; + // A transfer of the wrong amount is a different, louder fact than none. + const wrongAmount = findAmountMismatchedTransfers(transfers, expectation); + if (wrongAmount.length > 0) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.AMOUNT_MISMATCH, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: cp.amountBaseUnits.toString(), + chainState: wrongAmount.map((t) => t.amountBaseUnits.toString()).join(', '), + txHash: wrongAmount[0].txHash ?? null, + detail: + 'Asset moved to this recipient, but not in the expected amount.', + }); + if (r.opened) tally.findingsOpened++; + tally.mismatched++; + } + } + } + } + + // ── Outcome ── + if (chainSettled && !dbSettled) { + if (transferVerdict === 'UNREADABLE') { + // The contract says settled, but the movement could not be independently + // confirmed. Advancing on contract state alone would undo the point of + // having an independent check. + tally.unreadable++; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.CHAIN_UNREADABLE, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.state, chainState: 'contract reports FINALIZED', + detail: + 'The contract reports settlement, but asset transfers could not be ' + + 'read to confirm it. The projection was NOT advanced.', + }); + if (r.opened) tally.findingsOpened++; + continue; + } + + if (transferVerdict === 'ABSENT') { + tally.mismatched++; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.MISSING_PAYMENT_EVENT, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.state, chainState: 'contract reports FINALIZED, no transfer observed', + detail: + 'The contract reports this payment settled, but no matching asset ' + + 'transfer was observed. The projection was NOT advanced.', + }); + if (r.opened) tally.findingsOpened++; + continue; + } + + // Confirmed by the token's own events: catch the projection up. + const outcome = await applyTransition(db, { + paymentId: p.id, + to: PaymentState.PAID, + actor: RECONCILER, + orgId, + reason: 'Reconciliation: settlement independently confirmed on-chain.', + txHash: settlementTx ?? undefined, + metadata: { + source: 'reconciliation', + correlationId: run.correlationId, + verifiedBy: 'sac-transfer-event', + escrowOnChainId: escrow.onChainId, + paymentIndex: cp.index, + }, + }); + if (outcome.ok && outcome.changed) { + tally.chainAhead++; + tally.correctionsApplied++; + log(`payment ${p.id}: CHAIN_AHEAD corrected to PAID`); + } else { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.CHAIN_PAID_DB_NOT, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: p.state, chainState: 'FINALIZED (transfer confirmed)', + detail: + 'Settlement is confirmed on-chain but the recorded state does not ' + + `permit PAID: ${outcome.ok ? 'already there' : outcome.message}`, + }); + if (r.opened) tally.findingsOpened++; + tally.mismatched++; + } + continue; + } + + if (dbSettled && !chainSettled) { + // The worst finding in the system: we are telling a finance team that + // money moved when the chain does not agree. Never reverted β€” see header. + tally.databaseAhead++; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.DB_PAID_CHAIN_NOT, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: 'PAID', + chainState: `contract status=${cp.status}, transfer=${transferVerdict}`, + txHash: p.settlementTxHash, + detail: + 'CoreFlow records this payment as PAID but the chain does not report ' + + 'it as settled. The payment state was NOT changed; PAID is terminal ' + + 'and this finding is the durable record.', + metadata: { transferVerdict }, + }); + if (r.opened) tally.findingsOpened++; + continue; + } + + if (dbSettled && chainSettled) { + if (transferVerdict === 'CONFIRMED') { + // Only count as agreed if NOTHING about this payment disagreed. An + // identity mismatch earlier in this loop already counted it as + // mismatched; counting it again here would let a run report + // "3 of 3 agreed" for a batch with two wrong amounts. + if (!identityMismatch) tally.agreed++; + } else if (transferVerdict === 'UNREADABLE') { + tally.unreadable++; + } else { + // Both sides claim settled, yet no asset movement was observed. + tally.mismatched++; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.MISSING_PAYMENT_EVENT, + paymentId: p.id, escrowOnChainId: escrow.onChainId, paymentIndex: cp.index, + dbState: 'PAID', chainState: 'FINALIZED, no transfer observed', + txHash: p.settlementTxHash, + detail: + 'Both CoreFlow and the contract report this payment settled, but no ' + + 'matching asset transfer was observed.', + }); + if (r.opened) tally.findingsOpened++; + } + continue; + } + + if (chainCancelled && p.state !== PaymentState.CANCELLED) { + const outcome = await applyTransition(db, { + paymentId: p.id, + to: PaymentState.CANCELLED, + actor: RECONCILER, + orgId, + reason: 'Reconciliation: the escrow was cancelled on-chain.', + metadata: { source: 'reconciliation', correlationId: run.correlationId }, + }); + if (outcome.ok && outcome.changed) tally.correctionsApplied++; + continue; + } + + if (!identityMismatch) tally.agreed++; + } + } + + return tally; +} + +/** + * Cross-check transactions recorded as failed against the chain. + * + * An RPC timeout reported as a failure, while the transaction actually landed, is + * the most dangerous state in a payments system: it invites a retry that pays + * twice. The contract's `PaymentAlreadyFinalized` guard is the real backstop, but + * this finds the condition instead of waiting for someone to hit it. + */ +export async function reconcileFailedTransactions( + db: any, + verifier: ChainVerifier, + orgId: string, + run: { id: string; correlationId: string } +): Promise<{ checked: number; falselyFailed: number; findingsOpened: number }> { + const failed = await db.blockchainTransaction.findMany({ + where: { orgId, status: 'FAILED', hash: { not: null } }, + take: 200, + }); + + let falselyFailed = 0; + let findingsOpened = 0; + + for (const tx of failed) { + const res = await verifier.readTransactionSucceeded(tx.hash); + // Unreadable or forgotten by the node: leave it alone rather than guessing. + if (!res.ok || !res.value) continue; + + falselyFailed++; + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.FAILED_TX_ACTUALLY_SUCCEEDED, + paymentId: tx.paymentId, + txHash: tx.hash, + dbState: 'FAILED', chainState: 'SUCCESS', + detail: + `Transaction ${tx.hash} was recorded as failed but succeeded on-chain. ` + + 'Do not retry this payment.', + metadata: { transactionId: tx.id }, + }); + if (r.opened) findingsOpened++; + + await db.blockchainTransaction.update({ + where: { id: tx.id }, + data: { + status: 'CONFIRMED', + errorMessage: + 'Recorded as failed in error; the chain confirms success. Corrected by ' + + 'reconciliation.', + }, + }); + } + + return { checked: failed.length, falselyFailed, findingsOpened }; +} + +/** + * Report on-chain escrows belonging to no organization. + * + * Preserves the P2 #2 decision: CoreFlow never invents a tenant for a discovered + * escrow. These are reported so they are investigable, and claimable through the + * controlled workflow, rather than silently attached to whichever organization was + * convenient. + */ +export async function reportUnattributedEscrows( + db: any, + orgId: string, + run: { id: string; correlationId: string }, + opts: { contractId: string; network: string } +): Promise<{ unknown: number; findingsOpened: number }> { + const rows = await db.chainEvent.findMany({ + where: { + attributed: false, + contractId: opts.contractId, + network: opts.network, + escrowOnChainId: { not: null }, + }, + distinct: ['escrowOnChainId'], + take: 100, + }); + + let findingsOpened = 0; + for (const row of rows) { + const r = await upsertFinding(db, { + orgId, runId: run.id, kind: FindingKind.UNKNOWN_ON_CHAIN_OBJECT, + escrowOnChainId: row.escrowOnChainId, + chainState: 'exists on-chain, unattributed', + detail: + `Escrow ${row.escrowOnChainId} exists on ${opts.network} but belongs to no ` + + 'CoreFlow organization. It has not been attached to any tenant.', + metadata: { contractId: opts.contractId, firstSeenLedger: row.ledger }, + }); + if (r.opened) findingsOpened++; + } + return { unknown: rows.length, findingsOpened }; +} diff --git a/src/lib/reconciliation/scheduler.ts b/src/lib/reconciliation/scheduler.ts new file mode 100644 index 0000000..c8b88c3 --- /dev/null +++ b/src/lib/reconciliation/scheduler.ts @@ -0,0 +1,327 @@ +/** + * Reconciliation scheduling and run lifecycle. + * + * ── Why there is no job framework here ─────────────────────────────────────── + * CoreFlow deploys on Vercel, where there are no long-lived workers. Adding Redis + * or a queue purely to own a cron tick would be infrastructure with no other + * purpose, and one more thing that can be down. The lock lives in PostgreSQL, + * which the application already depends on absolutely: if it is unavailable, + * reconciliation could not run anyway. + * + * A run is therefore triggered externally (Vercel Cron, an operator, a test) and + * this module guarantees the parts that matter: one run at a time per + * organization, a heartbeat so a crashed holder does not block forever, and a + * durable record of what each run examined. + * + * ── Overlap safety ─────────────────────────────────────────────────────────── + * Two concurrent runs over the same payments would both see the same + * discrepancies and both try to correct them. The corrections are individually + * idempotent β€” `applyTransition` is a compare-and-swap β€” so the danger is not a + * double payment but duplicated findings and wasted RPC. The lock is a unique + * partial index on (orgId) for RUNNING rows, so the database refuses the second + * run rather than the application hoping it noticed. + */ + +import { randomUUID } from 'crypto'; +import { RunStatus } from '@prisma/client'; +import { createRpcVerifier, type ChainVerifier } from './chain-verifier'; +import { + reconcileOrganization, + reconcileFailedTransactions, + reportUnattributedEscrows, + type ReconcileOptions, + type RunSummary, +} from './reconciler'; + +/** + * A run whose heartbeat is older than this is considered abandoned and may be + * taken over. Long enough that a slow-but-alive run is not stolen from; short + * enough that a crash does not block reconciliation for an operational age. + */ +export const STALE_RUN_AFTER_MS = 10 * 60 * 1000; // 10 minutes + +/** How often a running pass refreshes its heartbeat. */ +export const HEARTBEAT_INTERVAL_MS = 30 * 1000; + +export type StartResult = + | { started: true; runId: string; correlationId: string } + | { started: false; reason: 'ALREADY_RUNNING'; runId: string; startedAt: Date }; + +/** + * Claim the reconciliation lock for an organization. + * + * Stale runs are marked STALE first, which both releases the lock and leaves a + * record that a run died β€” silently reusing the lock would erase the evidence that + * reconciliation has been failing. + */ +export async function startRun( + db: any, + orgId: string, + scope: string, + meta: { contractId?: string; network?: string } = {} +): Promise { + const cutoff = new Date(Date.now() - STALE_RUN_AFTER_MS); + + const stale = await db.reconciliationRun.updateMany({ + where: { orgId, status: RunStatus.RUNNING, heartbeatAt: { lt: cutoff } }, + data: { + status: RunStatus.STALE, + completedAt: new Date(), + errorMessage: + 'Abandoned: the worker stopped reporting a heartbeat. The lock was ' + + 'reclaimed by a later run.', + }, + }); + if (stale.count > 0) { + console.warn(`[reconcile] reclaimed ${stale.count} stale run lock(s) for ${orgId}`); + } + + const active = await db.reconciliationRun.findFirst({ + where: { orgId, status: RunStatus.RUNNING }, + select: { id: true, startedAt: true }, + }); + if (active) { + return { + started: false, + reason: 'ALREADY_RUNNING', + runId: active.id, + startedAt: active.startedAt, + }; + } + + const correlationId = `rec_${randomUUID()}`; + try { + const run = await db.reconciliationRun.create({ + data: { + orgId, + correlationId, + scope, + contractId: meta.contractId ?? null, + network: meta.network ?? null, + status: RunStatus.RUNNING, + // Set explicitly rather than relying on the column default: this value IS + // the lock's liveness signal, and a lock should not depend on something + // else to initialise the field that decides whether it is alive. + startedAt: new Date(), + heartbeatAt: new Date(), + }, + }); + return { started: true, runId: run.id, correlationId }; + } catch (e: any) { + // The unique partial index rejected us: another worker won the race between + // the check above and this insert. + if (e?.code === 'P2002') { + const other = await db.reconciliationRun.findFirst({ + where: { orgId, status: RunStatus.RUNNING }, + select: { id: true, startedAt: true }, + }); + return { + started: false, + reason: 'ALREADY_RUNNING', + runId: other?.id ?? 'unknown', + startedAt: other?.startedAt ?? new Date(), + }; + } + throw e; + } +} + +export async function heartbeat(db: any, runId: string): Promise { + await db.reconciliationRun.updateMany({ + where: { id: runId, status: RunStatus.RUNNING }, + data: { heartbeatAt: new Date() }, + }); +} + +async function finishRun( + db: any, + runId: string, + status: RunStatus, + tally: Partial & { errorMessage?: string } +): Promise { + await db.reconciliationRun.update({ + where: { id: runId }, + data: { + status, + completedAt: new Date(), + heartbeatAt: new Date(), + escrowsExamined: tally.escrowsExamined ?? 0, + paymentsExamined: tally.paymentsExamined ?? 0, + agreed: tally.agreed ?? 0, + mismatched: tally.mismatched ?? 0, + unreadable: tally.unreadable ?? 0, + chainAhead: tally.chainAhead ?? 0, + databaseAhead: tally.databaseAhead ?? 0, + findingsOpened: tally.findingsOpened ?? 0, + correctionsApplied: tally.correctionsApplied ?? 0, + errorMessage: tally.errorMessage ?? null, + }, + }); +} + +/** + * Run a full reconciliation pass for one organization. + * + * A failure still completes the run record, marked FAILED with the error. A run + * that simply stops existing is indistinguishable from one that never started, and + * "no findings" would then read as health. + */ +export async function runReconciliation( + db: any, + orgId: string, + opts: ReconcileOptions & { verifier?: ChainVerifier; scope?: string } = {} +): Promise { + const verifier = opts.verifier ?? createRpcVerifier(); + const scope = opts.scope ?? 'organization'; + + const claim = await startRun(db, orgId, scope, { + contractId: opts.contractId, + network: opts.network, + }); + if (!claim.started) { + console.info(`[reconcile] skipped ${orgId}: run ${claim.runId} already in progress`); + return { skipped: true, reason: 'ALREADY_RUNNING', runId: claim.runId }; + } + + const run = { id: claim.runId, correlationId: claim.correlationId }; + const beat = setInterval(() => void heartbeat(db, run.id).catch(() => {}), HEARTBEAT_INTERVAL_MS); + + try { + const tally = await reconcileOrganization(db, verifier, orgId, run, opts); + + const txCheck = await reconcileFailedTransactions(db, verifier, orgId, run); + tally.findingsOpened += txCheck.findingsOpened; + + if (opts.contractId && opts.network) { + const orphans = await reportUnattributedEscrows(db, orgId, run, { + contractId: opts.contractId, + network: opts.network, + }); + tally.findingsOpened += orphans.findingsOpened; + } + + await finishRun(db, run.id, RunStatus.COMPLETED, tally); + + console.info( + `[reconcile ${run.correlationId}] completed: ` + + `${tally.paymentsExamined} payments, ${tally.agreed} agreed, ` + + `${tally.mismatched} mismatched, ${tally.unreadable} unreadable, ` + + `${tally.findingsOpened} findings opened, ${tally.correctionsApplied} corrections` + ); + + return { + runId: run.id, + correlationId: run.correlationId, + status: RunStatus.COMPLETED, + ...tally, + }; + } catch (e: any) { + const message = e?.message ?? String(e); + console.error(`[reconcile ${run.correlationId}] FAILED: ${message}`); + await finishRun(db, run.id, RunStatus.FAILED, { errorMessage: message }).catch(() => {}); + return { + runId: run.id, + correlationId: run.correlationId, + status: RunStatus.FAILED, + escrowsExamined: 0, paymentsExamined: 0, agreed: 0, mismatched: 0, + unreadable: 0, chainAhead: 0, databaseAhead: 0, + findingsOpened: 0, correctionsApplied: 0, + errorMessage: message, + }; + } finally { + clearInterval(beat); + } +} + +/** Operational health for one organization, for the reconciliation screen. */ +export interface ReconciliationHealth { + lastRun: { + id: string; + correlationId: string; + status: RunStatus; + startedAt: Date; + completedAt: Date | null; + paymentsExamined: number; + agreed: number; + mismatched: number; + unreadable: number; + findingsOpened: number; + correctionsApplied: number; + errorMessage: string | null; + } | null; + openFindings: number; + criticalFindings: number; + /** How long the oldest unresolved finding has been open, in hours. */ + oldestUnresolvedHours: number | null; + /** True when the last run did not complete, or there has never been one. */ + degraded: boolean; + degradedReason?: string; +} + +export async function reconciliationHealth( + db: any, + orgId: string +): Promise { + const lastRun = await db.reconciliationRun.findFirst({ + where: { orgId }, + orderBy: { startedAt: 'desc' }, + }); + + const [openFindings, criticalFindings, oldest] = await Promise.all([ + db.reconciliationFinding.count({ where: { orgId, status: { not: 'RESOLVED' } } }), + db.reconciliationFinding.count({ + where: { orgId, status: { not: 'RESOLVED' }, severity: 'CRITICAL' }, + }), + db.reconciliationFinding.findFirst({ + where: { orgId, status: { not: 'RESOLVED' } }, + orderBy: { detectedAt: 'asc' }, + select: { detectedAt: true }, + }), + ]); + + let degraded = false; + let degradedReason: string | undefined; + + if (!lastRun) { + degraded = true; + degradedReason = 'Reconciliation has never run for this organization.'; + } else if (lastRun.status === RunStatus.FAILED) { + degraded = true; + degradedReason = `The last reconciliation run failed: ${lastRun.errorMessage ?? 'unknown error'}`; + } else if (lastRun.status === RunStatus.STALE) { + degraded = true; + degradedReason = 'The last reconciliation run was abandoned before finishing.'; + } else if (lastRun.status === RunStatus.RUNNING) { + const age = Date.now() - new Date(lastRun.heartbeatAt).getTime(); + if (age > STALE_RUN_AFTER_MS) { + degraded = true; + degradedReason = 'A reconciliation run appears to have stopped responding.'; + } + } + + return { + lastRun: lastRun + ? { + id: lastRun.id, + correlationId: lastRun.correlationId, + status: lastRun.status, + startedAt: lastRun.startedAt, + completedAt: lastRun.completedAt, + paymentsExamined: lastRun.paymentsExamined, + agreed: lastRun.agreed, + mismatched: lastRun.mismatched, + unreadable: lastRun.unreadable, + findingsOpened: lastRun.findingsOpened, + correctionsApplied: lastRun.correctionsApplied, + errorMessage: lastRun.errorMessage, + } + : null, + openFindings, + criticalFindings, + oldestUnresolvedHours: oldest + ? Math.floor((Date.now() - new Date(oldest.detectedAt).getTime()) / 3_600_000) + : null, + degraded, + degradedReason, + }; +} diff --git a/src/lib/tenancy/__tests__/isolation.test.ts b/src/lib/tenancy/__tests__/isolation.test.ts new file mode 100644 index 0000000..afcd074 --- /dev/null +++ b/src/lib/tenancy/__tests__/isolation.test.ts @@ -0,0 +1,310 @@ +// @vitest-environment node +/** + * Cross-tenant isolation tests. + * + * These treat the organization boundary as a SECURITY boundary, so they are + * written the way an attacker probes one: substitute an id, enumerate a range, + * and watch what the differences in response reveal. + * + * The database enforces the same boundary independently via composite foreign + * keys β€” verified directly against PostgreSQL, recorded in + * docs/evidence/REVIEWER_EVIDENCE.md. These tests cover the application layer + * that sits above it. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { OrgRole, MembershipStatus, PaymentState } from '@prisma/client'; +import { createFakeDb, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { + resolveTenant, listTenants, requirePermission, + findPayment, findBatch, findEscrow, findProject, findWorker, + findTransaction, findAuditEvent, findFinding, findMember, + findEscrowByOnChainId, assertProjectInTenant, + paymentReadScope, tenantScope, + type TenantContext, +} from '../resolve'; + +const A = 'orgA'; +const B = 'orgB'; + +let db: FakeDb; + +/** Two fully-populated tenants with identical resource shapes. */ +function seed() { + for (const [id, slug] of [[A, 'org-a'], [B, 'org-b']] as const) { + db.__tables.organization.rows.push({ id, name: id, slug }); + } + + const mk = (org: string, suffix: string) => { + db.__tables.project.rows.push({ id: `proj_${suffix}`, orgId: org, name: 'P', code: 'P1' }); + db.__tables.worker.rows.push({ id: `wk_${suffix}`, orgId: org, walletAddress: `GW${suffix}` }); + db.__tables.payrollBatch.rows.push({ id: `bat_${suffix}`, orgId: org, reference: `CF-${suffix}` }); + db.__tables.escrow.rows.push({ + id: `esc_${suffix}`, orgId: org, onChainId: 7, contractId: 'C', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', assetDecimals: 7, + }); + db.__tables.payment.rows.push({ + id: `pay_${suffix}`, orgId: org, batchId: `bat_${suffix}`, escrowId: `esc_${suffix}`, + projectId: `proj_${suffix}`, workerId: `wk_${suffix}`, + recipientAddress: `GR${suffix}`, onChainPaymentIndex: 0, + amountBaseUnits: 100n, rateBaseUnits: 1n, hours: 100n, + assetDecimals: 7, assetCode: 'USDC', state: PaymentState.READY_TO_SETTLE, + stateUpdatedAt: new Date(), createdAt: new Date(), + }); + db.__tables.blockchainTransaction.rows.push({ + id: `btx_${suffix}`, orgId: org, paymentId: `pay_${suffix}`, + kind: 'PAY_BATCH', status: 'PREPARING', idempotencyKey: `k_${suffix}`, attempt: 1, + }); + db.__tables.auditEvent.rows.push({ + id: `aud_${suffix}`, orgId: org, type: 'payment.state.changed', + paymentId: `pay_${suffix}`, createdAt: new Date(), + }); + db.__tables.reconciliationFinding.rows.push({ + id: `fnd_${suffix}`, orgId: org, paymentId: `pay_${suffix}`, + kind: 'AMOUNT_MISMATCH', detectedAt: new Date(), + }); + }; + mk(A, 'a'); + mk(B, 'b'); +} + +function addMember( + org: string, userId: string, role: OrgRole, wallet: string, + status: MembershipStatus = MembershipStatus.ACTIVE +) { + if (!db.__tables.user.rows.some((u) => u.id === userId)) { + db.__tables.user.rows.push({ id: userId, walletAddress: wallet, role: 'EMPLOYEE' }); + } + db.__tables.orgMember.rows.push({ + id: `ogm_${org}_${userId}`, orgId: org, userId, role, status, + orgId_: org, // unused; keeps shape obvious + createdAt: new Date(), + }); +} + +async function ctxFor(userId: string, org: string): Promise { + const r = await resolveTenant(db, userId, org); + if (!r.ok) throw new Error(`expected membership: ${r.message}`); + return r.value; +} + +beforeEach(() => { + db = createFakeDb(); + seed(); + addMember(A, 'u_a_owner', OrgRole.OWNER, 'G' + 'A'.repeat(55)); + addMember(A, 'u_a_mgr', OrgRole.MANAGER, 'G' + 'M'.repeat(55)); + addMember(A, 'u_a_fin', OrgRole.FINANCE, 'G' + 'F'.repeat(55)); + addMember(A, 'u_a_view', OrgRole.VIEWER, 'G' + 'V'.repeat(55)); + addMember(A, 'u_a_work', OrgRole.WORKER, 'GRa'); + addMember(B, 'u_b_owner', OrgRole.OWNER, 'G' + 'B'.repeat(55)); +}); + +describe('membership resolution', () => { + it('resolves an active membership', async () => { + const ctx = await ctxFor('u_a_owner', A); + expect(ctx.orgId).toBe(A); + expect(ctx.role).toBe(OrgRole.OWNER); + }); + + it('reports a foreign organization as NOT FOUND, never FORBIDDEN', async () => { + // 403 would confirm org B exists, turning id substitution into an + // enumeration oracle. + const r = await resolveTenant(db, 'u_a_owner', B); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + }); + + it('reports a nonexistent organization identically to a foreign one', async () => { + // The two MUST be indistinguishable, or existence leaks. + const foreign = await resolveTenant(db, 'u_a_owner', B); + const missing = await resolveTenant(db, 'u_a_owner', 'org_does_not_exist'); + expect(foreign.ok).toBe(false); + expect(missing.ok).toBe(false); + if (!foreign.ok && !missing.ok) { + expect(foreign.status).toBe(missing.status); + expect(foreign.message).toBe(missing.message); + } + }); + + it('401s an unauthenticated caller', async () => { + const r = await resolveTenant(db, undefined, A); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(401); + }); + + it.each([MembershipStatus.INVITED, MembershipStatus.SUSPENDED, MembershipStatus.REMOVED])( + 'refuses a %s membership, indistinguishably from non-membership', + async (status) => { + addMember(A, `u_${status}`, OrgRole.ADMIN, 'GX', status); + const r = await resolveTenant(db, `u_${status}`, A); + expect(r.ok).toBe(false); + if (!r.ok) { + expect(r.status).toBe(404); + // Suspending someone must not tell them they were ever a member. + expect(r.message).toBe('Organization not found.'); + } + } + ); + + it('lists only active memberships', async () => { + addMember(B, 'u_a_owner', OrgRole.VIEWER, 'G' + 'A'.repeat(55), MembershipStatus.SUSPENDED); + const orgs = await listTenants(db, 'u_a_owner'); + expect(orgs.map((o) => o.orgId)).toEqual([A]); + }); +}); + +describe('cross-tenant resource reads', () => { + const RESOURCES = [ + ['payment', findPayment, 'pay_b'], + ['batch', findBatch, 'bat_b'], + ['escrow', findEscrow, 'esc_b'], + ['project', findProject, 'proj_b'], + ['worker', findWorker, 'wk_b'], + ['transaction', findTransaction, 'btx_b'], + ['audit event', findAuditEvent, 'aud_b'], + ['finding', findFinding, 'fnd_b'], + ['member', findMember, 'ogm_orgB_u_b_owner'], + ] as const; + + it.each(RESOURCES)('refuses org A reading org B %s', async (_label, finder, foreignId) => { + const ctx = await ctxFor('u_a_owner', A); + const r = await finder(db, ctx, foreignId); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(404); + }); + + it.each(RESOURCES)('allows org A reading its OWN %s', async (_label, finder, foreignId) => { + const ctx = await ctxFor('u_a_owner', A); + const ownId = foreignId.replace(/_b$/, '_a').replace('orgB_u_b_owner', 'orgA_u_a_owner'); + const r = await finder(db, ctx, ownId); + expect(r.ok, `${ownId} should resolve`).toBe(true); + }); + + it('refuses a foreign resource even for the highest role', async () => { + // OWNER is the most privileged role in org A and still sees nothing in org B. + // Privilege is scoped to a tenant; it does not accumulate across them. + const ctx = await ctxFor('u_a_owner', A); + expect((await findPayment(db, ctx, 'pay_b')).ok).toBe(false); + }); +}); + +describe('on-chain id collisions across tenants', () => { + it('does not return another tenant’s escrow for the same onChainId', async () => { + // Escrow ids are assigned by the contract, so org A and org B both have an + // escrow 7. Resolving by onChainId alone would hand one tenant the other's. + const ctxA = await ctxFor('u_a_owner', A); + const ctxB = await ctxFor('u_b_owner', B); + + const a = await findEscrowByOnChainId(db, ctxA, 7); + const b = await findEscrowByOnChainId(db, ctxB, 7); + + expect(a.ok && b.ok).toBe(true); + if (a.ok && b.ok) { + expect(a.value.id).toBe('esc_a'); + expect(b.value.id).toBe('esc_b'); + expect(a.value.id).not.toBe(b.value.id); + } + }); + + it('returns not-found for an onChainId that exists only in another tenant', async () => { + db.__tables.escrow.rows.push({ + id: 'esc_b2', orgId: B, onChainId: 99, contractId: 'C', network: 'testnet', + managerAddress: 'GM', financeApproverAddress: 'GF', assetDecimals: 7, + }); + const ctxA = await ctxFor('u_a_owner', A); + expect((await findEscrowByOnChainId(db, ctxA, 99)).ok).toBe(false); + }); +}); + +describe('id enumeration', () => { + it('returns an identical response for foreign and nonexistent ids', async () => { + const ctx = await ctxFor('u_a_owner', A); + const foreign = await findPayment(db, ctx, 'pay_b'); + const absent = await findPayment(db, ctx, 'pay_totally_made_up'); + + expect(foreign.ok).toBe(false); + expect(absent.ok).toBe(false); + if (!foreign.ok && !absent.ok) { + // Any difference here is an existence oracle. + expect(foreign.status).toBe(absent.status); + expect(foreign.message).toBe(absent.message); + } + }); + + it('leaks nothing when enumerating a range of ids', async () => { + const ctx = await ctxFor('u_a_owner', A); + for (let i = 0; i < 25; i++) { + db.__tables.payment.rows.push({ + id: `pay_b_${i}`, orgId: B, batchId: 'bat_b', recipientAddress: 'GR', + amountBaseUnits: BigInt(i), rateBaseUnits: 1n, hours: BigInt(i), + assetDecimals: 7, assetCode: 'USDC', state: PaymentState.PAID, + onChainPaymentIndex: i, stateUpdatedAt: new Date(), createdAt: new Date(), + }); + } + const results = []; + for (let i = 0; i < 25; i++) results.push(await findPayment(db, ctx, `pay_b_${i}`)); + expect(results.every((r) => !r.ok && r.status === 404)).toBe(true); + }); +}); + +describe('supplied (not looked-up) tenant references', () => { + it('refuses a projectId from another organization', async () => { + // A create request could carry a foreign projectId. The composite FK would + // reject the write, but that surfaces as a 500; this is an honest 404 first. + const ctx = await ctxFor('u_a_owner', A); + const denial = await assertProjectInTenant(db, ctx, 'proj_b'); + expect(denial).not.toBeNull(); + expect(denial?.status).toBe(404); + }); + + it('accepts the caller’s own projectId', async () => { + const ctx = await ctxFor('u_a_owner', A); + expect(await assertProjectInTenant(db, ctx, 'proj_a')).toBeNull(); + }); + + it('accepts a null projectId', async () => { + const ctx = await ctxFor('u_a_owner', A); + expect(await assertProjectInTenant(db, ctx, null)).toBeNull(); + }); +}); + +describe('query scoping helpers', () => { + it('always carries the organization', async () => { + const ctx = await ctxFor('u_a_owner', A); + expect(tenantScope(ctx)).toEqual({ orgId: A }); + }); + + it('scopes a WORKER to their own payments only', async () => { + // Organization-wide read would let any contractor enumerate the whole + // payroll, including colleagues' rates. + const ctx = await ctxFor('u_a_work', A); + expect(paymentReadScope(ctx)).toEqual({ orgId: A, recipientAddress: 'GRa' }); + }); + + it('gives an operator the whole organization', async () => { + const ctx = await ctxFor('u_a_fin', A); + expect(paymentReadScope(ctx)).toEqual({ orgId: A }); + }); +}); + +describe('permission enforcement returns 403, not 404', () => { + it('tells a member their role is insufficient', async () => { + // The caller demonstrably belongs here, so there is nothing to conceal β€” + // and a 404 would be actively misleading. + const ctx = await ctxFor('u_a_view', A); + const denial = requirePermission(ctx, 'payment:approve:finance'); + expect(denial).not.toBeNull(); + expect(denial?.status).toBe(403); + expect(denial?.code).toBe('PERMISSION_DENIED'); + }); + + it('permits a role that holds the permission', async () => { + const ctx = await ctxFor('u_a_fin', A); + expect(requirePermission(ctx, 'payment:approve:finance')).toBeNull(); + }); + + it('refuses a MANAGER the finance approval inside their own organization', async () => { + const ctx = await ctxFor('u_a_mgr', A); + expect(requirePermission(ctx, 'payment:approve:finance')?.status).toBe(403); + expect(requirePermission(ctx, 'payment:approve:manager')).toBeNull(); + }); +}); diff --git a/src/lib/tenancy/__tests__/membership.test.ts b/src/lib/tenancy/__tests__/membership.test.ts new file mode 100644 index 0000000..2881ff8 --- /dev/null +++ b/src/lib/tenancy/__tests__/membership.test.ts @@ -0,0 +1,377 @@ +// @vitest-environment node +/** + * Membership lifecycle and invitation security tests. + * + * Membership is where privilege escalation lives: an invitation is a + * client-reachable object that grants authority. These tests probe the three + * escalations that are plausible mistakes rather than exotic attacks β€” + * granting above your level, granting a role you are forbidden to exercise, and + * stranding an organization with no administrator. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { OrgRole, MembershipStatus } from '@prisma/client'; +import { createFakeDb, type FakeDb } from '@/lib/payments/__tests__/fake-db'; +import { resolveTenant, type TenantContext } from '../resolve'; +import { + generateInvitationToken, hashInvitationToken, tokenHashesMatch, + canTransitionMembership, membershipTransitionsFrom, + checkRoleAssignment, checkNotLastAdministrator, checkNotSelf, + resolveInvitation, acceptInvitation, + INVITATION_TTL_DAYS, +} from '../membership'; + +const A = 'orgA'; +const B = 'orgB'; +let db: FakeDb; + +function addMember( + org: string, userId: string, role: OrgRole, wallet: string, + status: MembershipStatus = MembershipStatus.ACTIVE +) { + if (!db.__tables.user.rows.some((u) => u.id === userId)) { + db.__tables.user.rows.push({ id: userId, walletAddress: wallet, role: 'EMPLOYEE' }); + } + db.__tables.orgMember.rows.push({ + id: `ogm_${org}_${userId}`, orgId: org, userId, role, status, createdAt: new Date(), + }); +} + +function addInvitation(opts: { + id?: string; orgId?: string; email?: string; orgRole?: OrgRole; + token: string; expiresAt?: Date; usedAt?: Date | null; revokedAt?: Date | null; +}) { + db.__tables.invitation.rows.push({ + id: opts.id ?? `inv_${opts.token.slice(0, 6)}`, + orgId: opts.orgId ?? A, + email: opts.email ?? 'invitee@example.com', + orgRole: opts.orgRole ?? OrgRole.VIEWER, + role: 'EMPLOYEE', + tokenHash: hashInvitationToken(opts.token), + expiresAt: opts.expiresAt ?? new Date(Date.now() + 86400_000), + usedAt: opts.usedAt ?? null, + revokedAt: opts.revokedAt ?? null, + createdAt: new Date(), + }); +} + +async function ctxFor(userId: string, org: string): Promise { + const r = await resolveTenant(db, userId, org); + if (!r.ok) throw new Error(r.message); + return r.value; +} + +beforeEach(() => { + db = createFakeDb(); + db.__tables.organization.rows.push( + { id: A, name: 'A', slug: 'a' }, + { id: B, name: 'B', slug: 'b' } + ); + addMember(A, 'u_owner', OrgRole.OWNER, 'GOWNER'); + addMember(A, 'u_admin', OrgRole.ADMIN, 'GADMIN'); + addMember(A, 'u_mgr', OrgRole.MANAGER, 'GMGR'); + addMember(B, 'u_b_owner', OrgRole.OWNER, 'GBOWNER'); +}); + +describe('invitation tokens', () => { + it('are long, random and unpredictable', () => { + const a = generateInvitationToken(); + const b = generateInvitationToken(); + expect(a).not.toBe(b); + // 32 bytes base64url β‰ˆ 43 chars. + expect(a.length).toBeGreaterThanOrEqual(40); + expect(a).toMatch(/^[A-Za-z0-9_-]+$/); + }); + + it('are stored hashed, never in plaintext', () => { + const token = generateInvitationToken(); + addInvitation({ token }); + const row = db.__tables.invitation.rows[0]; + expect(row.tokenHash).not.toBe(token); + expect(row.tokenHash).toHaveLength(64); // sha256 hex + expect(JSON.stringify(row)).not.toContain(token); + }); + + it('compare hashes in constant time', () => { + const h = hashInvitationToken('abc'); + expect(tokenHashesMatch(h, h)).toBe(true); + expect(tokenHashesMatch(h, hashInvitationToken('abd'))).toBe(false); + expect(tokenHashesMatch(h, 'short')).toBe(false); + }); +}); + +describe('invitation resolution', () => { + it('accepts a valid token', async () => { + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.FINANCE }); + const r = await resolveInvitation(db, token); + expect(r.ok).toBe(true); + if (r.ok) { + expect(r.value.orgId).toBe(A); + expect(r.value.orgRole).toBe(OrgRole.FINANCE); + } + }); + + it.each([ + ['expired', { expiresAt: new Date(Date.now() - 1000) }, 'EXPIRED'], + ['already used', { usedAt: new Date() }, 'ALREADY_USED'], + ['revoked', { revokedAt: new Date() }, 'REVOKED'], + ] as const)('refuses an %s invitation', async (_label, overrides, reason) => { + const token = generateInvitationToken(); + addInvitation({ token, ...overrides }); + const r = await resolveInvitation(db, token); + expect(r.ok).toBe(false); + expect(r.reason).toBe(reason); + }); + + it('reports every rejection with an identical caller-visible message', async () => { + // Distinguishing "expired" from "never existed" tells an attacker which of + // their guesses were real tokens. The reason is kept for logs only. + const cases = [ + { expiresAt: new Date(Date.now() - 1000) }, + { usedAt: new Date() }, + { revokedAt: new Date() }, + ]; + const messages = new Set(); + for (const [i, overrides] of cases.entries()) { + const token = generateInvitationToken(); + addInvitation({ token, id: `inv_${i}`, email: `e${i}@x.com`, ...overrides }); + const r = await resolveInvitation(db, token); + if (!r.ok) messages.add(`${r.status}:${r.message}`); + } + const unknown = await resolveInvitation(db, generateInvitationToken()); + if (!unknown.ok) messages.add(`${unknown.status}:${unknown.message}`); + expect(messages.size, [...messages].join(' | ')).toBe(1); + }); + + it('refuses a guessed token', async () => { + addInvitation({ token: generateInvitationToken() }); + const r = await resolveInvitation(db, 'guessed-token-value'); + expect(r.ok).toBe(false); + expect(r.reason).toBe('NOT_FOUND'); + }); +}); + +describe('invitation acceptance', () => { + it('creates a membership at the role the INVITATION names', async () => { + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.MANAGER }); + + const r = await acceptInvitation(db, token, { id: 'u_new', walletAddress: 'GNEW' }); + + expect(r.ok).toBe(true); + const m = db.__tables.orgMember.rows.find((x) => x.userId === 'u_new'); + expect(m.orgId).toBe(A); + expect(m.role).toBe(OrgRole.MANAGER); + expect(m.status).toBe(MembershipStatus.ACTIVE); + }); + + it('is single-use', async () => { + const token = generateInvitationToken(); + addInvitation({ token }); + await acceptInvitation(db, token, { id: 'u_new', walletAddress: 'GNEW' }); + const second = await acceptInvitation(db, token, { id: 'u_other', walletAddress: 'GOTHER' }); + + expect(second.ok).toBe(false); + expect(db.__tables.orgMember.rows.filter((m) => m.userId === 'u_other')).toHaveLength(0); + }); + + it('cannot mint two memberships when two requests race', async () => { + // Both pass the read; only one can win `usedAt IS NULL`. Otherwise one token + // yields two memberships β€” possibly at two different roles. + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.ADMIN }); + + const [a, b] = await Promise.all([ + acceptInvitation(db, token, { id: 'u_race1', walletAddress: 'G1' }), + acceptInvitation(db, token, { id: 'u_race2', walletAddress: 'G2' }), + ]); + + expect([a.ok, b.ok].filter(Boolean)).toHaveLength(1); + const created = db.__tables.orgMember.rows.filter((m) => + ['u_race1', 'u_race2'].includes(m.userId) + ); + expect(created).toHaveLength(1); + }); + + it('does not change the role of someone who already belongs', async () => { + // An invitation must not be usable to alter an existing member's standing, + // in either direction. + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.OWNER }); + + const r = await acceptInvitation(db, token, { id: 'u_mgr', walletAddress: 'GMGR' }); + + expect(r.ok).toBe(true); + if (r.ok) expect(r.value.role).toBe(OrgRole.MANAGER); + expect(db.__tables.orgMember.rows.find((m) => m.userId === 'u_mgr').role) + .toBe(OrgRole.MANAGER); + }); + + it('refuses to re-admit a REMOVED member', async () => { + addMember(A, 'u_gone', OrgRole.VIEWER, 'GGONE', MembershipStatus.REMOVED); + const token = generateInvitationToken(); + addInvitation({ token }); + + const r = await acceptInvitation(db, token, { id: 'u_gone', walletAddress: 'GGONE' }); + + expect(r.ok).toBe(false); + if (!r.ok) expect(r.status).toBe(403); + }); + + it('reactivates a SUSPENDED member at their original role', async () => { + addMember(A, 'u_susp', OrgRole.FINANCE, 'GSUSP', MembershipStatus.SUSPENDED); + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.OWNER }); + + const r = await acceptInvitation(db, token, { id: 'u_susp', walletAddress: 'GSUSP' }); + + expect(r.ok).toBe(true); + const m = db.__tables.orgMember.rows.find((x) => x.userId === 'u_susp'); + expect(m.status).toBe(MembershipStatus.ACTIVE); + // Reinstatement is not a promotion opportunity. + expect(m.role).toBe(OrgRole.FINANCE); + }); + + it('writes an audit event naming the organization and role', async () => { + const token = generateInvitationToken(); + addInvitation({ token, orgRole: OrgRole.VIEWER }); + await acceptInvitation(db, token, { id: 'u_new', walletAddress: 'GNEW' }); + + const e = db.__tables.auditEvent.rows.find((x) => x.type === 'invitation.accepted'); + expect(e.orgId).toBe(A); + expect(e.actorAddress).toBe('GNEW'); + expect(e.metadata.role).toBe(OrgRole.VIEWER); + }); + + it('scopes acceptance to the invitation’s organization only', async () => { + const token = generateInvitationToken(); + addInvitation({ token, orgId: B, orgRole: OrgRole.OWNER }); + + await acceptInvitation(db, token, { id: 'u_new', walletAddress: 'GNEW' }); + + const memberships = db.__tables.orgMember.rows.filter((m) => m.userId === 'u_new'); + expect(memberships).toHaveLength(1); + expect(memberships[0].orgId).toBe(B); + }); +}); + +describe('role assignment', () => { + it('lets an OWNER grant any role', async () => { + const ctx = await ctxFor('u_owner', A); + for (const role of Object.values(OrgRole)) { + expect(checkRoleAssignment(ctx, role), role).toBeNull(); + } + }); + + it('refuses an ADMIN minting an OWNER', async () => { + const ctx = await ctxFor('u_admin', A); + const denial = checkRoleAssignment(ctx, OrgRole.OWNER); + expect(denial?.status).toBe(403); + expect(denial?.code).toBe('ROLE_ESCALATION_REFUSED'); + }); + + it('refuses a MANAGER minting a FINANCE approver', async () => { + // Otherwise a manager manufactures the second approval they are forbidden + // from giving. + const ctx = await ctxFor('u_mgr', A); + expect(checkRoleAssignment(ctx, OrgRole.FINANCE)?.status).toBe(403); + }); + + it('refuses a MANAGER any assignment at all', async () => { + const ctx = await ctxFor('u_mgr', A); + for (const role of Object.values(OrgRole)) { + expect(checkRoleAssignment(ctx, role), role).not.toBeNull(); + } + }); +}); + +describe('self-targeting', () => { + it('refuses changing your own membership', async () => { + // Self-assignment is how a limited role becomes an unlimited one. + const ctx = await ctxFor('u_admin', A); + const denial = checkNotSelf(ctx, 'u_admin'); + expect(denial?.status).toBe(409); + expect(denial?.code).toBe('SELF_TARGETED'); + }); + + it('permits changing someone else', async () => { + const ctx = await ctxFor('u_admin', A); + expect(checkNotSelf(ctx, 'u_mgr')).toBeNull(); + }); +}); + +describe('last administrator protection', () => { + it('refuses removing the only active administrator', async () => { + db.__tables.orgMember.rows = db.__tables.orgMember.rows.filter( + (m) => !(m.orgId === A && m.userId === 'u_admin') + ); + const owner = db.__tables.orgMember.rows.find((m) => m.userId === 'u_owner'); + + const denial = await checkNotLastAdministrator(db, A, owner); + expect(denial?.status).toBe(409); + expect(denial?.code).toBe('LAST_ADMINISTRATOR'); + }); + + it('permits removal while another administrator remains', async () => { + const owner = db.__tables.orgMember.rows.find((m) => m.userId === 'u_owner'); + expect(await checkNotLastAdministrator(db, A, owner)).toBeNull(); + }); + + it('does not count a SUSPENDED administrator as cover', async () => { + // An organization whose only other admin is suspended has nobody who can + // unsuspend them. + db.__tables.orgMember.rows.find((m) => m.userId === 'u_admin').status = + MembershipStatus.SUSPENDED; + const owner = db.__tables.orgMember.rows.find((m) => m.userId === 'u_owner'); + + expect((await checkNotLastAdministrator(db, A, owner))?.code).toBe('LAST_ADMINISTRATOR'); + }); + + it('does not count administrators in a DIFFERENT organization', async () => { + // org B having an owner is irrelevant to org A's recoverability. + db.__tables.orgMember.rows = db.__tables.orgMember.rows.filter( + (m) => !(m.orgId === A && m.userId === 'u_admin') + ); + const owner = db.__tables.orgMember.rows.find((m) => m.userId === 'u_owner'); + expect((await checkNotLastAdministrator(db, A, owner))?.code).toBe('LAST_ADMINISTRATOR'); + }); + + it('ignores non-administrative roles', async () => { + const mgr = db.__tables.orgMember.rows.find((m) => m.userId === 'u_mgr'); + expect(await checkNotLastAdministrator(db, A, mgr)).toBeNull(); + }); +}); + +describe('membership state machine', () => { + it.each([ + [MembershipStatus.INVITED, MembershipStatus.ACTIVE, true], + [MembershipStatus.INVITED, MembershipStatus.REMOVED, true], + [MembershipStatus.INVITED, MembershipStatus.SUSPENDED, false], + [MembershipStatus.ACTIVE, MembershipStatus.SUSPENDED, true], + [MembershipStatus.ACTIVE, MembershipStatus.REMOVED, true], + [MembershipStatus.ACTIVE, MembershipStatus.INVITED, false], + [MembershipStatus.SUSPENDED, MembershipStatus.ACTIVE, true], + [MembershipStatus.SUSPENDED, MembershipStatus.REMOVED, true], + [MembershipStatus.REMOVED, MembershipStatus.ACTIVE, false], + [MembershipStatus.REMOVED, MembershipStatus.INVITED, false], + ])('%s β†’ %s is %s', (from, to, allowed) => { + expect(canTransitionMembership(from, to)).toBe(allowed); + }); + + it('makes REMOVED terminal', () => { + // Re-admitting creates a NEW membership, so the previous one's history stays + // attributable. + expect(membershipTransitionsFrom(MembershipStatus.REMOVED)).toHaveLength(0); + }); + + it('keeps SUSPENDED reinstatable', () => { + expect(membershipTransitionsFrom(MembershipStatus.SUSPENDED)) + .toContain(MembershipStatus.ACTIVE); + }); +}); + +describe('configuration', () => { + it('expires invitations within a week', () => { + expect(INVITATION_TTL_DAYS).toBeLessThanOrEqual(7); + }); +}); diff --git a/src/lib/tenancy/__tests__/rbac.test.ts b/src/lib/tenancy/__tests__/rbac.test.ts new file mode 100644 index 0000000..886ae15 --- /dev/null +++ b/src/lib/tenancy/__tests__/rbac.test.ts @@ -0,0 +1,165 @@ +// @vitest-environment node +/** + * RBAC matrix tests. + * + * The whole permission model is enumerated, including the cells that must be + * EMPTY. A permission table tested only on what it allows will happily allow a + * MANAGER to exercise the finance approval. + */ +import { describe, it, expect } from 'vitest'; +import { OrgRole } from '@prisma/client'; +import { + can, canAll, canAny, permissionsFor, + canAssignRole, assignableRoles, isAdministrative, + ALL_PERMISSIONS, ALL_ROLES, ADMINISTRATIVE_ROLES, + type Permission, +} from '../rbac'; + +describe('matrix integrity', () => { + it('defines a grant list for every role', () => { + for (const role of ALL_ROLES) { + expect(permissionsFor(role), role).toBeDefined(); + } + }); + + it('grants no permission that is not in the permission vocabulary', () => { + for (const role of ALL_ROLES) { + for (const p of permissionsFor(role)) { + expect(ALL_PERMISSIONS, `${role} grants unknown ${p}`).toContain(p); + } + } + }); + + it('lists no duplicate permissions within a role', () => { + for (const role of ALL_ROLES) { + const list = permissionsFor(role); + expect(new Set(list).size, role).toBe(list.length); + } + }); + + it('has no permission that nobody holds', () => { + // An unreachable permission is dead code guarding a real endpoint. + for (const p of ALL_PERMISSIONS) { + expect(ALL_ROLES.some((r) => can(r, p)), `nobody can ${p}`).toBe(true); + } + }); +}); + +describe('separation of duties', () => { + it('does not let a MANAGER exercise the finance approval', () => { + // The product's central claim. A manager holding both halves would make the + // dual-approval gate decorative, exactly as a single on-chain key would. + expect(can(OrgRole.MANAGER, 'payment:approve:manager')).toBe(true); + expect(can(OrgRole.MANAGER, 'payment:approve:finance')).toBe(false); + }); + + it('does not let FINANCE exercise the manager approval', () => { + expect(can(OrgRole.FINANCE, 'payment:approve:finance')).toBe(true); + expect(can(OrgRole.FINANCE, 'payment:approve:manager')).toBe(false); + }); + + it('does not let FINANCE create the payroll it approves', () => { + // An approver who can also create what they approve is not an independent + // check. + expect(can(OrgRole.FINANCE, 'payroll:create')).toBe(false); + expect(can(OrgRole.FINANCE, 'worker:create')).toBe(false); + expect(can(OrgRole.FINANCE, 'escrow:create')).toBe(false); + }); +}); + +describe('VIEWER is read-only', () => { + const MUTATIONS: Permission[] = ALL_PERMISSIONS.filter( + (p) => !p.endsWith(':read') + ) as Permission[]; + + it.each(MUTATIONS)('refuses VIEWER %s', (p) => { + expect(can(OrgRole.VIEWER, p)).toBe(false); + }); + + it('allows VIEWER the read permissions', () => { + expect(can(OrgRole.VIEWER, 'payment:read')).toBe(true); + expect(can(OrgRole.VIEWER, 'audit:read')).toBe(true); + }); +}); + +describe('WORKER is a payee, not an operator', () => { + it.each(ALL_PERMISSIONS)('grants WORKER nothing: %s', (p) => { + // Organization-wide payment:read would let any contractor enumerate the + // whole payroll, including colleagues' rates. Self-scoped access is served + // by paymentReadScope() instead. + expect(can(OrgRole.WORKER, p)).toBe(false); + }); + + it('holds an empty grant list', () => { + expect(permissionsFor(OrgRole.WORKER)).toHaveLength(0); + }); +}); + +describe('organization deletion', () => { + it('is restricted to OWNER', () => { + expect(can(OrgRole.OWNER, 'org:delete')).toBe(true); + for (const role of ALL_ROLES.filter((r) => r !== OrgRole.OWNER)) { + expect(can(role, 'org:delete'), role).toBe(false); + } + }); +}); + +describe('role delegation', () => { + it('lets an OWNER grant any role, including another OWNER', () => { + // An organization with exactly one owner has no recovery path if that key is + // lost, so owners must be able to create a peer. + for (const role of ALL_ROLES) { + expect(canAssignRole(OrgRole.OWNER, role), role).toBe(true); + } + }); + + it('does NOT let an ADMIN mint an OWNER', () => { + // Otherwise an admin can take the organization. + expect(canAssignRole(OrgRole.ADMIN, OrgRole.OWNER)).toBe(false); + expect(canAssignRole(OrgRole.ADMIN, OrgRole.ADMIN)).toBe(true); + }); + + it('does NOT let a MANAGER mint a FINANCE approver', () => { + // Otherwise a manager manufactures the second approval they are forbidden + // from giving. + expect(canAssignRole(OrgRole.MANAGER, OrgRole.FINANCE)).toBe(false); + expect(assignableRoles(OrgRole.MANAGER)).toHaveLength(0); + }); + + it.each([OrgRole.MANAGER, OrgRole.FINANCE, OrgRole.WORKER, OrgRole.VIEWER])( + 'gives %s no delegation authority at all', + (role) => { + expect(assignableRoles(role)).toHaveLength(0); + for (const target of ALL_ROLES) { + expect(canAssignRole(role, target), `${role} -> ${target}`).toBe(false); + } + } + ); + + it('requires the assign permission as well as delegation', () => { + // Both are checked: holding member:role:assign does not imply every role is + // within reach. + expect(can(OrgRole.MANAGER, 'member:role:assign')).toBe(false); + expect(can(OrgRole.ADMIN, 'member:role:assign')).toBe(true); + }); +}); + +describe('administrative roles', () => { + it('counts exactly OWNER and ADMIN', () => { + expect([...ADMINISTRATIVE_ROLES].sort()).toEqual([OrgRole.ADMIN, OrgRole.OWNER].sort()); + expect(isAdministrative(OrgRole.MANAGER)).toBe(false); + expect(isAdministrative(OrgRole.OWNER)).toBe(true); + }); +}); + +describe('helpers', () => { + it('canAll requires every permission', () => { + expect(canAll(OrgRole.MANAGER, ['payment:read', 'payment:approve:manager'])).toBe(true); + expect(canAll(OrgRole.MANAGER, ['payment:read', 'payment:approve:finance'])).toBe(false); + }); + + it('canAny requires one', () => { + expect(canAny(OrgRole.FINANCE, ['payment:approve:manager', 'payment:approve:finance'])).toBe(true); + expect(canAny(OrgRole.WORKER, ['payment:read', 'audit:read'])).toBe(false); + }); +}); diff --git a/src/lib/tenancy/http.ts b/src/lib/tenancy/http.ts new file mode 100644 index 0000000..3671cc1 --- /dev/null +++ b/src/lib/tenancy/http.ts @@ -0,0 +1,113 @@ +/** + * HTTP plumbing for tenant-scoped routes. + * + * ── Where the organization comes from ──────────────────────────────────────── + * The client may NAME which of its organizations to act in (header, query or + * body). It may never assert membership or role β€” those are read from the + * database by `resolveTenant` on every request. + * + * When the caller belongs to exactly one organization, that one is used. When + * they belong to several, the request must say which: guessing would let an + * action be performed in the wrong tenant's name, and a payment approved in the + * wrong organization is not a recoverable mistake. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import prisma from '@/lib/db/prisma'; +import { getUserFromRequest } from '@/lib/auth'; +import { MembershipStatus } from '@prisma/client'; +import { resolveTenant, requirePermission, type TenantContext, type Denial } from './resolve'; +import type { Permission } from './rbac'; + +export const ORG_HEADER = 'x-organization-id'; + +export function denialResponse(d: Denial): NextResponse { + return NextResponse.json( + { error: d.message, ...(d.code ? { code: d.code } : {}) }, + { status: d.status } + ); +} + +/** Read the requested organization from the request, without trusting it. */ +export function requestedOrgId(request: NextRequest, body?: any): string | null { + const fromHeader = request.headers.get(ORG_HEADER); + if (fromHeader) return fromHeader; + const fromQuery = new URL(request.url).searchParams.get('orgId'); + if (fromQuery) return fromQuery; + if (body && typeof body.orgId === 'string') return body.orgId; + return null; +} + +export interface TenantRequest { + ctx: TenantContext; + body: any; + /** Caller-supplied key for retry-safe financial mutations. */ + idempotencyKey?: string; +} + +/** + * Resolve authentication β†’ membership β†’ (optionally) permission, then run the + * handler. Every tenant-scoped route goes through this, so no route can forget a + * layer. + */ +export async function withTenant( + request: NextRequest, + opts: { permission?: Permission; parseBody?: boolean }, + handler: (req: TenantRequest) => Promise +): Promise { + const user = await getUserFromRequest(request); + if (!user) { + return NextResponse.json({ error: 'Authentication required.' }, { status: 401 }); + } + + const body = opts.parseBody === false ? undefined : await request.json().catch(() => ({})); + + let orgId = requestedOrgId(request, body); + if (!orgId) { + // Sole membership is unambiguous; several is not. + const memberships = await prisma.orgMember.findMany({ + where: { userId: user.userId, status: MembershipStatus.ACTIVE }, + select: { orgId: true }, + take: 2, + }); + if (memberships.length === 1) { + orgId = memberships[0].orgId; + } else { + return NextResponse.json( + { + error: + memberships.length === 0 + ? 'You do not belong to any organization yet.' + : `Specify the organization for this request via the ${ORG_HEADER} header.`, + code: memberships.length === 0 ? 'NO_ORGANIZATION' : 'ORGANIZATION_REQUIRED', + }, + { status: memberships.length === 0 ? 403 : 400 } + ); + } + } + + const tenant = await resolveTenant(prisma, user.userId, orgId); + if (!tenant.ok) return denialResponse(tenant); + + if (opts.permission) { + const denied = requirePermission(tenant.value, opts.permission); + if (denied) return denialResponse(denied); + } + + try { + return await handler({ + ctx: tenant.value, + body, + idempotencyKey: + request.headers.get('idempotency-key') ?? body?.idempotencyKey ?? undefined, + }); + } catch (e: any) { + // Never echo a database error: constraint names and column names describe the + // schema, and a failed composite FK would reveal another tenant's id space. + console.error('[tenant route] failed:', e?.message); + return NextResponse.json( + { error: 'The request could not be completed.' }, + { status: 500 } + ); + } +} diff --git a/src/lib/tenancy/membership.ts b/src/lib/tenancy/membership.ts new file mode 100644 index 0000000..a5aa91a --- /dev/null +++ b/src/lib/tenancy/membership.ts @@ -0,0 +1,337 @@ +/** + * Membership lifecycle and invitations. + * + * ── Why this file is cautious ──────────────────────────────────────────────── + * Membership is where privilege escalation lives. An invitation is a + * client-reachable object that grants authority; a role change is a direct grant + * of it. Three specific attacks are refused explicitly below, because each is a + * plausible mistake rather than an exotic one: + * + * 1. Granting a role above your own (ADMIN minting an OWNER). + * 2. Granting a role you are forbidden to exercise (MANAGER minting FINANCE, + * then approving their own payment with the second key). + * 3. Removing the last administrator, leaving an organization nobody can run. + * + * Invitation tokens are stored HASHED. A database dump must not hand over working + * invitations, and an invitation is a bearer credential for joining a tenant. + */ + +import { randomBytes, createHash, timingSafeEqual } from 'crypto'; +import { OrgRole, MembershipStatus } from '@prisma/client'; +import { canAssignRole, isAdministrative, ADMINISTRATIVE_ROLES } from './rbac'; +import { requirePermission, type TenantContext, type Denial, type Result } from './resolve'; + +export const INVITATION_TTL_DAYS = 7; +/** 32 bytes of CSPRNG output β€” not guessable, and not derived from any input. */ +const TOKEN_BYTES = 32; + +export function generateInvitationToken(): string { + return randomBytes(TOKEN_BYTES).toString('base64url'); +} + +export function hashInvitationToken(token: string): string { + return createHash('sha256').update(token, 'utf8').digest('hex'); +} + +/** + * Constant-time comparison of two token hashes. + * + * Both sides are already fixed-length hex digests, so length is not secret; the + * comparison is constant-time anyway to avoid leaking how long a correct prefix + * was, which with enough attempts narrows the search. + */ +export function tokenHashesMatch(a: string, b: string): boolean { + const ba = Buffer.from(a, 'utf8'); + const bb = Buffer.from(b, 'utf8'); + if (ba.length !== bb.length) return false; + return timingSafeEqual(ba, bb); +} + +// ── Membership state machine ───────────────────────────────────────────────── + +const MEMBERSHIP_TRANSITIONS: Record = { + [MembershipStatus.INVITED]: [MembershipStatus.ACTIVE, MembershipStatus.REMOVED], + [MembershipStatus.ACTIVE]: [MembershipStatus.SUSPENDED, MembershipStatus.REMOVED], + // Reinstatable: suspension is a temporary revocation, not a deletion. + [MembershipStatus.SUSPENDED]: [MembershipStatus.ACTIVE, MembershipStatus.REMOVED], + // Terminal. Re-admitting someone creates a NEW membership, so the history of + // the previous one stays intact and attributable. + [MembershipStatus.REMOVED]: [], +}; + +export function canTransitionMembership( + from: MembershipStatus, + to: MembershipStatus +): boolean { + return (MEMBERSHIP_TRANSITIONS[from] ?? []).includes(to); +} + +export function membershipTransitionsFrom( + from: MembershipStatus +): readonly MembershipStatus[] { + return MEMBERSHIP_TRANSITIONS[from] ?? []; +} + +// ── Guards ─────────────────────────────────────────────────────────────────── + +/** + * Refuse a grant the actor is not entitled to make. + * + * Checked as a pair: the actor needs the permission to assign roles AT ALL, and + * separately the specific target role must be within their delegation. Holding + * `member:role:assign` does not imply being able to grant every role. + */ +export function checkRoleAssignment( + ctx: TenantContext, + targetRole: OrgRole +): Denial | null { + const denied = requirePermission(ctx, 'member:role:assign'); + if (denied) return denied; + + if (!canAssignRole(ctx.role, targetRole)) { + return { + ok: false, + status: 403, + message: + `A ${ctx.role} cannot grant the ${targetRole} role. ` + + 'Roles can only be delegated at or below your own level.', + code: 'ROLE_ESCALATION_REFUSED', + }; + } + return null; +} + +/** + * Refuse an action that would leave the organization with no administrator. + * + * Counts only ACTIVE administrative memberships. An organization whose sole owner + * is suspended has nobody who can unsuspend them, which is unrecoverable without + * operator intervention β€” so the transition is blocked before it happens rather + * than repaired afterwards. + */ +export async function checkNotLastAdministrator( + db: any, + orgId: string, + member: { id: string; role: OrgRole; status: MembershipStatus } +): Promise { + if (!isAdministrative(member.role)) return null; + if (member.status !== MembershipStatus.ACTIVE) return null; + + const remaining = await db.orgMember.count({ + where: { + orgId, + status: MembershipStatus.ACTIVE, + role: { in: ADMINISTRATIVE_ROLES as OrgRole[] }, + id: { not: member.id }, + }, + }); + + if (remaining === 0) { + return { + ok: false, + status: 409, + message: + 'This is the organization’s last active administrator. Promote another ' + + 'member to OWNER or ADMIN first β€” otherwise nobody could manage the ' + + 'organization afterwards.', + code: 'LAST_ADMINISTRATOR', + }; + } + return null; +} + +/** Refuse self-targeted privilege changes. */ +export function checkNotSelf(ctx: TenantContext, targetUserId: string): Denial | null { + if (ctx.userId !== targetUserId) return null; + return { + ok: false, + status: 409, + message: + 'You cannot change your own role or membership status. Ask another ' + + 'administrator β€” self-assignment is how a limited role becomes an ' + + 'unlimited one.', + code: 'SELF_TARGETED', + }; +} + +// ── Invitation acceptance ──────────────────────────────────────────────────── + +export interface AcceptableInvitation { + id: string; + orgId: string; + orgRole: OrgRole; + email: string; +} + +export type InvitationRejection = + | 'NOT_FOUND' + | 'EXPIRED' + | 'ALREADY_USED' + | 'REVOKED'; + +/** + * Look up an invitation by its plaintext token and decide whether it is usable. + * + * Every rejection reason is returned to the CALLER as the same generic failure by + * the route: distinguishing "expired" from "never existed" tells an attacker + * which of their guesses were real tokens. The specific reason is kept here for + * logging and for the audit trail. + */ +export async function resolveInvitation( + db: any, + token: string +): Promise & { reason?: InvitationRejection }> { + const tokenHash = hashInvitationToken(token); + + const invitation = await db.invitation.findUnique({ where: { tokenHash } }); + + if (!invitation) { + return { ok: false, status: 404, message: 'Invitation not found.', reason: 'NOT_FOUND' }; + } + // Defence in depth: the lookup was by unique hash, but comparing explicitly + // means a future change to the lookup cannot quietly drop the check. + if (!tokenHashesMatch(invitation.tokenHash, tokenHash)) { + return { ok: false, status: 404, message: 'Invitation not found.', reason: 'NOT_FOUND' }; + } + if (invitation.revokedAt) { + return { ok: false, status: 404, message: 'Invitation not found.', reason: 'REVOKED' }; + } + if (invitation.usedAt) { + return { ok: false, status: 404, message: 'Invitation not found.', reason: 'ALREADY_USED' }; + } + if (invitation.expiresAt.getTime() <= Date.now()) { + return { ok: false, status: 404, message: 'Invitation not found.', reason: 'EXPIRED' }; + } + + return { + ok: true, + value: { + id: invitation.id, + orgId: invitation.orgId, + orgRole: invitation.orgRole, + email: invitation.email, + }, + }; +} + +/** + * Accept an invitation: mark it used and create or reactivate the membership. + * + * Single-use is enforced by a conditional update inside the transaction rather + * than by the read above. Two requests racing with the same token would both pass + * the read; only one can win `usedAt IS NULL`, so a token cannot mint two + * memberships β€” or, worse, two memberships at two different roles. + */ +export async function acceptInvitation( + db: any, + token: string, + user: { id: string; walletAddress: string } +): Promise> { + const resolved = await resolveInvitation(db, token); + if (!resolved.ok) return resolved as Denial; + const invitation = resolved.value; + + return db.$transaction(async (tx: any) => { + const claimed = await tx.invitation.updateMany({ + where: { id: invitation.id, usedAt: null, revokedAt: null }, + data: { usedAt: new Date() }, + }); + if (claimed.count === 0) { + return { + ok: false as const, + status: 404 as const, + message: 'Invitation not found.', + }; + } + + const existing = await tx.orgMember.findUnique({ + where: { orgId_userId: { orgId: invitation.orgId, userId: user.id } }, + }); + + if (existing) { + // Already a member. The invitation is consumed either way, but an existing + // role is NOT overwritten: an invitation must not be usable to change the + // standing of someone who already belongs, in either direction. + if (existing.status === MembershipStatus.ACTIVE) { + return { + ok: true as const, + value: { orgId: invitation.orgId, role: existing.role }, + }; + } + if (existing.status === MembershipStatus.REMOVED) { + return { + ok: false as const, + status: 403 as const, + message: + 'Your membership of this organization was removed. An administrator ' + + 'must issue a new invitation.', + }; + } + const reactivated = await tx.orgMember.update({ + where: { id: existing.id }, + data: { + status: MembershipStatus.ACTIVE, + activatedAt: new Date(), + suspendedAt: null, + }, + }); + await writeMembershipAudit(tx, { + orgId: invitation.orgId, + type: 'member.reactivated', + actorAddress: user.walletAddress, + targetUserId: user.id, + metadata: { invitationId: invitation.id, role: reactivated.role }, + }); + return { ok: true as const, value: { orgId: invitation.orgId, role: reactivated.role } }; + } + + const created = await tx.orgMember.create({ + data: { + orgId: invitation.orgId, + userId: user.id, + // The role comes from the INVITATION, written by an authorized inviter β€” + // never from the acceptance request. + role: invitation.orgRole, + status: MembershipStatus.ACTIVE, + invitedAt: new Date(), + activatedAt: new Date(), + }, + }); + + await writeMembershipAudit(tx, { + orgId: invitation.orgId, + type: 'invitation.accepted', + actorAddress: user.walletAddress, + targetUserId: user.id, + metadata: { invitationId: invitation.id, role: created.role }, + }); + + return { ok: true as const, value: { orgId: invitation.orgId, role: created.role } }; + }); +} + +/** Append a membership-related audit row. */ +export async function writeMembershipAudit( + db: any, + input: { + orgId: string; + type: string; + actorAddress?: string; + actorSystem?: string; + targetUserId?: string; + metadata?: Record; + } +): Promise { + await db.auditEvent.create({ + data: { + orgId: input.orgId, + type: input.type, + actorAddress: input.actorAddress ?? null, + actorSystem: input.actorSystem ?? null, + metadata: { + ...(input.targetUserId ? { targetUserId: input.targetUserId } : {}), + ...(input.metadata ?? {}), + } as any, + }, + }); +} diff --git a/src/lib/tenancy/rbac.ts b/src/lib/tenancy/rbac.ts new file mode 100644 index 0000000..a64e4ac --- /dev/null +++ b/src/lib/tenancy/rbac.ts @@ -0,0 +1,244 @@ +/** + * CoreFlow role-based permissions. + * + * ── Why this is a table and not scattered `if` statements ──────────────────── + * A permission model spread across route handlers cannot be reviewed, cannot be + * rendered as a matrix, and cannot be tested exhaustively. Every question an + * auditor asks β€” "who can approve finance?", "can a VIEWER see treasury?" β€” has + * to be answered by reading control flow. Declared as data, the whole model fits + * on one screen and every cell is enumerable by a test. + * + * ── The boundary this sits inside ──────────────────────────────────────────── + * AUTHENTICATION who are you (wallet signature, session) + * MEMBERSHIP which organizations (OrgMember, status ACTIVE) + * ROLE what may you do ← THIS FILE + * RESOURCE OWNERSHIP is this record in scope (tenancy/resolve.ts + DB FKs) + * BUSINESS RULE is the action valid now (payments/state-machine.ts) + * BLOCKCHAIN did it actually happen (contract + indexer) + * + * These layers are deliberately separate. Holding a role does not imply owning a + * record, and owning a record does not imply the action is valid right now. + */ + +import { OrgRole } from '@prisma/client'; + +/** + * Every distinct capability in the product. + * + * Named after what the actor is trying to DO, not after an endpoint, so the same + * permission governs the API, the UI and any future surface. + */ +export type Permission = + // Organization + | 'org:read' + | 'org:update' + | 'org:delete' + // Membership + | 'member:read' + | 'member:invite' + | 'member:role:assign' + | 'member:suspend' + | 'member:remove' + // Projects + | 'project:read' + | 'project:create' + | 'project:update' + | 'project:archive' + // Workers + | 'worker:read' + | 'worker:create' + | 'worker:update' + | 'worker:archive' + // Payroll + | 'payroll:read' + | 'payroll:create' + | 'payroll:update' + | 'payroll:delete' + // Payments + | 'payment:read' + | 'payment:approve:manager' + | 'payment:approve:finance' + | 'payment:reject' + | 'payment:cancel' + | 'payment:submit' + | 'payment:retry' + // Escrow / chain + | 'escrow:read' + | 'escrow:create' + | 'escrow:cancel' + | 'oracle:attest:request' + // Treasury + | 'treasury:read' + // Audit & reconciliation + | 'audit:read' + | 'reconciliation:read' + | 'reconciliation:resolve'; + +/** Everything a role may do. Absence of a permission is a denial. */ +const GRANTS: Record = { + /** + * OWNER β€” full authority, including destroying the organization. + * + * Note what this does NOT mean: an owner still cannot satisfy both halves of + * the dual-approval gate. See `approvePayment`, which refuses a second + * approval from a wallet that already recorded the first. + */ + [OrgRole.OWNER]: [ + 'org:read', 'org:update', 'org:delete', + 'member:read', 'member:invite', 'member:role:assign', 'member:suspend', 'member:remove', + 'project:read', 'project:create', 'project:update', 'project:archive', + 'worker:read', 'worker:create', 'worker:update', 'worker:archive', + 'payroll:read', 'payroll:create', 'payroll:update', 'payroll:delete', + 'payment:read', 'payment:approve:manager', 'payment:approve:finance', + 'payment:reject', 'payment:cancel', 'payment:submit', 'payment:retry', + 'escrow:read', 'escrow:create', 'escrow:cancel', 'oracle:attest:request', + 'treasury:read', + 'audit:read', 'reconciliation:read', 'reconciliation:resolve', + ], + + /** ADMIN β€” operational authority, but cannot delete the organization. */ + [OrgRole.ADMIN]: [ + 'org:read', 'org:update', + 'member:read', 'member:invite', 'member:role:assign', 'member:suspend', 'member:remove', + 'project:read', 'project:create', 'project:update', 'project:archive', + 'worker:read', 'worker:create', 'worker:update', 'worker:archive', + 'payroll:read', 'payroll:create', 'payroll:update', 'payroll:delete', + 'payment:read', 'payment:approve:manager', 'payment:approve:finance', + 'payment:reject', 'payment:cancel', 'payment:submit', 'payment:retry', + 'escrow:read', 'escrow:create', 'escrow:cancel', 'oracle:attest:request', + 'treasury:read', + 'audit:read', 'reconciliation:read', 'reconciliation:resolve', + ], + + /** + * MANAGER β€” prepares and authorizes work, holds the MANAGER half of the gate. + * + * Deliberately lacks `payment:approve:finance`. A manager who could exercise + * the finance approval would collapse separation of duties, which is the + * product's central claim and what the contract enforces on-chain with + * SignersNotDistinct. + */ + [OrgRole.MANAGER]: [ + 'org:read', + 'member:read', + 'project:read', + 'worker:read', 'worker:create', 'worker:update', + 'payroll:read', 'payroll:create', 'payroll:update', + 'payment:read', 'payment:approve:manager', 'payment:reject', 'payment:cancel', + 'payment:submit', 'payment:retry', + 'escrow:read', 'escrow:create', 'oracle:attest:request', + 'treasury:read', + 'audit:read', 'reconciliation:read', + ], + + /** + * FINANCE β€” holds the FINANCE half of the gate and controls money leaving. + * + * Deliberately lacks `payment:approve:manager`, `payroll:create` and + * `worker:create`: an approver who can also create the thing they approve is + * not an independent check. + */ + [OrgRole.FINANCE]: [ + 'org:read', + 'member:read', + 'project:read', + 'worker:read', + 'payroll:read', + 'payment:read', 'payment:approve:finance', 'payment:reject', 'payment:cancel', + 'payment:submit', 'payment:retry', + 'escrow:read', + 'treasury:read', + 'audit:read', 'reconciliation:read', + ], + + /** + * WORKER β€” a payee, not an operator. + * + * Intentionally holds NO permissions here. A worker's own payment history is + * served by a separate self-scoped path that filters on their wallet address, + * not by organization-wide `payment:read` β€” granting that would let any payee + * enumerate the whole payroll, including their colleagues' rates. + */ + [OrgRole.WORKER]: [], + + /** VIEWER β€” read-only. No mutation, no approval, no treasury movement. */ + [OrgRole.VIEWER]: [ + 'org:read', + 'member:read', + 'project:read', + 'worker:read', + 'payroll:read', + 'payment:read', + 'escrow:read', + 'treasury:read', + 'audit:read', 'reconciliation:read', + ], +}; + +export function permissionsFor(role: OrgRole): readonly Permission[] { + return GRANTS[role] ?? []; +} + +/** True if `role` holds `permission`. The only way to ask this question. */ +export function can(role: OrgRole, permission: Permission): boolean { + return (GRANTS[role] ?? []).includes(permission); +} + +export function canAll(role: OrgRole, permissions: readonly Permission[]): boolean { + return permissions.every((p) => can(role, p)); +} + +export function canAny(role: OrgRole, permissions: readonly Permission[]): boolean { + return permissions.some((p) => can(role, p)); +} + +/** Every permission in the model, for matrix generation and exhaustive tests. */ +export const ALL_PERMISSIONS: readonly Permission[] = Array.from( + new Set(Object.values(GRANTS).flat()) +).sort() as Permission[]; + +export const ALL_ROLES: readonly OrgRole[] = Object.values(OrgRole); + +// ── Role delegation ────────────────────────────────────────────────────────── + +/** + * Which roles a given role may grant. + * + * Strictly below the granter's own level, with one deliberate exception: an OWNER + * may create another OWNER, because an organization with exactly one owner has no + * recovery path if that key is lost. + * + * This is the main privilege-escalation surface in a multi-tenant product: an + * ADMIN able to grant OWNER could take the organization, and a MANAGER able to + * grant FINANCE could manufacture the second approval they are forbidden from + * giving. Both are refused. + */ +const DELEGATABLE: Record = { + [OrgRole.OWNER]: [ + OrgRole.OWNER, OrgRole.ADMIN, OrgRole.MANAGER, + OrgRole.FINANCE, OrgRole.WORKER, OrgRole.VIEWER, + ], + // An ADMIN may staff the organization but may not create a peer owner. + [OrgRole.ADMIN]: [ + OrgRole.ADMIN, OrgRole.MANAGER, OrgRole.FINANCE, OrgRole.WORKER, OrgRole.VIEWER, + ], + [OrgRole.MANAGER]: [], + [OrgRole.FINANCE]: [], + [OrgRole.WORKER]: [], + [OrgRole.VIEWER]: [], +}; + +export function canAssignRole(actor: OrgRole, target: OrgRole): boolean { + return (DELEGATABLE[actor] ?? []).includes(target); +} + +export function assignableRoles(actor: OrgRole): readonly OrgRole[] { + return DELEGATABLE[actor] ?? []; +} + +/** Roles that keep an organization administrable. At least one must remain. */ +export const ADMINISTRATIVE_ROLES: readonly OrgRole[] = [OrgRole.OWNER, OrgRole.ADMIN]; + +export function isAdministrative(role: OrgRole): boolean { + return ADMINISTRATIVE_ROLES.includes(role); +} diff --git a/src/lib/tenancy/resolve.ts b/src/lib/tenancy/resolve.ts new file mode 100644 index 0000000..03389b3 --- /dev/null +++ b/src/lib/tenancy/resolve.ts @@ -0,0 +1,273 @@ +/** + * The tenant boundary. Every tenant-scoped request passes through here. + * + * ── The rule ───────────────────────────────────────────────────────────────── + * Organization identity is derived from AUTHENTICATED MEMBERSHIP, read from the + * database on every request. Nothing from the client is trusted: not an + * `organizationId` field, not a role, not a project id, not a hidden form field, + * and certainly not client-side route protection. + * + * A client may *name* which of its organizations it wants to act in. It may never + * assert that it belongs to one, nor what it can do there. + * + * ── Why 404 and not 403 ────────────────────────────────────────────────────── + * A 403 on a foreign resource confirms the resource exists. Repeated against a + * range of ids that turns into an enumeration oracle: an attacker learns how many + * payments another tenant has, and roughly what they are worth, without reading a + * single record. Every cross-tenant miss therefore looks exactly like a genuine + * miss. A 403 is reserved for resources the caller CAN see but may not act on. + * + * ── Why scoping is in the WHERE clause ─────────────────────────────────────── + * Resources are loaded with `orgId` as part of the query, never fetched by global + * id and checked afterwards. A post-fetch check still performs the read, and any + * logging, error path or timing difference around it can disclose existence. The + * composite foreign keys in the schema back this up at the database level, so even + * a query that forgot its filter cannot join across tenants. + */ + +import { OrgRole, MembershipStatus } from '@prisma/client'; +import { can, type Permission } from './rbac'; + +export type Denial = { + ok: false; + /** + * 401 unauthenticated Β· 403 visible but not permitted Β· 404 outside scope + * (see the note above) Β· 409 a real conflict with organization state, such as + * removing the last administrator Β· 400 a malformed request. + */ + status: 400 | 401 | 403 | 404 | 409; + message: string; + code?: string; +}; +export type Allowed = { ok: true; value: T }; +export type Result = Allowed | Denial; + +/** A resolved, ACTIVE membership. The basis of every authorization decision. */ +export interface TenantContext { + orgId: string; + orgName: string; + orgSlug: string; + userId: string; + walletAddress: string; + role: OrgRole; +} + +const NOT_FOUND = (what: string): Denial => ({ + ok: false, + status: 404, + message: `${what} not found.`, +}); + +/** + * Resolve the caller's membership of `orgId`. + * + * Only an ACTIVE membership counts. INVITED has not accepted; SUSPENDED has had + * access revoked; REMOVED is gone. All three are indistinguishable from + * non-membership to the caller, so suspending someone does not tell them they + * were ever a member. + */ +export async function resolveTenant( + db: any, + userId: string | undefined, + orgId: string | undefined | null +): Promise> { + if (!userId) { + return { ok: false, status: 401, message: 'Authentication required.' }; + } + if (!orgId) { + return { + ok: false, + status: 400, + message: 'An organization must be specified for this request.', + code: 'ORGANIZATION_REQUIRED', + }; + } + + const member = await db.orgMember.findUnique({ + where: { orgId_userId: { orgId, userId } }, + include: { + org: { select: { id: true, name: true, slug: true } }, + user: { select: { walletAddress: true } }, + }, + }); + + if (!member || member.status !== MembershipStatus.ACTIVE) { + return NOT_FOUND('Organization'); + } + + return { + ok: true, + value: { + orgId: member.orgId, + orgName: member.org?.name ?? '', + orgSlug: member.org?.slug ?? '', + userId: member.userId, + walletAddress: member.user?.walletAddress ?? '', + role: member.role, + }, + }; +} + +/** Every organization the caller is an ACTIVE member of. */ +export async function listTenants( + db: any, + userId: string +): Promise<{ orgId: string; name: string; slug: string; role: OrgRole }[]> { + const rows = await db.orgMember.findMany({ + where: { userId, status: MembershipStatus.ACTIVE }, + include: { org: { select: { id: true, name: true, slug: true } } }, + orderBy: { createdAt: 'asc' }, + }); + return rows.map((m: any) => ({ + orgId: m.orgId, + name: m.org?.name ?? '', + slug: m.org?.slug ?? '', + role: m.role, + })); +} + +/** + * Require a permission. Returns 403, not 404: the caller demonstrably belongs to + * this organization, so there is nothing to conceal β€” telling them their role is + * insufficient is useful, and reveals nothing they could not already see. + */ +export function requirePermission( + ctx: TenantContext, + permission: Permission +): Denial | null { + if (can(ctx.role, permission)) return null; + return { + ok: false, + status: 403, + message: `Your role (${ctx.role}) cannot perform this action.`, + code: 'PERMISSION_DENIED', + }; +} + +/** + * Require ANY ONE of several permissions. + * + * Dual approval needs this: a batch approval is legitimate from a manager + * (`payment:approve:manager`) or from finance (`payment:approve:finance`), and + * gating on either one alone would reject half the people entitled to act. The + * caller's role still decides WHICH half they exercise β€” that is derived from + * membership in `approvePayment`, never from the request. + */ +export function requireAnyPermission( + ctx: TenantContext, + permissions: readonly Permission[] +): Denial | null { + if (permissions.some((p) => can(ctx.role, p))) return null; + return { + ok: false, + status: 403, + message: `Your role (${ctx.role}) cannot perform this action.`, + code: 'PERMISSION_DENIED', + }; +} + +// ── Resource resolution ────────────────────────────────────────────────────── +// +// One function per resource type. Each takes the resolved TenantContext and +// returns the record or a 404. Possession of an id never implies authorization, +// so there is no variant of these that skips the scope. + +type Include = Record | undefined; + +async function scoped( + db: any, + model: string, + label: string, + ctx: TenantContext, + id: string, + include?: Include +): Promise> { + const row = await db[model].findFirst({ + where: { id, orgId: ctx.orgId }, + ...(include ? { include } : {}), + }); + return row ? { ok: true, value: row } : NOT_FOUND(label); +} + +export const findPayment = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'payment', 'Payment', ctx, id, inc); + +export const findBatch = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'payrollBatch', 'Batch', ctx, id, inc); + +export const findEscrow = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'escrow', 'Escrow', ctx, id, inc); + +export const findProject = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'project', 'Project', ctx, id, inc); + +export const findWorker = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'worker', 'Worker', ctx, id, inc); + +export const findTransaction = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'blockchainTransaction', 'Transaction', ctx, id, inc); + +export const findAuditEvent = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'auditEvent', 'Audit event', ctx, id, inc); + +export const findFinding = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'reconciliationFinding', 'Finding', ctx, id, inc); + +export const findMember = (db: any, ctx: TenantContext, id: string, inc?: Include) => + scoped(db, 'orgMember', 'Member', ctx, id, inc); + +/** + * Resolve an escrow by its ON-CHAIN id within the caller's organization. + * + * `onChainId` is globally unique across the database but NOT per-tenant-safe on + * its own: escrow ids are assigned by the contract, so org A and org B both have + * an escrow "3" if they use different deployments. Resolving it without the + * organization filter would hand one tenant another's escrow. + */ +export async function findEscrowByOnChainId( + db: any, + ctx: TenantContext, + onChainId: number, + include?: Include +): Promise> { + const row = await db.escrow.findFirst({ + where: { onChainId, orgId: ctx.orgId }, + ...(include ? { include } : {}), + }); + return row ? { ok: true, value: row } : NOT_FOUND('Escrow'); +} + +/** + * Confirm a project belongs to the caller's organization, for use when a project + * is supplied as an INPUT rather than looked up. + * + * Without this, a create request could carry `projectId` from another tenant. The + * composite foreign key would reject the write, but that surfaces as a 500 β€” this + * turns it into an honest 404 before anything is attempted. + */ +export async function assertProjectInTenant( + db: any, + ctx: TenantContext, + projectId: string | null | undefined +): Promise { + if (!projectId) return null; + const found = await findProject(db, ctx, projectId); + return found.ok ? null : (found as Denial); +} + +/** A `where` fragment that scopes any tenant-owned query. */ +export function tenantScope(ctx: TenantContext): { orgId: string } { + return { orgId: ctx.orgId }; +} + +/** + * Scope a payment query for a caller whose role has no organization-wide read. + * + * A WORKER is a payee, not an operator: they may see payments made to their own + * wallet and nothing else. Granting them `payment:read` would let any contractor + * enumerate the entire payroll, including colleagues' rates. + */ +export function paymentReadScope(ctx: TenantContext): Record { + if (can(ctx.role, 'payment:read')) return { orgId: ctx.orgId }; + return { orgId: ctx.orgId, recipientAddress: ctx.walletAddress }; +} diff --git a/src/lib/validation/__tests__/validation.test.ts b/src/lib/validation/__tests__/validation.test.ts index 42baf56..d18ca9f 100644 --- a/src/lib/validation/__tests__/validation.test.ts +++ b/src/lib/validation/__tests__/validation.test.ts @@ -12,15 +12,15 @@ describe('parseBody / schemas', () => { it('accepts a valid escrow body and rejects bad amounts', () => { const ok = parseBody(createEscrowSchema, { workerPubKey: 'GWORKER', - amountCents: 4200, - rateCents: 250, + amountBaseUnits: '42000000000', + rateBaseUnits: '25000000', }); expect(ok.ok).toBe(true); const bad = parseBody(createEscrowSchema, { workerPubKey: 'GWORKER', - amountCents: -1, - rateCents: 250, + amountBaseUnits: '-1', + rateBaseUnits: '25000000', }); expect(bad.ok).toBe(false); }); @@ -29,8 +29,8 @@ describe('parseBody / schemas', () => { const r = parseBody(createEscrowSchema, { onChainId: 0, workerPubKey: 'GWORKER', - amountCents: 10, - rateCents: 1, + amountBaseUnits: '10', + rateBaseUnits: '100000', }); expect(r.ok).toBe(false); }); diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index c4dc736..d57011c 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -19,11 +19,26 @@ export const verifySchema = z.object({ signature: z.string().min(1, 'signature is required'), }); +/** + * Base-unit amounts arrive as decimal STRINGS, not numbers. + * + * JSON has no bigint, and a large payroll batch in 7-decimal base units + * exceeds Number.MAX_SAFE_INTEGER (2^53-1) at roughly 900 million units of the + * asset β€” where a JSON number would start silently rounding. A string parsed + * with BigInt is exact at any size. + */ +export const baseUnitString = z + .string() + .regex(/^\d+$/, 'must be a whole number of base units, as a string') + .refine((v) => BigInt(v) > 0n, 'must be greater than zero'); + export const createEscrowSchema = z.object({ onChainId: z.number().int().positive().nullish(), workerPubKey: z.string().min(1), - amountCents: z.number().int().positive(), - rateCents: z.number().int().positive(), + financeApprover: stellarAddress.nullish(), + amountBaseUnits: baseUnitString, + rateBaseUnits: baseUnitString, + assetDecimals: z.number().int().min(0).max(18).default(7), tokenAddress: z.string().min(1).nullish(), }); diff --git a/src/middleware.ts b/src/middleware.ts index ec9462b..1e81b0a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -18,7 +18,14 @@ import { NextRequest, NextResponse } from 'next/server'; import { jwtVerify } from 'jose'; -const PROTECTED_API_PREFIXES = ['/api/escrows', '/api/hours', '/api/admin', '/api/oracle/attest']; +const PROTECTED_API_PREFIXES = [ + '/api/escrows', + '/api/hours', + '/api/admin', + '/api/oracle/attest', + // Issues Ed25519 attestations that unlock on-chain settlement β€” never public. + '/api/submit-batch', +]; /** * Routes under /api/admin that use their own auth mechanism (e.g. BOOTSTRAP_SECRET) @@ -85,5 +92,6 @@ export const config = { '/api/hours/:path*', '/api/admin/:path*', '/api/oracle/attest', + '/api/submit-batch', ], }; diff --git a/vercel.json b/vercel.json index c84eb5b..70e4456 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,15 @@ { "buildCommand": "npm run vercel-build", "installCommand": "npm install", - "outputDirectory": ".next" -} \ No newline at end of file + "outputDirectory": ".next", + "crons": [ + { + "path": "/api/indexer/run", + "schedule": "*/10 * * * *" + }, + { + "path": "/api/reconciliation/run", + "schedule": "17 * * * *" + } + ] +} diff --git a/vitest.config.ts b/vitest.config.ts index 0398819..6f408f8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,7 +9,11 @@ export default defineConfig({ setupFiles: ['./test/setup.ts'], globals: true, // Playwright specs live in e2e/ and must not be picked up by vitest. - exclude: ['node_modules', 'e2e', '.next', 'dist'], + // + // *.integration.test.ts is excluded too: those need a real PostgreSQL database + // and have their own config (vitest.integration.config.ts). Keeping them out + // means the unit total can never be mistaken for database validation. + exclude: ['node_modules', 'e2e', '.next', 'dist', '**/*.integration.test.ts'], coverage: { provider: 'v8', reporter: ['text', 'json-summary', 'html'], diff --git a/vitest.integration.config.ts b/vitest.integration.config.ts new file mode 100644 index 0000000..d6a4bf4 --- /dev/null +++ b/vitest.integration.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +/** + * INTEGRATION tests: these talk to a real PostgreSQL database. + * + * Deliberately a separate config from the unit suite, for two reasons: + * + * 1. The counts must not merge. "761 passing" meaning nothing about the database + * is exactly the false confidence this gate exists to remove, so unit and + * integration totals are reported separately and cannot be conflated. + * + * 2. They share one database. Tests truncate tables between cases, so they must + * run in a single process, one file at a time β€” parallel files would reset each + * other's fixtures and fail in ways that look like product bugs. + * + * Requires a LOCAL database (see docs/ENVIRONMENTS.md). scripts/check-env.mjs + * refuses a non-local DATABASE_URL, and `npm run test:integration` runs it first. + */ +export default defineConfig({ + test: { + environment: 'node', + globals: true, + include: ['src/**/*.integration.test.ts'], + exclude: ['node_modules', 'e2e', '.next', 'dist'], + // One database, one worker, one file at a time. + fileParallelism: false, + pool: 'forks', + // Vitest 4 moved these to the top level. + maxWorkers: 1, + minWorkers: 1, + // Real connections and real DDL are slower than an in-memory double. + testTimeout: 30_000, + hookTimeout: 30_000, + }, + resolve: { + alias: { '@': path.resolve(__dirname, './src') }, + }, +});