diff --git a/ARCHITECTURE_ESSENTIALS.md b/ARCHITECTURE_ESSENTIALS.md index c2b0bfab..8d6a803b 100644 --- a/ARCHITECTURE_ESSENTIALS.md +++ b/ARCHITECTURE_ESSENTIALS.md @@ -20,6 +20,7 @@ release_with_approvals(escrow_id, approvers) -> result // IMPLEMENTED (Phase E refund(escrow_id, caller) -> result // IMPLEMENTED (Phase E2 + E4) — a releaser when threshold is 1; anyone too, once refund_after has passed, and then still only back to payer refund_with_approvals(escrow_id, approvers) -> result // IMPLEMENTED (Phase E5, issue #119) — same approval rules as release_with_approvals top_up(escrow_id, payer, amount) -> result // IMPLEMENTED (issue #26) — original payer only, funded state only; emits `top_up` +create_escrows(payer, items) -> escrow_ids // IMPLEMENTED (issue #28) — atomic batch of 1–5 escrows ``` No new contract-level role came in with the M-of-N work: `releasers` is diff --git a/README.md b/README.md index ae851aee..2b7f1d7a 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ deployment, so choosing an empty list deliberately disables the protection. |---|---|---| | `__constructor(allowed_tokens)` | ✅ Implemented | Stores the immutable deploy-time token allowlist; an empty list allows all tokens. | | `create_escrow(payer, payee, token, amount, condition_ref, releaser, refund_after, notify_payee) -> escrow_id` | ✅ Implemented locally (callback not deployed) | Pulls funds from `payer`, stores a bounded `condition_ref`, and stores the single `releaser` as a one-address set with threshold 1. Requires payer auth; when `notify_payee` is true, also requires payee auth and calls `on_escrow_released` on release. | +| `create_escrows(payer, items) -> escrow_ids` | ✅ Implemented locally | Atomically creates 1–5 escrows for one payer. Each item can use a different payee, token, amount, releaser, and refund deadline; any invalid item reverts the full batch. | | `create_escrow_with_releasers(payer, payee, token, amount, condition_ref, releasers, threshold, refund_after, notify_payee) -> escrow_id` | ✅ Implemented | Same, but settles by M-of-N: 1–10 distinct `releasers` (`MAX_RELEASERS`), `1 <= threshold <= releasers.len()`. Callback opt-in has the same payee-authorization requirement. See [ADR 0007](docs/adr/0007-in-contract-m-of-n-releaser-quorum.md). | | `get_status(escrow_id) -> EscrowStatus` | ✅ Implemented | Read-only. Errors on an unknown id. | | `get_escrow(escrow_id) -> EscrowData` | ✅ Implemented | Read-only: everything stored about the escrow, including `releasers`, `threshold` and `refund_after`, so payees can check the deal before relying on it. | diff --git a/ROADMAP.md b/ROADMAP.md index b040bba5..7963d010 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -173,6 +173,12 @@ deployment predates this constructor; a new testnet deployment and verification are required before claiming the live contract is protected. Closes issue #36. +- [x] Atomic batch creation (issue #28): `create_escrows` accepts 1–5 + escrow parameter records for one payer, checks combined token funding for + overflow, and uses the same validation and exact-delivery checks as + individual creation. A failure reverts all transfers and storage writes. + Regression tests cover mixed tokens, sequential ids, invalid-item + rollback, and batch-size bounds. Not yet redeployed to testnet. - [x] Optional release callback (issue #53): both single-releaser and quorum creation require payee authorization when `notify_payee` is true; release calls `on_escrow_released` after transfer and rolls back atomically diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 640bdf72..810fdfdb 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -20,7 +20,7 @@ use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, symbol_short, token, Address, Env, - IntoVal, String, Vec, + IntoVal, Map, String, Vec, }; /// Largest allowed releaser set for one escrow. Ten covers every @@ -33,6 +33,21 @@ pub const MAX_RELEASERS: u32 = 10; /// Maximum `condition_ref` length in bytes (threat model T10, Phase E5). pub const MAX_CONDITION_REF_LEN: u32 = 64; +/// Conservative upper bound for a single atomic batch; see ADR 0008. +pub const MAX_BATCH_SIZE: u32 = 5; + +/// One escrow to create in an atomic batch. All entries share the payer. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EscrowParams { + pub payee: Address, + pub token: Address, + pub amount: i128, + pub condition_ref: String, + pub releaser: Address, + pub refund_after: Option, +} + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum EscrowStatus { @@ -115,6 +130,13 @@ pub enum Error { DuplicateApprover = 16, /// The same address appeared twice in the releaser set at creation. DuplicateReleaser = 17, + InvalidBatchSize = 30, + InvalidBatchItem0 = 31, + InvalidBatchItem1 = 32, + InvalidBatchItem2 = 33, + InvalidBatchItem3 = 34, + InvalidBatchItem4 = 35, + BatchAmountOverflow = 36, } #[contract] @@ -163,6 +185,7 @@ impl EscrowContract { 1, refund_after, notify_payee, + false, ) } @@ -196,9 +219,66 @@ impl EscrowContract { threshold, refund_after, notify_payee, + false, ) } + /// Atomically creates up to five escrows for one payer. Any failed item + /// reverts the whole invocation, including transfers and storage writes. + pub fn create_escrows( + env: Env, + payer: Address, + items: Vec, + ) -> Result, Error> { + if items.is_empty() || items.len() > MAX_BATCH_SIZE { + return Err(Error::InvalidBatchSize); + } + payer.require_auth(); + + // Check combined funding before any transfer. Per-item validation is + // also performed by create_escrow_inner below, before its transfer. + let now = env.ledger().timestamp(); + let mut totals: Map = Map::new(&env); + for (index, item) in items.iter().enumerate() { + if item.amount <= 0 || item.refund_after.map_or(false, |deadline| deadline <= now) { + return Err(Self::batch_item_error(index as u32)); + } + let total = totals + .get(item.token.clone()) + .unwrap_or(0_i128) + .checked_add(item.amount) + .ok_or(Error::BatchAmountOverflow)?; + totals.set(item.token, total); + } + + let mut ids = Vec::new(&env); + for item in items.iter() { + let mut releasers = Vec::new(&env); + releasers.push_back(item.releaser); + let id = Self::create_escrow_inner( + &env, + payer.clone(), + item.payee, + item.token, + item.amount, + item.condition_ref, + releasers, + 1, + item.refund_after, + false, + true, + ) + .map_err(|error| match error { + Error::InvalidAmount | Error::InvalidRefundAfter => { + Self::batch_item_error(ids.len()) + } + other => other, + })?; + ids.push_back(id); + } + Ok(ids) + } + /// Read-only status query. Errors if `escrow_id` doesn't exist. pub fn get_status(env: Env, escrow_id: u64) -> Result { let data: EscrowData = env @@ -345,6 +425,7 @@ impl EscrowContract { threshold: u32, refund_after: Option, notify_payee: bool, + payer_already_authorized: bool, ) -> Result { let allowed_tokens: Vec
= env .storage() @@ -370,7 +451,9 @@ impl EscrowContract { } Self::check_releaser_set(env, &releasers, threshold)?; - payer.require_auth(); + if !payer_already_authorized { + payer.require_auth(); + } if notify_payee { payee.require_auth(); } @@ -529,6 +612,16 @@ impl EscrowContract { id } + fn batch_item_error(index: u32) -> Error { + match index { + 0 => Error::InvalidBatchItem0, + 1 => Error::InvalidBatchItem1, + 2 => Error::InvalidBatchItem2, + 3 => Error::InvalidBatchItem3, + _ => Error::InvalidBatchItem4, + } + } + fn transfer_in_exact( env: &Env, token: &Address, diff --git a/contracts/escrow/test_snapshots/batch_creation_returns_sequential_ids_and_checks_mixed_tokens.1.json b/contracts/escrow/test_snapshots/batch_creation_returns_sequential_ids_and_checks_mixed_tokens.1.json new file mode 100644 index 00000000..b100e255 --- /dev/null +++ b/contracts/escrow/test_snapshots/batch_creation_returns_sequential_ids_and_checks_mixed_tokens.1.json @@ -0,0 +1,1305 @@ +{ + "generators": { + "address": 12, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "1000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "1000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "create_escrows", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "100" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releaser" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "200" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releaser" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "300" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releaser" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": "100" + } + ] + } + }, + "sub_invocations": [] + }, + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": "200" + } + ] + } + }, + "sub_invocations": [] + }, + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": "300" + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u64": "0" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "100" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "notify_payee" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + }, + { + "key": { + "symbol": "payer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releasers" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Funded" + } + ] + } + }, + { + "key": { + "symbol": "threshold" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "200" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "notify_payee" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + }, + { + "key": { + "symbol": "payer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releasers" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Funded" + } + ] + } + }, + { + "key": { + "symbol": "threshold" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "vec": [ + { + "symbol": "Escrow" + }, + { + "u64": "2" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "300" + } + }, + { + "key": { + "symbol": "condition_ref" + }, + "val": { + "string": "batch-item" + } + }, + { + "key": { + "symbol": "notify_payee" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "payee" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + }, + { + "key": { + "symbol": "payer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "refund_after" + }, + "val": "void" + }, + { + "key": { + "symbol": "releasers" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Funded" + } + ] + } + }, + { + "key": { + "symbol": "threshold" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "AllowedTokens" + } + ] + }, + "val": { + "vec": [] + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextId" + } + ] + }, + "val": { + "u64": "3" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "600" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "400" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "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:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "800" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "200" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/batch_size_is_bounded_and_empty_batches_are_rejected.1.json b/contracts/escrow/test_snapshots/batch_size_is_bounded_and_empty_batches_are_rejected.1.json new file mode 100644 index 00000000..35f40bef --- /dev/null +++ b/contracts/escrow/test_snapshots/batch_size_is_bounded_and_empty_batches_are_rejected.1.json @@ -0,0 +1,239 @@ +{ + "generators": { + "address": 16, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "AllowedTokens" + } + ] + }, + "val": { + "vec": [] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "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:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/invalid_item_reports_index_and_rolls_back_all_funding.1.json b/contracts/escrow/test_snapshots/invalid_item_reports_index_and_rolls_back_all_funding.1.json new file mode 100644 index 00000000..09c744bf --- /dev/null +++ b/contracts/escrow/test_snapshots/invalid_item_reports_index_and_rolls_back_all_funding.1.json @@ -0,0 +1,335 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "1000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "AllowedTokens" + } + ] + }, + "val": { + "vec": [] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "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:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/escrow/tests/batch_create.rs b/contracts/escrow/tests/batch_create.rs new file mode 100644 index 00000000..bd0908cb --- /dev/null +++ b/contracts/escrow/tests/batch_create.rs @@ -0,0 +1,93 @@ +use docforum_escrow::{ + EscrowContract, EscrowContractClient, EscrowParams, EscrowStatus, MAX_BATCH_SIZE, +}; +use soroban_sdk::{ + testutils::Address as _, + token::{StellarAssetClient, TokenClient}, + Address, Env, String, Vec, +}; + +fn item(env: &Env, token: &Address, amount: i128) -> EscrowParams { + EscrowParams { + payee: Address::generate(env), + token: token.clone(), + amount, + condition_ref: String::from_str(env, "batch-item"), + releaser: Address::generate(env), + refund_after: None, + } +} + +#[test] +fn batch_creation_returns_sequential_ids_and_checks_mixed_tokens() { + let env = Env::default(); + env.mock_all_auths(); + let payer = Address::generate(&env); + let token_a = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let token_b = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + StellarAssetClient::new(&env, &token_a).mint(&payer, &1_000); + StellarAssetClient::new(&env, &token_b).mint(&payer, &1_000); + let contract_id = env.register(EscrowContract, (Vec::
::new(&env),)); + let client = EscrowContractClient::new(&env, &contract_id); + let mut items = Vec::new(&env); + items.push_back(item(&env, &token_a, 100)); + items.push_back(item(&env, &token_b, 200)); + items.push_back(item(&env, &token_a, 300)); + + let ids = client.create_escrows(&payer, &items); + assert_eq!(ids.len(), 3); + assert_eq!(ids.get(0), Some(0)); + assert_eq!(ids.get(1), Some(1)); + assert_eq!(ids.get(2), Some(2)); + assert_eq!(client.get_escrow(&ids.get(1).unwrap()).amount, 200); + assert_eq!( + client.get_status(&ids.get(2).unwrap()), + EscrowStatus::Funded + ); + assert_eq!(TokenClient::new(&env, &token_a).balance(&contract_id), 400); + assert_eq!(TokenClient::new(&env, &token_b).balance(&contract_id), 200); +} + +#[test] +fn invalid_item_reports_index_and_rolls_back_all_funding() { + let env = Env::default(); + env.mock_all_auths(); + let payer = Address::generate(&env); + let token_id = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let token = TokenClient::new(&env, &token_id); + StellarAssetClient::new(&env, &token_id).mint(&payer, &1_000); + let contract_id = env.register(EscrowContract, (Vec::
::new(&env),)); + let client = EscrowContractClient::new(&env, &contract_id); + let mut items = Vec::new(&env); + items.push_back(item(&env, &token_id, 100)); + items.push_back(item(&env, &token_id, 0)); + let result = client.try_create_escrows(&payer, &items); + assert!(result.is_err()); + assert_eq!(token.balance(&payer), 1_000); + assert_eq!(token.balance(&contract_id), 0); + assert!(client.try_get_escrow(&0).is_err()); +} + +#[test] +fn batch_size_is_bounded_and_empty_batches_are_rejected() { + let env = Env::default(); + env.mock_all_auths(); + let payer = Address::generate(&env); + let token_id = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let contract_id = env.register(EscrowContract, (Vec::
::new(&env),)); + let client = EscrowContractClient::new(&env, &contract_id); + assert!(client.try_create_escrows(&payer, &Vec::new(&env)).is_err()); + let mut too_many = Vec::new(&env); + for _ in 0..=MAX_BATCH_SIZE { + too_many.push_back(item(&env, &token_id, 1)); + } + assert!(client.try_create_escrows(&payer, &too_many).is_err()); +} diff --git a/sdk/src/generated/contract-client.ts b/sdk/src/generated/contract-client.ts index 4ff12c32..6bc6ae10 100644 --- a/sdk/src/generated/contract-client.ts +++ b/sdk/src/generated/contract-client.ts @@ -90,7 +90,14 @@ export const Errors = { /** * The same address appeared twice in the releaser set at creation. */ - 17: {message:"DuplicateReleaser"} + 17: {message:"DuplicateReleaser"}, + 30: {message:"InvalidBatchSize"}, + 31: {message:"InvalidBatchItem0"}, + 32: {message:"InvalidBatchItem1"}, + 33: {message:"InvalidBatchItem2"}, + 34: {message:"InvalidBatchItem3"}, + 35: {message:"InvalidBatchItem4"}, + 36: {message:"BatchAmountOverflow"} } @@ -126,6 +133,19 @@ threshold: u32; token: string; } + +/** + * One escrow to create in an atomic batch. All entries share the payer. + */ +export interface EscrowParams { + amount: i128; + condition_ref: string; + payee: string; + refund_after: Option; + releaser: string; + token: string; +} + export type EscrowStatus = {tag: "Funded", values: void} | {tag: "Released", values: void} | {tag: "Refunded", values: void}; export interface Client { @@ -196,6 +216,13 @@ export interface Client { */ create_escrow: ({payer, payee, token, amount, condition_ref, releaser, refund_after, notify_payee}: {payer: string, payee: string, token: string, amount: i128, condition_ref: string, releaser: string, refund_after: Option, notify_payee: boolean}, options?: MethodOptions) => Promise>> + /** + * Construct and simulate a create_escrows transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Atomically creates up to five escrows for one payer. Any failed item + * reverts the whole invocation, including transfers and storage writes. + */ + create_escrows: ({payer, items}: {payer: string, items: Array}, options?: MethodOptions) => Promise>>> + /** * Construct and simulate a refund_with_approvals transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Returns the escrowed `amount` to `payer` on behalf of an M-of-N @@ -249,8 +276,9 @@ export class Client extends ContractClient { } constructor(public readonly options: ContractClientOptions) { super( - new ContractSpec([ "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAADwAAAAAAAAAITm90Rm91bmQAAAABAAAAAAAAAA1JbnZhbGlkQW1vdW50AAAAAAAAAgAAAAAAAAAMVW5hdXRob3JpemVkAAAAAwAAAAAAAAAJTm90RnVuZGVkAAAAAAAABAAAALpgcmVmdW5kX2FmdGVyYCB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24g4oCUIHNlZQpkb2NzL2Fkci8wMDA0OiBhIGRlYWRsaW5lIHRoYXQgaGFzIGFscmVhZHkgcGFzc2VkIHdvdWxkIGxldAphbnlvbmUgcmVmdW5kIHRoZSBlc2Nyb3cgYmVmb3JlIHRoZSByZWxlYXNlciBjb3VsZCBldmVyIGFjdC4AAAAAABJJbnZhbGlkUmVmdW5kQWZ0ZXIAAAAAAAUAAACjVGhlIGNvbnRyYWN0J3MgdG9rZW4gYmFsYW5jZSBkaWRuJ3QgcmlzZSBieSBleGFjdGx5IGBhbW91bnRgCmFjcm9zcyBgY3JlYXRlX2VzY3Jvd2AncyB0cmFuc2ZlciAoYSBseWluZyBvciBzaG9ydC1kZWxpdmVyaW5nCnRva2VuKSDigJQgc2VlIGRvY3MvdGhyZWF0LW1vZGVsLm1kIFQ1LgAAAAAPQmFsYW5jZU1pc21hdGNoAAAAAAYAAAEAYGNvbmRpdGlvbl9yZWZgIGV4Y2VlZGVkIHRoZSBtYXhpbXVtIGFsbG93ZWQgbGVuZ3RoIChjdXJyZW50bHkKNjQgYnl0ZXMpIOKAlCBzZWUgZG9jcy90aHJlYXQtbW9kZWwubWQgVDEwIGFuZCBST0FETUFQLm1kIFBoYXNlIEU1LgpCb3VuZGVkIHRvIGtlZXAgc3RvcmFnZSBhbmQgZXZlbnQgY29zdHMgcHJlZGljdGFibGU7IHJlamVjdCBhdApjcmVhdGlvbiBzbyBubyBvbi1jaGFpbiBzdG9yYWdlIGlzIHdhc3RlZCBvbiBhbiBvdmVyc2l6ZWQgcmVmLgAAABNDb25kaXRpb25SZWZUb29Mb25nAAAAAAcAAAA2YHRva2VuYCBpcyBub3QgaW4gdGhlIGltbXV0YWJsZSBkZXBsb3ktdGltZSBhbGxvd2xpc3QuAAAAAAAPRGlzYWxsb3dlZFRva2VuAAAAAAgAAACDVGhlIG9wdGVkLWluIHBheWVlIGNhbGxiYWNrIHJldHVybmVkIGFuIGVycm9yIG9yIGNvdWxkIG5vdCBiZQppbnZva2VkLiBUaGUgcmVsZWFzZSB0cmFuc2FjdGlvbiAoaW5jbHVkaW5nIGl0cyB0cmFuc2Zlcikgcm9sbHMgYmFjay4AAAAADkNhbGxiYWNrRmFpbGVkAAAAAAAJAAAAAAAAAAhPdmVyZmxvdwAAAAwAAABsVGhlIHJlbGVhc2VyIHNldCBpcyBlbXB0eSwgb3IgYHRocmVzaG9sZGAgaXMgMCBvciBsYXJnZXIgdGhhbiB0aGUKbnVtYmVyIG9mIHJlbGVhc2VycyDigJQgc2VlIGRvY3MvYWRyLzAwMDcuAAAAEEludmFsaWRUaHJlc2hvbGQAAAANAAAAQ01vcmUgdGhhbiBgTUFYX1JFTEVBU0VSU2AgKDEwKSBhZGRyZXNzZXMgd2VyZSBzdXBwbGllZCBhdCBjcmVhdGlvbi4AAAAAEFRvb01hbnlSZWxlYXNlcnMAAAAOAAAAn0Zld2VyIGRpc3RpbmN0IGFwcHJvdmFscyB3ZXJlIHN1cHBsaWVkIHRoYW4gdGhlIGVzY3JvdydzIHRocmVzaG9sZCwKb3IgYSBzaW5nbGUgcmVsZWFzZXIgdHJpZWQgdG8gc2V0dGxlIGEgcXVvcnVtIGVzY3JvdyB0aHJvdWdoIHRoZQpzaW5nbGUtY2FsbGVyIGVudHJ5IHBvaW50LgAAAAAPVGhyZXNob2xkTm90TWV0AAAAAA8AAABoVGhlIHNhbWUgYWRkcmVzcyBhcHBlYXJlZCB0d2ljZSBpbiBvbmUgYXBwcm92YWwgbGlzdC4gRHVwbGljYXRlcwpuZXZlciBjb3VudCB0d2ljZSB0b3dhcmQgdGhlIHRocmVzaG9sZC4AAAARRHVwbGljYXRlQXBwcm92ZXIAAAAAAAAQAAAAQFRoZSBzYW1lIGFkZHJlc3MgYXBwZWFyZWQgdHdpY2UgaW4gdGhlIHJlbGVhc2VyIHNldCBhdCBjcmVhdGlvbi4AAAARRHVwbGljYXRlUmVsZWFzZXIAAAAAAAAR", + new ContractSpec([ "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAFgAAAAAAAAAITm90Rm91bmQAAAABAAAAAAAAAA1JbnZhbGlkQW1vdW50AAAAAAAAAgAAAAAAAAAMVW5hdXRob3JpemVkAAAAAwAAAAAAAAAJTm90RnVuZGVkAAAAAAAABAAAALpgcmVmdW5kX2FmdGVyYCB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24g4oCUIHNlZQpkb2NzL2Fkci8wMDA0OiBhIGRlYWRsaW5lIHRoYXQgaGFzIGFscmVhZHkgcGFzc2VkIHdvdWxkIGxldAphbnlvbmUgcmVmdW5kIHRoZSBlc2Nyb3cgYmVmb3JlIHRoZSByZWxlYXNlciBjb3VsZCBldmVyIGFjdC4AAAAAABJJbnZhbGlkUmVmdW5kQWZ0ZXIAAAAAAAUAAACjVGhlIGNvbnRyYWN0J3MgdG9rZW4gYmFsYW5jZSBkaWRuJ3QgcmlzZSBieSBleGFjdGx5IGBhbW91bnRgCmFjcm9zcyBgY3JlYXRlX2VzY3Jvd2AncyB0cmFuc2ZlciAoYSBseWluZyBvciBzaG9ydC1kZWxpdmVyaW5nCnRva2VuKSDigJQgc2VlIGRvY3MvdGhyZWF0LW1vZGVsLm1kIFQ1LgAAAAAPQmFsYW5jZU1pc21hdGNoAAAAAAYAAAEAYGNvbmRpdGlvbl9yZWZgIGV4Y2VlZGVkIHRoZSBtYXhpbXVtIGFsbG93ZWQgbGVuZ3RoIChjdXJyZW50bHkKNjQgYnl0ZXMpIOKAlCBzZWUgZG9jcy90aHJlYXQtbW9kZWwubWQgVDEwIGFuZCBST0FETUFQLm1kIFBoYXNlIEU1LgpCb3VuZGVkIHRvIGtlZXAgc3RvcmFnZSBhbmQgZXZlbnQgY29zdHMgcHJlZGljdGFibGU7IHJlamVjdCBhdApjcmVhdGlvbiBzbyBubyBvbi1jaGFpbiBzdG9yYWdlIGlzIHdhc3RlZCBvbiBhbiBvdmVyc2l6ZWQgcmVmLgAAABNDb25kaXRpb25SZWZUb29Mb25nAAAAAAcAAAA2YHRva2VuYCBpcyBub3QgaW4gdGhlIGltbXV0YWJsZSBkZXBsb3ktdGltZSBhbGxvd2xpc3QuAAAAAAAPRGlzYWxsb3dlZFRva2VuAAAAAAgAAACDVGhlIG9wdGVkLWluIHBheWVlIGNhbGxiYWNrIHJldHVybmVkIGFuIGVycm9yIG9yIGNvdWxkIG5vdCBiZQppbnZva2VkLiBUaGUgcmVsZWFzZSB0cmFuc2FjdGlvbiAoaW5jbHVkaW5nIGl0cyB0cmFuc2Zlcikgcm9sbHMgYmFjay4AAAAADkNhbGxiYWNrRmFpbGVkAAAAAAAJAAAAAAAAAAhPdmVyZmxvdwAAAAwAAABsVGhlIHJlbGVhc2VyIHNldCBpcyBlbXB0eSwgb3IgYHRocmVzaG9sZGAgaXMgMCBvciBsYXJnZXIgdGhhbiB0aGUKbnVtYmVyIG9mIHJlbGVhc2VycyDigJQgc2VlIGRvY3MvYWRyLzAwMDcuAAAAEEludmFsaWRUaHJlc2hvbGQAAAANAAAAQ01vcmUgdGhhbiBgTUFYX1JFTEVBU0VSU2AgKDEwKSBhZGRyZXNzZXMgd2VyZSBzdXBwbGllZCBhdCBjcmVhdGlvbi4AAAAAEFRvb01hbnlSZWxlYXNlcnMAAAAOAAAAn0Zld2VyIGRpc3RpbmN0IGFwcHJvdmFscyB3ZXJlIHN1cHBsaWVkIHRoYW4gdGhlIGVzY3JvdydzIHRocmVzaG9sZCwKb3IgYSBzaW5nbGUgcmVsZWFzZXIgdHJpZWQgdG8gc2V0dGxlIGEgcXVvcnVtIGVzY3JvdyB0aHJvdWdoIHRoZQpzaW5nbGUtY2FsbGVyIGVudHJ5IHBvaW50LgAAAAAPVGhyZXNob2xkTm90TWV0AAAAAA8AAABoVGhlIHNhbWUgYWRkcmVzcyBhcHBlYXJlZCB0d2ljZSBpbiBvbmUgYXBwcm92YWwgbGlzdC4gRHVwbGljYXRlcwpuZXZlciBjb3VudCB0d2ljZSB0b3dhcmQgdGhlIHRocmVzaG9sZC4AAAARRHVwbGljYXRlQXBwcm92ZXIAAAAAAAAQAAAAQFRoZSBzYW1lIGFkZHJlc3MgYXBwZWFyZWQgdHdpY2UgaW4gdGhlIHJlbGVhc2VyIHNldCBhdCBjcmVhdGlvbi4AAAARRHVwbGljYXRlUmVsZWFzZXIAAAAAAAARAAAAAAAAABBJbnZhbGlkQmF0Y2hTaXplAAAAHgAAAAAAAAARSW52YWxpZEJhdGNoSXRlbTAAAAAAAAAfAAAAAAAAABFJbnZhbGlkQmF0Y2hJdGVtMQAAAAAAACAAAAAAAAAAEUludmFsaWRCYXRjaEl0ZW0yAAAAAAAAIQAAAAAAAAARSW52YWxpZEJhdGNoSXRlbTMAAAAAAAAiAAAAAAAAABFJbnZhbGlkQmF0Y2hJdGVtNAAAAAAAACMAAAAAAAAAE0JhdGNoQW1vdW50T3ZlcmZsb3cAAAAAJA==", "AAAAAQAAAAAAAAAAAAAACkVzY3Jvd0RhdGEAAAAAAAoAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAANY29uZGl0aW9uX3JlZgAAAAAAABAAAACGSWYgdHJ1ZSwgaW52b2tlIHRoZSBjb250cmFjdCBwYXllZSdzIGBvbl9lc2Nyb3dfcmVsZWFzZWRgIGhvb2sgYWZ0ZXIKdGhlIHBheW91dCB0cmFuc2Zlci4gQSBmYWlsZWQgY2FsbGJhY2sgcmV2ZXJ0cyB0aGUgd2hvbGUgcmVsZWFzZS4AAAAAAAxub3RpZnlfcGF5ZWUAAAABAAAAAAAAAAVwYXllZQAAAAAAABMAAAAAAAAABXBheWVyAAAAAAAAEwAAAMZPcHRpb25hbCBkZWFkbGluZSAodW5peCBzZWNvbmRzKSBhZnRlciB3aGljaCBgcmVmdW5kYCBvcGVucyB0bwphbnlvbmUg4oCUIHN0aWxsIG9ubHkgYmFjayB0byBgcGF5ZXJgLiBgTm9uZWAgbWVhbnMgbm8gdGltZW91dCwKYmVoYXZpb3VyIGlkZW50aWNhbCB0byBiZWZvcmUgdGhpcyBmaWVsZCBleGlzdGVkIOKAlCBzZWUgZG9jcy9hZHIvMDAwNC4AAAAAAAxyZWZ1bmRfYWZ0ZXIAAAPoAAAABgAAAL1BZGRyZXNzZXMgcGVybWl0dGVkIHRvIHNldHRsZSB0aGlzIGVzY3JvdyDigJQgc2VlIGRvY3MvYWRyLzAwMDIgZm9yIHRoZQpwZXItZXNjcm93IHJlbGVhc2VyLCBhbmQgZG9jcy9hZHIvMDAwNyBmb3IgTS1vZi1OIHNldHMuIEEgcGxhaW4Kc2luZ2xlLXJlbGVhc2VyIGVzY3JvdyBzdG9yZXMgZXhhY3RseSBvbmUgZW50cnkgaGVyZS4AAAAAAAAJcmVsZWFzZXJzAAAAAAAD6gAAABMAAAAAAAAABnN0YXR1cwAAAAAH0AAAAAxFc2Nyb3dTdGF0dXMAAAC6SG93IG1hbnkgKmRpc3RpbmN0KiBhZGRyZXNzZXMgZnJvbSBgcmVsZWFzZXJzYCBtdXN0IGF1dGhvcml6ZSBvbmUKc2V0dGxlbWVudC4gQWx3YXlzIGA+PSAxYCBhbmQgYDw9IHJlbGVhc2Vycy5sZW4oKWA7IGAxYCBtZWFucyBhbnkKc2luZ2xlIHJlbGVhc2VyIGNhbiBzZXR0bGUgKHRoZSBwcmUtcXVvcnVtIGJlaGF2aW91cikuAAAAAAAJdGhyZXNob2xkAAAAAAAABAAAAAAAAAAFdG9rZW4AAAAAAAAT", + "AAAAAQAAAEVPbmUgZXNjcm93IHRvIGNyZWF0ZSBpbiBhbiBhdG9taWMgYmF0Y2guIEFsbCBlbnRyaWVzIHNoYXJlIHRoZSBwYXllci4AAAAAAAAAAAAADEVzY3Jvd1BhcmFtcwAAAAYAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAANY29uZGl0aW9uX3JlZgAAAAAAABAAAAAAAAAABXBheWVlAAAAAAAAEwAAAAAAAAAMcmVmdW5kX2FmdGVyAAAD6AAAAAYAAAAAAAAACHJlbGVhc2VyAAAAEwAAAAAAAAAFdG9rZW4AAAAAAAAT", "AAAAAgAAAAAAAAAAAAAADEVzY3Jvd1N0YXR1cwAAAAMAAAAAAAAAAAAAAAZGdW5kZWQAAAAAAAAAAAAAAAAACFJlbGVhc2VkAAAAAAAAAAAAAAAIUmVmdW5kZWQ=", "AAAAAAAAAoVSZXR1cm5zIHRoZSBlc2Nyb3dlZCBgYW1vdW50YCB0byBgcGF5ZXJgLiBTYW1lIHN0YXRlIHJ1bGUgYXMKYHJlbGVhc2VgIChvbmx5IHdoaWxlIGBGdW5kZWRgKSwgYnV0IHR3byBjYWxsZXJzIGFyZSBhdXRob3Jpc2VkOgoKLSB0aGUgZXNjcm93J3MgcmVsZWFzZXIgKGRvY3MvYWRyLzAwMDIpLCBhbHdheXM7IG9yCi0gKiphbnlvbmUqKiwgb25jZSBgcmVmdW5kX2FmdGVyYCBoYXMgYmVlbiByZWFjaGVkIOKAlCB0aGUgdGltZW91dApyZWZ1bmQgcGF0aCBmb3IgYSBsb3N0L3Vuc2lnbmFibGUgcmVsZWFzZXIga2V5IChkb2NzL2Fkci8wMDA0KS4KClRoZSBkZXN0aW5hdGlvbiBuZXZlciBjaGFuZ2VzOiB0aW1lb3V0IG9yIG5vdCwgZnVuZHMgb25seSBldmVyIGdvCmJhY2sgdG8gdGhlIHN0b3JlZCBgcGF5ZXJgLiBXaXRoIG5vIGByZWZ1bmRfYWZ0ZXJgIHNldCwgYmVoYXZpb3VyCmlzIGlkZW50aWNhbCB0byBiZWZvcmUgdGhlIHRpbWVvdXQgZXhpc3RlZCDigJQgcmVsZWFzZXIgb25seS4gT24gYW4KTS1vZi1OIGVzY3JvdyB0aGUgcmVsZWFzZXIgcGF0aCBuZWVkcyBhIHRocmVzaG9sZCBvZiAxOyBvdGhlcndpc2UgdXNlCmByZWZ1bmRfd2l0aF9hcHByb3ZhbHNgICh0aGUgdGltZW91dCBwYXRoIHN0YXlzIG9wZW4gdG8gYW55b25lKS4AAAAAAAAGcmVmdW5kAAAAAAACAAAAAAAAAAllc2Nyb3dfaWQAAAAAAAAGAAAAAAAAAAZjYWxsZXIAAAAAABMAAAABAAAD6QAAAAIAAAAD", "AAAAAAAAAHVBZGRzIGZ1bmRzIHRvIGEgZnVuZGVkIGVzY3JvdyB3aXRob3V0IGNoYW5naW5nIGl0cyBwYXJ0aWVzIG9yIGlkLgpPbmx5IGl0cyBvcmlnaW5hbCBwYXllciBtYXkgYXV0aG9yaXplIHRoZSB0cmFuc2Zlci4AAAAAAAAGdG9wX3VwAAAAAAADAAAAAAAAAAllc2Nyb3dfaWQAAAAAAAAGAAAAAAAAAAVwYXllcgAAAAAAABMAAAAAAAAABmFtb3VudAAAAAAACwAAAAEAAAPpAAAAAgAAAAM=", @@ -259,6 +287,7 @@ export class Client extends ContractClient { "AAAAAAAAADxSZWFkLW9ubHkgc3RhdHVzIHF1ZXJ5LiBFcnJvcnMgaWYgYGVzY3Jvd19pZGAgZG9lc24ndCBleGlzdC4AAAAKZ2V0X3N0YXR1cwAAAAAAAQAAAAAAAAAJZXNjcm93X2lkAAAAAAAABgAAAAEAAAPpAAAH0AAAAAxFc2Nyb3dTdGF0dXMAAAAD", "AAAAAAAAAHtTZXRzIHRoZSBpbW11dGFibGUgdG9rZW4gYWxsb3dsaXN0IGF0IGRlcGxveW1lbnQuIEFuIGVtcHR5IGxpc3QKcHJlc2VydmVzIHRoZSBsZWdhY3kgYmVoYXZpb3IgYW5kIGFsbG93cyBhbnkgdG9rZW4gYWRkcmVzcy4AAAAADV9fY29uc3RydWN0b3IAAAAAAAABAAAAAAAAAA5hbGxvd2VkX3Rva2VucwAAAAAD6gAAABMAAAAA", "AAAAAAAAAghMb2NrcyBgYW1vdW50YCBvZiBgdG9rZW5gIHB1bGxlZCBmcm9tIGBwYXllcmAsIGhlbGQgZm9yIGBwYXllZWAsCmRvY3MvdGhyZWF0LW1vZGVsLm1kIFQxMCBhbmQgUk9BRE1BUC5tZCBQaGFzZSBFNSkuIGByZWxlYXNlcmAgaXMgdGhlCmFkZHJlc3MgcGVybWl0dGVkIHRvIGNhbGwgYHJlbGVhc2VgL2ByZWZ1bmRgIG9uIHRoaXMgZXNjcm93Cihkb2NzL2Fkci8wMDAyKS4gYHJlZnVuZF9hZnRlcmAgaXMgb3B0aW9uYWwgKGRvY3MvYWRyLzAwMDQpLgpgbm90aWZ5X3BheWVlYCBvcHRzIGFuIGltcGxlbWVudGluZyBjb250cmFjdCBwYXllZSBpbnRvCmBvbl9lc2Nyb3dfcmVsZWFzZWQoZXNjcm93X2lkLCBhbW91bnQpYCAoZG9jcy9hZHIvMDAwNSkuIFRoZSBwYXllcgptdXN0IGF1dGhvcml6ZSBjcmVhdGlvbiwgYW5kIHRoZSBwYXllZSBtdXN0IGFsc28gYXV0aG9yaXplIGlmCmBub3RpZnlfcGF5ZWVgIGlzIHRydWUuIEZvciBNLW9mLU4gcmVsZWFzZXJzIHVzZQpgY3JlYXRlX2VzY3Jvd193aXRoX3JlbGVhc2Vyc2AuAAAADWNyZWF0ZV9lc2Nyb3cAAAAAAAAIAAAAAAAAAAVwYXllcgAAAAAAABMAAAAAAAAABXBheWVlAAAAAAAAEwAAAAAAAAAFdG9rZW4AAAAAAAATAAAAAAAAAAZhbW91bnQAAAAAAAsAAAAAAAAADWNvbmRpdGlvbl9yZWYAAAAAAAAQAAAAAAAAAAhyZWxlYXNlcgAAABMAAAAAAAAADHJlZnVuZF9hZnRlcgAAA+gAAAAGAAAAAAAAAAxub3RpZnlfcGF5ZWUAAAABAAAAAQAAA+kAAAAGAAAAAw==", + "AAAAAAAAAIpBdG9taWNhbGx5IGNyZWF0ZXMgdXAgdG8gZml2ZSBlc2Nyb3dzIGZvciBvbmUgcGF5ZXIuIEFueSBmYWlsZWQgaXRlbQpyZXZlcnRzIHRoZSB3aG9sZSBpbnZvY2F0aW9uLCBpbmNsdWRpbmcgdHJhbnNmZXJzIGFuZCBzdG9yYWdlIHdyaXRlcy4AAAAAAA5jcmVhdGVfZXNjcm93cwAAAAAAAgAAAAAAAAAFcGF5ZXIAAAAAAAATAAAAAAAAAAVpdGVtcwAAAAAAA+oAAAfQAAAADEVzY3Jvd1BhcmFtcwAAAAEAAAPpAAAD6gAAAAYAAAAD", "AAAAAAAAASNSZXR1cm5zIHRoZSBlc2Nyb3dlZCBgYW1vdW50YCB0byBgcGF5ZXJgIG9uIGJlaGFsZiBvZiBhbiBNLW9mLU4KcmVsZWFzZXIgc2V0IChkb2NzL2Fkci8wMDA3KS4gU2FtZSBhcHByb3ZhbCBydWxlcyBhcwpgcmVsZWFzZV93aXRoX2FwcHJvdmFsc2AuIFRoZSB0aW1lb3V0IHJlZnVuZCBwYXRoIGluIGByZWZ1bmRgIHN0YXlzCmF2YWlsYWJsZSBhcyB3ZWxsOiBvbmNlIGByZWZ1bmRfYWZ0ZXJgIHBhc3NlcywgYW55b25lIGNhbiByZXR1cm4gdGhlCmZ1bmRzIHRvIHRoZSBwYXllciB3aXRob3V0IGFwcHJvdmFscy4AAAAAFXJlZnVuZF93aXRoX2FwcHJvdmFscwAAAAAAAAIAAAAAAAAACWVzY3Jvd19pZAAAAAAAAAYAAAAAAAAACWFwcHJvdmVycwAAAAAAA+oAAAATAAAAAQAAA+kAAAACAAAAAw==", "AAAAAAAAAZFNb3ZlcyB0aGUgZXNjcm93ZWQgYGFtb3VudGAgdG8gYHBheWVlYCBvbiBiZWhhbGYgb2YgYW4gTS1vZi1OCnJlbGVhc2VyIHNldCAoZG9jcy9hZHIvMDAwNykuIEV2ZXJ5IGFkZHJlc3MgaW4gYGFwcHJvdmVyc2AgbXVzdCBiZSBhCnJlbGVhc2VyLCBhcHBlYXIgYXQgbW9zdCBvbmNlLCBhbmQgYXV0aG9yaXplIHRoaXMgZXhhY3QgaW52b2NhdGlvbjsKYXQgbGVhc3QgYHRocmVzaG9sZGAgZGlzdGluY3QgYXBwcm92ZXJzIGFyZSByZXF1aXJlZC4gQXBwcm92YWxzIGFyZQpub3Qgc3RvcmVkIGJldHdlZW4gY2FsbHMsIHNvIHRoZXJlIGlzIG5vdGhpbmcgdG8gY2xlYXIgb24gc2V0dGxlbWVudAphbmQgbm8gYXBwcm92YWwgdG8gcmVwbGF5IOKAlCBhIHNldHRsZWQgZXNjcm93IGZhaWxzIGBOb3RGdW5kZWRgLgAAAAAAABZyZWxlYXNlX3dpdGhfYXBwcm92YWxzAAAAAAACAAAAAAAAAAllc2Nyb3dfaWQAAAAAAAAGAAAAAAAAAAlhcHByb3ZlcnMAAAAAAAPqAAAAEwAAAAEAAAPpAAAAAgAAAAM=", "AAAAAAAAAZNTYW1lIGFzIGBjcmVhdGVfZXNjcm93YCwgYnV0IHRoZSBlc2Nyb3cgaXMgc2V0dGxlZCBieSBNLW9mLU4KcmVsZWFzZXJzIGluc3RlYWQgb2Ygb25lIGFkZHJlc3MgKGRvY3MvYWRyLzAwMDcpOiBgdGhyZXNob2xkYApkaXN0aW5jdCBhZGRyZXNzZXMgZnJvbSBgcmVsZWFzZXJzYCBtdXN0IGF1dGhvcml6ZSBhIHNldHRsZW1lbnQuClRoZSBzZXQgaXMgc3RvcmVkIGF0IGNyZWF0aW9uIGFuZCBjYW5ub3QgYmUgY2hhbmdlZCBhZnRlcndhcmRzIOKAlAp0aGVyZSBpcyBubyBhZG1pbiBhbmQgbm8gc2V0dGVyLiBSZWplY3RzIGFuIGVtcHR5IHNldCwgbW9yZSB0aGFuCjEwIHJlbGVhc2VycywgZHVwbGljYXRlIGFkZHJlc3NlcywgYW5kIGEgdGhyZXNob2xkIG9mIDAgb3Igb25lCmxhcmdlciB0aGFuIHRoZSBzZXQuAAAAABxjcmVhdGVfZXNjcm93X3dpdGhfcmVsZWFzZXJzAAAACQAAAAAAAAAFcGF5ZXIAAAAAAAATAAAAAAAAAAVwYXllZQAAAAAAABMAAAAAAAAABXRva2VuAAAAAAAAEwAAAAAAAAAGYW1vdW50AAAAAAALAAAAAAAAAA1jb25kaXRpb25fcmVmAAAAAAAAEAAAAAAAAAAJcmVsZWFzZXJzAAAAAAAD6gAAABMAAAAAAAAACXRocmVzaG9sZAAAAAAAAAQAAAAAAAAADHJlZnVuZF9hZnRlcgAAA+gAAAAGAAAAAAAAAAxub3RpZnlfcGF5ZWUAAAABAAAAAQAAA+kAAAAGAAAAAw==" ]), @@ -272,6 +301,7 @@ export class Client extends ContractClient { get_escrow: this.txFromJSON>, get_status: this.txFromJSON>, create_escrow: this.txFromJSON>, + create_escrows: this.txFromJSON>>, refund_with_approvals: this.txFromJSON>, release_with_approvals: this.txFromJSON>, create_escrow_with_releasers: this.txFromJSON>