From a7ff30b79c59891398455f99191435676228a827 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 10 Aug 2023 14:24:13 -0400 Subject: [PATCH 01/12] space to log --- components/chainhook-cli/src/scan/bitcoin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/chainhook-cli/src/scan/bitcoin.rs b/components/chainhook-cli/src/scan/bitcoin.rs index b6cd43e1e..4f1af97ab 100644 --- a/components/chainhook-cli/src/scan/bitcoin.rs +++ b/components/chainhook-cli/src/scan/bitcoin.rs @@ -132,7 +132,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( Err((e, _)) => { warn!( ctx.expect_logger(), - "Unable to standardize block#{} {}: {}", current_block_height, block_hash, e + "Unable to standardize block# {} {}: {}", current_block_height, block_hash, e ); continue; } From 018096fbc55a67cf1cf8598b59e0c261ea9106ae Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 10 Aug 2023 14:31:09 -0400 Subject: [PATCH 02/12] add contract abi to stacks transaction for scan output --- components/chainhook-sdk/src/indexer/stacks/mod.rs | 4 ++++ .../chainhook-sdk/src/indexer/tests/helpers/transactions.rs | 1 + components/chainhook-types-rs/src/rosetta.rs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/components/chainhook-sdk/src/indexer/stacks/mod.rs b/components/chainhook-sdk/src/indexer/stacks/mod.rs index 01f3318ae..4225c1126 100644 --- a/components/chainhook-sdk/src/indexer/stacks/mod.rs +++ b/components/chainhook-sdk/src/indexer/stacks/mod.rs @@ -76,6 +76,7 @@ pub struct NewTransaction { pub raw_result: String, pub raw_tx: String, pub execution_cost: Option, + pub contract_abi: Option, } #[derive(Deserialize, Debug)] @@ -89,6 +90,7 @@ pub struct NewMicroblockTransaction { pub microblock_sequence: usize, pub microblock_hash: String, pub microblock_parent_hash: String, + pub contract_abi: Option, } #[derive(Debug, Deserialize, Serialize)] @@ -311,6 +313,7 @@ pub fn standardize_stacks_block( description, position: StacksTransactionPosition::anchor_block(tx.tx_index), proof: None, + contract_abi: tx.contract_abi.clone(), }, }); } @@ -452,6 +455,7 @@ pub fn standardize_stacks_microblock_trail( tx.tx_index, ), proof: None, + contract_abi: tx.contract_abi.clone(), }, }; diff --git a/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs b/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs index 24b7fe9e8..505c9b9ad 100644 --- a/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs +++ b/components/chainhook-sdk/src/indexer/tests/helpers/transactions.rs @@ -64,6 +64,7 @@ pub fn generate_test_tx_stacks_contract_call( sponsor: None, position: chainhook_types::StacksTransactionPosition::anchor_block(0), proof: None, + contract_abi: None, }, } } diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index 39ba9c005..946cb125d 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -1,6 +1,7 @@ use super::bitcoin::{TxIn, TxOut}; use crate::events::*; use schemars::JsonSchema; +use serde_json::Value; use std::cmp::Ordering; use std::collections::HashSet; use std::fmt::Display; @@ -219,6 +220,7 @@ pub struct StacksTransactionMetadata { pub execution_cost: Option, pub position: StacksTransactionPosition, pub proof: Option, + pub contract_abi: Option, } /// TODO From f1bd0bac88aa3a45e75697bb801e4ef73a17b4c0 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 15 Aug 2023 13:55:28 -0400 Subject: [PATCH 03/12] fix comment --- components/chainhook-cli/src/scan/bitcoin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/chainhook-cli/src/scan/bitcoin.rs b/components/chainhook-cli/src/scan/bitcoin.rs index 4f1af97ab..01c7cf4e1 100644 --- a/components/chainhook-cli/src/scan/bitcoin.rs +++ b/components/chainhook-cli/src/scan/bitcoin.rs @@ -132,7 +132,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( Err((e, _)) => { warn!( ctx.expect_logger(), - "Unable to standardize block# {} {}: {}", current_block_height, block_hash, e + "Unable to standardize block #{} {}: {}", current_block_height, block_hash, e ); continue; } From 3c92d104adb80d856c5a49d1ba51ac38b075cda6 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 16 Aug 2023 12:57:24 -0400 Subject: [PATCH 04/12] add field to optionally include contract_abi --- README.md | 3 +++ components/chainhook-cli/src/cli/mod.rs | 2 ++ .../src/chainhooks/stacks/mod.rs | 26 ++++++++++++++----- .../chainhook-sdk/src/chainhooks/tests/mod.rs | 5 ++++ .../chainhook-sdk/src/chainhooks/types.rs | 4 +++ .../chainhook-sdk/src/observer/tests/mod.rs | 1 + .../how-to-use-chainhooks-with-stacks.md | 5 +++- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bd0da3cb7..d6c7169b5 100644 --- a/README.md +++ b/README.md @@ -536,6 +536,9 @@ Additional configuration knobs available: // Include decoded clarity values in payload "decode_clarity_values": true + +// Include the contract ABI for transactions that deploy contracts: +"include_contract_abi": true ``` Putting all the pieces together: diff --git a/components/chainhook-cli/src/cli/mod.rs b/components/chainhook-cli/src/cli/mod.rs index a07a7aebc..8ee706a45 100644 --- a/components/chainhook-cli/src/cli/mod.rs +++ b/components/chainhook-cli/src/cli/mod.rs @@ -342,6 +342,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: None, action: HookAction::FileAppend(FileHook { path: "arkadiko.txt".into() }) @@ -358,6 +359,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: None, action: HookAction::FileAppend(FileHook { path: "arkadiko.txt".into() }) diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index d4ba4a808..2ee9894c5 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -512,7 +512,7 @@ fn encode_transaction_including_with_clarity_decoding( transaction: &StacksTransactionData, ctx: &Context, ) -> serde_json::Value { - json!({ + let mut json = json!({ "transaction_identifier": transaction.transaction_identifier, "operations": transaction.operations, "metadata": { @@ -533,9 +533,13 @@ fn encode_transaction_including_with_clarity_decoding( "description": transaction.metadata.description, "sponsor": transaction.metadata.sponsor, "execution_cost": transaction.metadata.execution_cost, - "position": transaction.metadata.position, + "position": transaction.metadata.position }, - }) + }); + if let Some(abi) = &transaction.metadata.contract_abi { + json["contract_abi"] = abi.to_owned(); + } + json } pub fn serialized_event_with_decoded_clarity_value( @@ -770,9 +774,13 @@ pub fn serialize_stacks_payload_to_json<'a>( "block_identifier": block.get_identifier(), "parent_block_identifier": block.get_parent_identifier(), "timestamp": block.get_timestamp(), - "transactions": transactions.iter().map(|transaction| { + "transactions": transactions.into_iter().map(|transaction| { + let mut transaction = transaction.clone(); + if transaction.metadata.contract_abi.is_some() && !trigger.chainhook.include_contract_abi { + transaction.metadata.contract_abi = None; + } if decode_clarity_values { - encode_transaction_including_with_clarity_decoding(transaction, ctx) + encode_transaction_including_with_clarity_decoding(&transaction, ctx) } else { json!(transaction) } @@ -785,9 +793,13 @@ pub fn serialize_stacks_payload_to_json<'a>( "block_identifier": block.get_identifier(), "parent_block_identifier": block.get_parent_identifier(), "timestamp": block.get_timestamp(), - "transactions": transactions.iter().map(|transaction| { + "transactions": transactions.into_iter().map(|transaction| { + let mut transaction = transaction.clone(); + if transaction.metadata.contract_abi.is_some() && !trigger.chainhook.include_contract_abi { + transaction.metadata.contract_abi = None; + } if decode_clarity_values { - encode_transaction_including_with_clarity_decoding(transaction, ctx) + encode_transaction_including_with_clarity_decoding(&transaction, ctx) } else { json!(transaction) } diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index 021c4ae70..6b9d3b9d3 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -348,6 +348,7 @@ fn test_stacks_predicates( expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: false, predicate: predicate, action: HookAction::Noop, enabled: true, @@ -427,6 +428,7 @@ fn test_stacks_predicate_contract_deploy(predicate: StacksPredicate, expected_ap expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: false, predicate: predicate, action: HookAction::Noop, enabled: true, @@ -512,6 +514,7 @@ fn test_stacks_predicate_contract_call(predicate: StacksPredicate, expected_appl expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: false, predicate: predicate, action: HookAction::Noop, enabled: true, @@ -546,6 +549,7 @@ fn test_stacks_hook_action_noop() { expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, + include_contract_abi: false, predicate: StacksPredicate::Txid(ExactMatchingRule::Equals( "0xb92c2ade84a8b85f4c72170680ae42e65438aea4db72ba4b2d6a6960f4141ce8".to_string(), )), @@ -603,6 +607,7 @@ fn test_stacks_hook_action_file_append() { expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: Some(true), + include_contract_abi: false, predicate: StacksPredicate::Txid(ExactMatchingRule::Equals( "0xb92c2ade84a8b85f4c72170680ae42e65438aea4db72ba4b2d6a6960f4141ce8".to_string(), )), diff --git a/components/chainhook-sdk/src/chainhooks/types.rs b/components/chainhook-sdk/src/chainhooks/types.rs index 44f73f471..1f017bc8c 100644 --- a/components/chainhook-sdk/src/chainhooks/types.rs +++ b/components/chainhook-sdk/src/chainhooks/types.rs @@ -410,6 +410,7 @@ impl StacksChainhookFullSpecification { capture_all_events: spec.capture_all_events, decode_clarity_values: spec.decode_clarity_values, expire_after_occurrence: spec.expire_after_occurrence, + include_contract_abi: spec.include_contract_abi.unwrap_or(false), predicate: spec.predicate, action: spec.action, enabled: false, @@ -432,6 +433,8 @@ pub struct StacksChainhookNetworkSpecification { pub capture_all_events: Option, #[serde(skip_serializing_if = "Option::is_none")] pub decode_clarity_values: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_contract_abi: Option, #[serde(rename = "if_this")] pub predicate: StacksPredicate, #[serde(rename = "then_that")] @@ -732,6 +735,7 @@ pub struct StacksChainhookSpecification { pub capture_all_events: Option, #[serde(skip_serializing_if = "Option::is_none")] pub decode_clarity_values: Option, + pub include_contract_abi: bool, #[serde(rename = "predicate")] pub predicate: StacksPredicate, pub action: HookAction, diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 1efea079d..2b5910887 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -67,6 +67,7 @@ fn stacks_chainhook_contract_call( expire_after_occurrence, capture_all_events: None, decode_clarity_values: Some(true), + include_contract_abi: None, predicate: StacksPredicate::ContractCall(StacksContractCallBasedPredicate { contract_identifier: contract_identifier.to_string(), method: method.to_string(), diff --git a/docs/how-to-guides/how-to-use-chainhooks-with-stacks.md b/docs/how-to-guides/how-to-use-chainhooks-with-stacks.md index 0a5b5c506..f95fd213f 100644 --- a/docs/how-to-guides/how-to-use-chainhooks-with-stacks.md +++ b/docs/how-to-guides/how-to-use-chainhooks-with-stacks.md @@ -225,9 +225,12 @@ Following additional configurations can be used to improve the performance of ch - Stop evaluating chainhook after a given number of occurrences found: `"expire_after_occurrence": 1` -- Include decoded clarity values in the payload +- Include decoded clarity values in the payload: `"decode_clarity_values": true` +- Include the contract ABI for transactions that deploy contracts: +`"include_contract_abi": true` + ## Example predicate definition to print events Retrieve and HTTP Post to `http://localhost:3000/api/v1/wrapBtc` the first five transactions interacting with ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09, emitting print events containing the word 'vault'. From 919266ddf1665a4a3c684bbe08d6941341155159 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 16 Aug 2023 12:57:36 -0400 Subject: [PATCH 05/12] skip serializing if no contract_abi included --- components/chainhook-types-rs/src/rosetta.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index 946cb125d..479d2279a 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -220,6 +220,7 @@ pub struct StacksTransactionMetadata { pub execution_cost: Option, pub position: StacksTransactionPosition, pub proof: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub contract_abi: Option, } From c73b7213718fc7a1451894b45169fb0d9f0ad4c0 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 16 Aug 2023 14:29:46 -0400 Subject: [PATCH 06/12] add tests for including contract abi --- .../src/chainhooks/stacks/mod.rs | 2 +- .../base/transaction_contract_deploy.json | 79 ++++++------- .../chainhook-sdk/src/chainhooks/tests/mod.rs | 104 +++++++++++++++++- 3 files changed, 144 insertions(+), 41 deletions(-) diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 2ee9894c5..9ee461ad5 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -537,7 +537,7 @@ fn encode_transaction_including_with_clarity_decoding( }, }); if let Some(abi) = &transaction.metadata.contract_abi { - json["contract_abi"] = abi.to_owned(); + json["metadata"]["contract_abi"] = abi.to_owned(); } json } diff --git a/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json b/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json index 12d266544..f6693dedc 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json +++ b/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json @@ -1,41 +1,42 @@ { - "metadata": { - "description": "", - "execution_cost": { - "read_count": 8, - "read_length": 6, - "runtime": 84581, - "write_count": 13, - "write_length": 1612 - }, - "fee": 750000, - "kind": { - "data": { - "code": ";; The .subnet contract\n\n(define-constant CONTRACT_ADDRESS (as-contract tx-sender))\n\n;; Error codes\n(define-constant ERR_BLOCK_ALREADY_COMMITTED 1)\n(define-constant ERR_INVALID_MINER 2)\n(define-constant ERR_CONTRACT_CALL_FAILED 3)\n(define-constant ERR_TRANSFER_FAILED 4)\n(define-constant ERR_DISALLOWED_ASSET 5)\n(define-constant ERR_ASSET_ALREADY_ALLOWED 6)\n(define-constant ERR_MERKLE_ROOT_DOES_NOT_MATCH 7)\n(define-constant ERR_INVALID_MERKLE_ROOT 8)\n(define-constant ERR_WITHDRAWAL_ALREADY_PROCESSED 9)\n(define-constant ERR_VALIDATION_FAILED 10)\n;;; The value supplied for `target-chain-tip` does not match the current chain tip.\n(define-constant ERR_INVALID_CHAIN_TIP 11)\n;;; The contract was called before reaching this-chain height reaches 1.\n(define-constant ERR_CALLED_TOO_EARLY 12)\n(define-constant ERR_MINT_FAILED 13)\n(define-constant ERR_ATTEMPT_TO_TRANSFER_ZERO_AMOUNT 14)\n(define-constant ERR_IN_COMPUTATION 15)\n;; The contract does not own this NFT to withdraw it.\n(define-constant ERR_NFT_NOT_OWNED_BY_CONTRACT 16)\n(define-constant ERR_VALIDATION_LEAF_FAILED 30)\n\n;; Map from Stacks block height to block commit\n(define-map block-commits uint (buff 32))\n;; Map recording withdrawal roots\n(define-map withdrawal-roots-map (buff 32) bool)\n;; Map recording processed withdrawal leaves\n(define-map processed-withdrawal-leaves-map { withdrawal-leaf-hash: (buff 32), withdrawal-root-hash: (buff 32) } bool)\n\n;; principal that can commit blocks\n(define-data-var miner principal tx-sender)\n;; principal that can register contracts\n(define-data-var admin principal 'ST167FDXCJGS54J1T0J42VTX46G0QQQFRJGBK28RN)\n\n;; Map of allowed contracts for asset transfers - maps L1 contract principal to L2 contract principal\n(define-map allowed-contracts principal principal)\n\n;; Use trait declarations\n(use-trait nft-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.nft-trait)\n(use-trait ft-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.sip-010-trait)\n(use-trait mint-from-subnet-trait .subnet-traits-v1.mint-from-subnet-trait)\n\n;; Update the miner for this contract.\n(define-public (update-miner (new-miner principal))\n (begin\n (asserts! (is-eq tx-sender (var-get miner)) (err ERR_INVALID_MINER))\n (ok (var-set miner new-miner))\n )\n)\n\n;; Register a new FT contract to be supported by this subnet.\n(define-public (register-new-ft-contract (ft-contract ) (l2-contract principal))\n (begin\n ;; Verify that tx-sender is an authorized admin\n (asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))\n\n ;; Set up the assets that the contract is allowed to transfer\n (asserts! (map-insert allowed-contracts (contract-of ft-contract) l2-contract)\n (err ERR_ASSET_ALREADY_ALLOWED))\n\n (print {\n event: \"register-contract\",\n asset-type: \"ft\",\n l1-contract: (contract-of ft-contract),\n l2-contract: l2-contract\n })\n\n (ok true)\n )\n)\n\n;; Register a new NFT contract to be supported by this subnet.\n(define-public (register-new-nft-contract (nft-contract ) (l2-contract principal))\n (begin\n ;; Verify that tx-sender is an authorized admin\n (asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))\n\n ;; Set up the assets that the contract is allowed to transfer\n (asserts! (map-insert allowed-contracts (contract-of nft-contract) l2-contract)\n (err ERR_ASSET_ALREADY_ALLOWED))\n\n (print {\n event: \"register-contract\",\n asset-type: \"nft\",\n l1-contract: (contract-of nft-contract),\n l2-contract: l2-contract\n })\n\n (ok true)\n )\n)\n\n;; Helper function: returns a boolean indicating whether the given principal is a miner\n;; Returns bool\n(define-private (is-miner (miner-to-check principal))\n (is-eq miner-to-check (var-get miner))\n)\n\n;; Helper function: returns a boolean indicating whether the given principal is an admin\n;; Returns bool\n(define-private (is-admin (addr-to-check principal))\n (is-eq addr-to-check (var-get admin))\n)\n\n;; Helper function: determines whether the commit-block operation satisfies pre-conditions\n;; listed in `commit-block`.\n;; Returns response\n(define-private (can-commit-block? (commit-block-height uint) (target-chain-tip (buff 32)))\n (begin\n ;; check no block has been committed at this height\n (asserts! (is-none (map-get? block-commits commit-block-height)) (err ERR_BLOCK_ALREADY_COMMITTED))\n\n ;; check that `target-chain-tip` matches the burn chain tip\n (asserts! (is-eq\n target-chain-tip\n (unwrap! (get-block-info? id-header-hash (- block-height u1)) (err ERR_CALLED_TOO_EARLY)) )\n (err ERR_INVALID_CHAIN_TIP))\n\n ;; check that the tx sender is one of the miners\n (asserts! (is-miner tx-sender) (err ERR_INVALID_MINER))\n\n ;; check that the miner called this contract directly\n (asserts! (is-miner contract-caller) (err ERR_INVALID_MINER))\n\n (ok true)\n )\n)\n\n;; Helper function: modifies the block-commits map with a new commit and prints related info\n;; Returns response<(buff 32), ?>\n(define-private (inner-commit-block (block (buff 32)) (commit-block-height uint) (withdrawal-root (buff 32)))\n (begin\n (map-set block-commits commit-block-height block)\n (map-set withdrawal-roots-map withdrawal-root true)\n (print {\n event: \"block-commit\",\n block-commit: block,\n withdrawal-root: withdrawal-root,\n block-height: commit-block-height\n })\n (ok block)\n )\n)\n\n;; The subnet miner calls this function to commit a block at a particular height.\n;; `block` is the hash of the block being submitted.\n;; `target-chain-tip` is the `id-header-hash` of the burn block (i.e., block on\n;; this chain) that the miner intends to build off.\n;;\n;; Fails if:\n;; 1) we have already committed at this block height\n;; 2) `target-chain-tip` is not the burn chain tip (i.e., on this chain)\n;; 3) the sender is not a miner\n(define-public (commit-block (block (buff 32)) (target-chain-tip (buff 32)) (withdrawal-root (buff 32)))\n (let ((commit-block-height block-height))\n (try! (can-commit-block? commit-block-height target-chain-tip))\n (inner-commit-block block commit-block-height withdrawal-root)\n )\n)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR NFT ASSET TRANSFERS\n\n;; Helper function that transfers the specified NFT from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-nft-asset\n (nft-contract )\n (id uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract transfer id sender recipient))\n (transfer-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-mint-nft-asset\n (nft-mint-contract )\n (id uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (as-contract (contract-call? nft-mint-contract mint-from-subnet id sender recipient)))\n (mint-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! mint-result (err ERR_MINT_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-transfer-or-mint-nft-asset\n (nft-contract )\n (nft-mint-contract )\n (id uint)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract get-owner id))\n (nft-owner (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-nft (is-eq nft-owner (some CONTRACT_ADDRESS)))\n (no-owner (is-eq nft-owner none))\n )\n\n (if contract-owns-nft\n (inner-transfer-nft-asset nft-contract id CONTRACT_ADDRESS recipient)\n (if no-owner\n ;; Try minting the asset if there is no existing owner of this NFT\n (inner-mint-nft-asset nft-mint-contract id CONTRACT_ADDRESS recipient)\n ;; In this case, a principal other than this contract owns this NFT, so minting is not possible\n (err ERR_MINT_FAILED)\n )\n )\n )\n)\n\n;; A user calls this function to deposit an NFT into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-nft-asset\n (nft-contract )\n (id uint)\n (sender principal)\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (subnet-contract-id (unwrap! (map-get? allowed-contracts (contract-of nft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n\n ;; Try to transfer the NFT to this contract\n (asserts! (try! (inner-transfer-nft-asset nft-contract id sender CONTRACT_ADDRESS)) (err ERR_TRANSFER_FAILED))\n\n ;; Emit a print event - the node consumes this\n (print {\n event: \"deposit-nft\",\n l1-contract-id: (as-contract nft-contract),\n nft-id: id,\n sender: sender,\n subnet-contract-id: subnet-contract-id,\n })\n\n (ok true)\n )\n)\n\n\n;; Helper function for `withdraw-nft-asset`\n;; Returns response\n(define-public (inner-withdraw-nft-asset\n (nft-contract )\n (l2-contract principal)\n (id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (nft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-nft l2-contract id recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts!\n (try!\n (match nft-mint-contract\n mint-contract (as-contract (inner-transfer-or-mint-nft-asset nft-contract mint-contract id recipient))\n (as-contract (inner-transfer-without-mint-nft-asset nft-contract id recipient))\n )\n )\n (err ERR_TRANSFER_FAILED)\n )\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED)\n )\n\n (ok true)\n )\n)\n\n;; A user calls this function to withdraw the specified NFT from this contract.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-nft-asset\n (nft-contract )\n (id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (nft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (l2-contract (unwrap! (map-get? allowed-contracts (contract-of nft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n (asserts!\n (try! (inner-withdraw-nft-asset\n nft-contract\n l2-contract\n id\n recipient\n withdrawal-id\n height\n nft-mint-contract\n withdrawal-root\n withdrawal-leaf-hash\n sibling-hashes\n ))\n (err ERR_TRANSFER_FAILED)\n )\n\n ;; Emit a print event\n (print {\n event: \"withdraw-nft\",\n l1-contract-id: (as-contract nft-contract),\n nft-id: id,\n recipient: recipient\n })\n\n (ok true)\n )\n)\n\n\n;; Like `inner-transfer-or-mint-nft-asset but without allowing or requiring a mint function. In order to withdraw, the user must\n;; have the appropriate balance.\n(define-private (inner-transfer-without-mint-nft-asset\n (nft-contract )\n (id uint)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract get-owner id))\n (nft-owner (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-nft (is-eq nft-owner (some CONTRACT_ADDRESS)))\n )\n\n (asserts! contract-owns-nft (err ERR_NFT_NOT_OWNED_BY_CONTRACT))\n (inner-transfer-nft-asset nft-contract id CONTRACT_ADDRESS recipient)\n )\n)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR FUNGIBLE TOKEN ASSET TRANSFERS\n\n;; Helper function that transfers a specified amount of the fungible token from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-ft-asset\n (ft-contract )\n (amount uint)\n (sender principal)\n (recipient principal)\n (memo (optional (buff 34)))\n )\n (let (\n (call-result (contract-call? ft-contract transfer amount sender recipient memo))\n (transfer-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; FIXME: SIP-010 doesn't require that transfer returns (ok true) on success, so is this check necessary?\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-mint-ft-asset\n (ft-mint-contract )\n (amount uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (as-contract (contract-call? ft-mint-contract mint-from-subnet amount sender recipient)))\n (mint-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! mint-result (err ERR_MINT_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-transfer-or-mint-ft-asset\n (ft-contract )\n (ft-mint-contract )\n (amount uint)\n (recipient principal)\n (memo (optional (buff 34)))\n )\n (let (\n (call-result (contract-call? ft-contract get-balance CONTRACT_ADDRESS))\n (contract-ft-balance (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-enough (>= contract-ft-balance amount))\n (amount-to-transfer (if contract-owns-enough amount contract-ft-balance))\n (amount-to-mint (- amount amount-to-transfer))\n )\n\n ;; Check that the total balance between the transfer and mint is equal to the original balance\n (asserts! (is-eq amount (+ amount-to-transfer amount-to-mint)) (err ERR_IN_COMPUTATION))\n\n (and\n (> amount-to-transfer u0)\n (try! (inner-transfer-ft-asset ft-contract amount-to-transfer CONTRACT_ADDRESS recipient memo))\n )\n (and\n (> amount-to-mint u0)\n (try! (inner-mint-ft-asset ft-mint-contract amount-to-mint CONTRACT_ADDRESS recipient))\n )\n\n (ok true)\n )\n)\n\n;; A user calls this function to deposit a fungible token into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-ft-asset\n (ft-contract )\n (amount uint)\n (sender principal)\n (memo (optional (buff 34)))\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (subnet-contract-id (unwrap! (map-get? allowed-contracts (contract-of ft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n ;; Try to transfer the FT to this contract\n (asserts! (try! (inner-transfer-ft-asset ft-contract amount sender CONTRACT_ADDRESS memo)) (err ERR_TRANSFER_FAILED))\n\n (let (\n (ft-name (unwrap! (contract-call? ft-contract get-name) (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Emit a print event - the node consumes this\n (print {\n event: \"deposit-ft\",\n l1-contract-id: (as-contract ft-contract),\n ft-name: ft-name,\n ft-amount: amount,\n sender: sender,\n subnet-contract-id: subnet-contract-id,\n })\n )\n\n (ok true)\n )\n)\n\n;; This function performs validity checks related to the withdrawal and performs the withdrawal as well.\n;; Returns response\n(define-private (inner-withdraw-ft-asset\n (ft-contract )\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (memo (optional (buff 34)))\n (ft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-ft (contract-of ft-contract) amount recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts!\n (try!\n (match ft-mint-contract\n mint-contract (as-contract (inner-transfer-or-mint-ft-asset ft-contract mint-contract amount recipient memo))\n (as-contract (inner-transfer-ft-asset ft-contract amount CONTRACT_ADDRESS recipient memo))\n )\n )\n (err ERR_TRANSFER_FAILED)\n )\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n (ok true)\n )\n)\n\n;; A user can call this function to withdraw some amount of a fungible token asset from the\n;; contract and send it to a recipient.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-ft-asset\n (ft-contract )\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (memo (optional (buff 34)))\n (ft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (begin\n ;; Check that the withdraw amount is positive\n (asserts! (> amount u0) (err ERR_ATTEMPT_TO_TRANSFER_ZERO_AMOUNT))\n\n ;; Check that the asset belongs to the allowed-contracts map\n (unwrap! (map-get? allowed-contracts (contract-of ft-contract)) (err ERR_DISALLOWED_ASSET))\n\n (asserts!\n (try! (inner-withdraw-ft-asset\n ft-contract\n amount\n recipient\n withdrawal-id\n height\n memo\n ft-mint-contract\n withdrawal-root\n withdrawal-leaf-hash\n sibling-hashes))\n (err ERR_TRANSFER_FAILED)\n )\n\n (let (\n (ft-name (unwrap! (contract-call? ft-contract get-name) (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Emit a print event\n (print {\n event: \"withdraw-ft\",\n l1-contract-id: (as-contract ft-contract),\n ft-name: ft-name,\n ft-amount: amount,\n recipient: recipient,\n })\n )\n\n (ok true)\n )\n)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR STX TRANSFERS\n\n\n;; Helper function that transfers the given amount from the specified fungible token from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-stx (amount uint) (sender principal) (recipient principal))\n (let (\n (call-result (stx-transfer? amount sender recipient))\n (transfer-result (unwrap! call-result (err ERR_TRANSFER_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n;; A user calls this function to deposit STX into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-stx (amount uint) (sender principal))\n (begin\n ;; Try to transfer the STX to this contract\n (asserts! (try! (inner-transfer-stx amount sender CONTRACT_ADDRESS)) (err ERR_TRANSFER_FAILED))\n\n ;; Emit a print event - the node consumes this\n (print { event: \"deposit-stx\", sender: sender, amount: amount })\n\n (ok true)\n )\n)\n\n(define-read-only (leaf-hash-withdraw-stx\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"stx\",\n amount: amount,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n(define-read-only (leaf-hash-withdraw-nft\n (asset-contract principal)\n (nft-id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"nft\",\n nft-id: nft-id,\n asset-contract: asset-contract,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n(define-read-only (leaf-hash-withdraw-ft\n (asset-contract principal)\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"ft\",\n amount: amount,\n asset-contract: asset-contract,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n;; A user calls this function to withdraw STX from this contract.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-stx\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-stx amount recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts! (try! (as-contract (inner-transfer-stx amount tx-sender recipient))) (err ERR_TRANSFER_FAILED))\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n ;; Emit a print event\n (print { event: \"withdraw-stx\", recipient: recipient, amount: amount })\n\n (ok true)\n )\n)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; GENERAL WITHDRAWAL FUNCTIONS\n\n;; This function concats the two given hashes in the correct order. It also prepends the buff `0x01`, which is\n;; a tag denoting a node (versus a leaf).\n;; Returns a buff\n(define-private (create-node-hash\n (curr-hash (buff 32))\n (sibling-hash (buff 32))\n (is-sibling-left-side bool)\n )\n (let (\n (concatted-hash (if is-sibling-left-side\n (concat sibling-hash curr-hash)\n (concat curr-hash sibling-hash)\n ))\n )\n\n (concat 0x01 concatted-hash)\n )\n)\n\n;; This function hashes the curr hash with its sibling hash.\n;; Returns (buff 32)\n(define-private (hash-help\n (sibling {\n hash: (buff 32),\n is-left-side: bool,\n })\n (curr-node-hash (buff 32))\n )\n (let (\n (sibling-hash (get hash sibling))\n (is-sibling-left-side (get is-left-side sibling))\n (new-buff (create-node-hash curr-node-hash sibling-hash is-sibling-left-side))\n )\n (sha512/256 new-buff)\n )\n)\n\n;; This function checks:\n;; - That the provided withdrawal root matches a previously submitted one (passed to the function `commit-block`)\n;; - That the computed withdrawal root matches a previous valid withdrawal root\n;; - That the given withdrawal leaf hash has not been previously processed\n;; Returns response\n(define-private (check-withdrawal-hashes\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (begin\n ;; Check that the user submitted a valid withdrawal root\n (asserts! (is-some (map-get? withdrawal-roots-map withdrawal-root)) (err ERR_INVALID_MERKLE_ROOT))\n\n ;; Check that this withdrawal leaf has not been processed before\n (asserts!\n (is-none\n (map-get? processed-withdrawal-leaves-map\n { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root }))\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n (let ((calculated-withdrawal-root (fold hash-help sibling-hashes withdrawal-leaf-hash))\n (roots-match (is-eq calculated-withdrawal-root withdrawal-root)))\n (if roots-match\n (ok true)\n (err ERR_MERKLE_ROOT_DOES_NOT_MATCH))\n )\n )\n)\n\n;; This function should be called after the asset in question has been transferred.\n;; It adds the withdrawal leaf hash to a map of processed leaves. This ensures that\n;; this withdrawal leaf can't be used again to withdraw additional funds.\n;; Returns bool\n(define-private (finish-withdraw\n (withdraw-info {\n withdrawal-leaf-hash: (buff 32),\n withdrawal-root-hash: (buff 32)\n })\n )\n (map-insert processed-withdrawal-leaves-map withdraw-info true)\n)\n", - "contract_identifier": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1" - }, - "type": "ContractDeployment" - }, - "nonce": 33, - "position": { - "index": 1 - }, - "proof": null, - "raw_tx": "0x808000000004003e44ad50f99edc5e6cc5543b636284186894a008000000000000002100000000000b71b00000a84691e27fd2d46475230707a657ef7ddf6de7a4b06a1ac2049384c6474e73f85ee4ce205e0904160adaa160498df02db1782f898b5905db4d249b7025f6604c030100000000060218666169722d616d6172616e74682d7268696e6f6365726f73000005103b3b2068656c6c6f2d776f726c6420636f6e74726163740a0a28646566696e652d636f6e7374616e742073656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b51394836445052290a28646566696e652d636f6e7374616e7420726563697069656e742027534d324a365a593438475631455a35563256355242394d5036365357383650594b4b51565838583047290a0a28646566696e652d66756e6769626c652d746f6b656e206e6f76656c2d746f6b656e2d3139290a28626567696e202866742d6d696e743f206e6f76656c2d746f6b656e2d3139207531322073656e64657229290a28626567696e202866742d7472616e736665723f206e6f76656c2d746f6b656e2d31392075322073656e64657220726563697069656e7429290a0a28646566696e652d6e6f6e2d66756e6769626c652d746f6b656e2068656c6c6f2d6e66742075696e74290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075312073656e64657229290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075322073656e64657229290a28626567696e20286e66742d7472616e736665723f2068656c6c6f2d6e66742075312073656e64657220726563697069656e7429290a0a28646566696e652d7075626c69632028746573742d656d69742d6576656e74290a2020202028626567696e0a2020202020202020287072696e7420224576656e74212048656c6c6f20776f726c6422290a2020202020202020286f6b2075312929290a28626567696e2028746573742d656d69742d6576656e7429290a0a28646566696e652d7075626c69632028746573742d6576656e742d7479706573290a2020202028626567696e0a202020202020202028756e777261702d70616e6963202866742d6d696e743f206e6f76656c2d746f6b656e2d313920753320726563697069656e7429290a202020202020202028756e777261702d70616e696320286e66742d6d696e743f2068656c6c6f2d6e667420753220726563697069656e7429290a202020202020202028756e777261702d70616e696320287374782d7472616e736665723f207536302074782d73656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b5139483644505229290a202020202020202028756e777261702d70616e696320287374782d6275726e3f207532302074782d73656e64657229290a2020202020202020286f6b2075312929290a0a28646566696e652d6d61702073746f7265207b6b65793a202862756666203332297d207b76616c75653a202862756666203332297d290a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a2020202028626567696e0a2020202020202020286d6174636820286d61702d6765743f2073746f7265207b6b65793a206b65797d290a202020202020202020202020656e74727920286f6b20286765742076616c756520656e74727929290a202020202020202020202020286572722030292929290a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a2020202028626567696e0a2020202020202020286d61702d7365742073746f7265207b6b65793a206b65797d207b76616c75653a2076616c75657d290a2020202020202020286f6b207531292929", - "receipt": { - "contract_calls_stack": [], - "events": [], - "mutated_assets_radius": [], - "mutated_contracts_radius": [ - "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1" - ] - }, - "result": "(ok true)", - "sender": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9", - "success": true + "metadata": { + "description": "", + "execution_cost": { + "read_count": 8, + "read_length": 6, + "runtime": 84581, + "write_count": 13, + "write_length": 1612 }, - "operations": [], - "transaction_identifier": { - "hash": "0x93c89ffdac77ed2ba52611563bd491f56f5d558e23d311a105663ae32bdf18e5" - } -} \ No newline at end of file + "fee": 750000, + "kind": { + "data": { + "code": ";; The .subnet contract\n\n(define-constant CONTRACT_ADDRESS (as-contract tx-sender))\n\n;; Error codes\n(define-constant ERR_BLOCK_ALREADY_COMMITTED 1)\n(define-constant ERR_INVALID_MINER 2)\n(define-constant ERR_CONTRACT_CALL_FAILED 3)\n(define-constant ERR_TRANSFER_FAILED 4)\n(define-constant ERR_DISALLOWED_ASSET 5)\n(define-constant ERR_ASSET_ALREADY_ALLOWED 6)\n(define-constant ERR_MERKLE_ROOT_DOES_NOT_MATCH 7)\n(define-constant ERR_INVALID_MERKLE_ROOT 8)\n(define-constant ERR_WITHDRAWAL_ALREADY_PROCESSED 9)\n(define-constant ERR_VALIDATION_FAILED 10)\n;;; The value supplied for `target-chain-tip` does not match the current chain tip.\n(define-constant ERR_INVALID_CHAIN_TIP 11)\n;;; The contract was called before reaching this-chain height reaches 1.\n(define-constant ERR_CALLED_TOO_EARLY 12)\n(define-constant ERR_MINT_FAILED 13)\n(define-constant ERR_ATTEMPT_TO_TRANSFER_ZERO_AMOUNT 14)\n(define-constant ERR_IN_COMPUTATION 15)\n;; The contract does not own this NFT to withdraw it.\n(define-constant ERR_NFT_NOT_OWNED_BY_CONTRACT 16)\n(define-constant ERR_VALIDATION_LEAF_FAILED 30)\n\n;; Map from Stacks block height to block commit\n(define-map block-commits uint (buff 32))\n;; Map recording withdrawal roots\n(define-map withdrawal-roots-map (buff 32) bool)\n;; Map recording processed withdrawal leaves\n(define-map processed-withdrawal-leaves-map { withdrawal-leaf-hash: (buff 32), withdrawal-root-hash: (buff 32) } bool)\n\n;; principal that can commit blocks\n(define-data-var miner principal tx-sender)\n;; principal that can register contracts\n(define-data-var admin principal 'ST167FDXCJGS54J1T0J42VTX46G0QQQFRJGBK28RN)\n\n;; Map of allowed contracts for asset transfers - maps L1 contract principal to L2 contract principal\n(define-map allowed-contracts principal principal)\n\n;; Use trait declarations\n(use-trait nft-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.nft-trait)\n(use-trait ft-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.sip-010-trait)\n(use-trait mint-from-subnet-trait .subnet-traits-v1.mint-from-subnet-trait)\n\n;; Update the miner for this contract.\n(define-public (update-miner (new-miner principal))\n (begin\n (asserts! (is-eq tx-sender (var-get miner)) (err ERR_INVALID_MINER))\n (ok (var-set miner new-miner))\n )\n)\n\n;; Register a new FT contract to be supported by this subnet.\n(define-public (register-new-ft-contract (ft-contract ) (l2-contract principal))\n (begin\n ;; Verify that tx-sender is an authorized admin\n (asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))\n\n ;; Set up the assets that the contract is allowed to transfer\n (asserts! (map-insert allowed-contracts (contract-of ft-contract) l2-contract)\n (err ERR_ASSET_ALREADY_ALLOWED))\n\n (print {\n event: \"register-contract\",\n asset-type: \"ft\",\n l1-contract: (contract-of ft-contract),\n l2-contract: l2-contract\n })\n\n (ok true)\n )\n)\n\n;; Register a new NFT contract to be supported by this subnet.\n(define-public (register-new-nft-contract (nft-contract ) (l2-contract principal))\n (begin\n ;; Verify that tx-sender is an authorized admin\n (asserts! (is-admin tx-sender) (err ERR_INVALID_MINER))\n\n ;; Set up the assets that the contract is allowed to transfer\n (asserts! (map-insert allowed-contracts (contract-of nft-contract) l2-contract)\n (err ERR_ASSET_ALREADY_ALLOWED))\n\n (print {\n event: \"register-contract\",\n asset-type: \"nft\",\n l1-contract: (contract-of nft-contract),\n l2-contract: l2-contract\n })\n\n (ok true)\n )\n)\n\n;; Helper function: returns a boolean indicating whether the given principal is a miner\n;; Returns bool\n(define-private (is-miner (miner-to-check principal))\n (is-eq miner-to-check (var-get miner))\n)\n\n;; Helper function: returns a boolean indicating whether the given principal is an admin\n;; Returns bool\n(define-private (is-admin (addr-to-check principal))\n (is-eq addr-to-check (var-get admin))\n)\n\n;; Helper function: determines whether the commit-block operation satisfies pre-conditions\n;; listed in `commit-block`.\n;; Returns response\n(define-private (can-commit-block? (commit-block-height uint) (target-chain-tip (buff 32)))\n (begin\n ;; check no block has been committed at this height\n (asserts! (is-none (map-get? block-commits commit-block-height)) (err ERR_BLOCK_ALREADY_COMMITTED))\n\n ;; check that `target-chain-tip` matches the burn chain tip\n (asserts! (is-eq\n target-chain-tip\n (unwrap! (get-block-info? id-header-hash (- block-height u1)) (err ERR_CALLED_TOO_EARLY)) )\n (err ERR_INVALID_CHAIN_TIP))\n\n ;; check that the tx sender is one of the miners\n (asserts! (is-miner tx-sender) (err ERR_INVALID_MINER))\n\n ;; check that the miner called this contract directly\n (asserts! (is-miner contract-caller) (err ERR_INVALID_MINER))\n\n (ok true)\n )\n)\n\n;; Helper function: modifies the block-commits map with a new commit and prints related info\n;; Returns response<(buff 32), ?>\n(define-private (inner-commit-block (block (buff 32)) (commit-block-height uint) (withdrawal-root (buff 32)))\n (begin\n (map-set block-commits commit-block-height block)\n (map-set withdrawal-roots-map withdrawal-root true)\n (print {\n event: \"block-commit\",\n block-commit: block,\n withdrawal-root: withdrawal-root,\n block-height: commit-block-height\n })\n (ok block)\n )\n)\n\n;; The subnet miner calls this function to commit a block at a particular height.\n;; `block` is the hash of the block being submitted.\n;; `target-chain-tip` is the `id-header-hash` of the burn block (i.e., block on\n;; this chain) that the miner intends to build off.\n;;\n;; Fails if:\n;; 1) we have already committed at this block height\n;; 2) `target-chain-tip` is not the burn chain tip (i.e., on this chain)\n;; 3) the sender is not a miner\n(define-public (commit-block (block (buff 32)) (target-chain-tip (buff 32)) (withdrawal-root (buff 32)))\n (let ((commit-block-height block-height))\n (try! (can-commit-block? commit-block-height target-chain-tip))\n (inner-commit-block block commit-block-height withdrawal-root)\n )\n)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR NFT ASSET TRANSFERS\n\n;; Helper function that transfers the specified NFT from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-nft-asset\n (nft-contract )\n (id uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract transfer id sender recipient))\n (transfer-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-mint-nft-asset\n (nft-mint-contract )\n (id uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (as-contract (contract-call? nft-mint-contract mint-from-subnet id sender recipient)))\n (mint-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! mint-result (err ERR_MINT_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-transfer-or-mint-nft-asset\n (nft-contract )\n (nft-mint-contract )\n (id uint)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract get-owner id))\n (nft-owner (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-nft (is-eq nft-owner (some CONTRACT_ADDRESS)))\n (no-owner (is-eq nft-owner none))\n )\n\n (if contract-owns-nft\n (inner-transfer-nft-asset nft-contract id CONTRACT_ADDRESS recipient)\n (if no-owner\n ;; Try minting the asset if there is no existing owner of this NFT\n (inner-mint-nft-asset nft-mint-contract id CONTRACT_ADDRESS recipient)\n ;; In this case, a principal other than this contract owns this NFT, so minting is not possible\n (err ERR_MINT_FAILED)\n )\n )\n )\n)\n\n;; A user calls this function to deposit an NFT into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-nft-asset\n (nft-contract )\n (id uint)\n (sender principal)\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (subnet-contract-id (unwrap! (map-get? allowed-contracts (contract-of nft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n\n ;; Try to transfer the NFT to this contract\n (asserts! (try! (inner-transfer-nft-asset nft-contract id sender CONTRACT_ADDRESS)) (err ERR_TRANSFER_FAILED))\n\n ;; Emit a print event - the node consumes this\n (print {\n event: \"deposit-nft\",\n l1-contract-id: (as-contract nft-contract),\n nft-id: id,\n sender: sender,\n subnet-contract-id: subnet-contract-id,\n })\n\n (ok true)\n )\n)\n\n\n;; Helper function for `withdraw-nft-asset`\n;; Returns response\n(define-public (inner-withdraw-nft-asset\n (nft-contract )\n (l2-contract principal)\n (id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (nft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-nft l2-contract id recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts!\n (try!\n (match nft-mint-contract\n mint-contract (as-contract (inner-transfer-or-mint-nft-asset nft-contract mint-contract id recipient))\n (as-contract (inner-transfer-without-mint-nft-asset nft-contract id recipient))\n )\n )\n (err ERR_TRANSFER_FAILED)\n )\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED)\n )\n\n (ok true)\n )\n)\n\n;; A user calls this function to withdraw the specified NFT from this contract.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-nft-asset\n (nft-contract )\n (id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (nft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (l2-contract (unwrap! (map-get? allowed-contracts (contract-of nft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n (asserts!\n (try! (inner-withdraw-nft-asset\n nft-contract\n l2-contract\n id\n recipient\n withdrawal-id\n height\n nft-mint-contract\n withdrawal-root\n withdrawal-leaf-hash\n sibling-hashes\n ))\n (err ERR_TRANSFER_FAILED)\n )\n\n ;; Emit a print event\n (print {\n event: \"withdraw-nft\",\n l1-contract-id: (as-contract nft-contract),\n nft-id: id,\n recipient: recipient\n })\n\n (ok true)\n )\n)\n\n\n;; Like `inner-transfer-or-mint-nft-asset but without allowing or requiring a mint function. In order to withdraw, the user must\n;; have the appropriate balance.\n(define-private (inner-transfer-without-mint-nft-asset\n (nft-contract )\n (id uint)\n (recipient principal)\n )\n (let (\n (call-result (contract-call? nft-contract get-owner id))\n (nft-owner (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-nft (is-eq nft-owner (some CONTRACT_ADDRESS)))\n )\n\n (asserts! contract-owns-nft (err ERR_NFT_NOT_OWNED_BY_CONTRACT))\n (inner-transfer-nft-asset nft-contract id CONTRACT_ADDRESS recipient)\n )\n)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR FUNGIBLE TOKEN ASSET TRANSFERS\n\n;; Helper function that transfers a specified amount of the fungible token from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-ft-asset\n (ft-contract )\n (amount uint)\n (sender principal)\n (recipient principal)\n (memo (optional (buff 34)))\n )\n (let (\n (call-result (contract-call? ft-contract transfer amount sender recipient memo))\n (transfer-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; FIXME: SIP-010 doesn't require that transfer returns (ok true) on success, so is this check necessary?\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-mint-ft-asset\n (ft-mint-contract )\n (amount uint)\n (sender principal)\n (recipient principal)\n )\n (let (\n (call-result (as-contract (contract-call? ft-mint-contract mint-from-subnet amount sender recipient)))\n (mint-result (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! mint-result (err ERR_MINT_FAILED))\n\n (ok true)\n )\n)\n\n(define-private (inner-transfer-or-mint-ft-asset\n (ft-contract )\n (ft-mint-contract )\n (amount uint)\n (recipient principal)\n (memo (optional (buff 34)))\n )\n (let (\n (call-result (contract-call? ft-contract get-balance CONTRACT_ADDRESS))\n (contract-ft-balance (unwrap! call-result (err ERR_CONTRACT_CALL_FAILED)))\n (contract-owns-enough (>= contract-ft-balance amount))\n (amount-to-transfer (if contract-owns-enough amount contract-ft-balance))\n (amount-to-mint (- amount amount-to-transfer))\n )\n\n ;; Check that the total balance between the transfer and mint is equal to the original balance\n (asserts! (is-eq amount (+ amount-to-transfer amount-to-mint)) (err ERR_IN_COMPUTATION))\n\n (and\n (> amount-to-transfer u0)\n (try! (inner-transfer-ft-asset ft-contract amount-to-transfer CONTRACT_ADDRESS recipient memo))\n )\n (and\n (> amount-to-mint u0)\n (try! (inner-mint-ft-asset ft-mint-contract amount-to-mint CONTRACT_ADDRESS recipient))\n )\n\n (ok true)\n )\n)\n\n;; A user calls this function to deposit a fungible token into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-ft-asset\n (ft-contract )\n (amount uint)\n (sender principal)\n (memo (optional (buff 34)))\n )\n (let (\n ;; Check that the asset belongs to the allowed-contracts map\n (subnet-contract-id (unwrap! (map-get? allowed-contracts (contract-of ft-contract)) (err ERR_DISALLOWED_ASSET)))\n )\n ;; Try to transfer the FT to this contract\n (asserts! (try! (inner-transfer-ft-asset ft-contract amount sender CONTRACT_ADDRESS memo)) (err ERR_TRANSFER_FAILED))\n\n (let (\n (ft-name (unwrap! (contract-call? ft-contract get-name) (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Emit a print event - the node consumes this\n (print {\n event: \"deposit-ft\",\n l1-contract-id: (as-contract ft-contract),\n ft-name: ft-name,\n ft-amount: amount,\n sender: sender,\n subnet-contract-id: subnet-contract-id,\n })\n )\n\n (ok true)\n )\n)\n\n;; This function performs validity checks related to the withdrawal and performs the withdrawal as well.\n;; Returns response\n(define-private (inner-withdraw-ft-asset\n (ft-contract )\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (memo (optional (buff 34)))\n (ft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-ft (contract-of ft-contract) amount recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts!\n (try!\n (match ft-mint-contract\n mint-contract (as-contract (inner-transfer-or-mint-ft-asset ft-contract mint-contract amount recipient memo))\n (as-contract (inner-transfer-ft-asset ft-contract amount CONTRACT_ADDRESS recipient memo))\n )\n )\n (err ERR_TRANSFER_FAILED)\n )\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n (ok true)\n )\n)\n\n;; A user can call this function to withdraw some amount of a fungible token asset from the\n;; contract and send it to a recipient.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-ft-asset\n (ft-contract )\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (memo (optional (buff 34)))\n (ft-mint-contract (optional ))\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (begin\n ;; Check that the withdraw amount is positive\n (asserts! (> amount u0) (err ERR_ATTEMPT_TO_TRANSFER_ZERO_AMOUNT))\n\n ;; Check that the asset belongs to the allowed-contracts map\n (unwrap! (map-get? allowed-contracts (contract-of ft-contract)) (err ERR_DISALLOWED_ASSET))\n\n (asserts!\n (try! (inner-withdraw-ft-asset\n ft-contract\n amount\n recipient\n withdrawal-id\n height\n memo\n ft-mint-contract\n withdrawal-root\n withdrawal-leaf-hash\n sibling-hashes))\n (err ERR_TRANSFER_FAILED)\n )\n\n (let (\n (ft-name (unwrap! (contract-call? ft-contract get-name) (err ERR_CONTRACT_CALL_FAILED)))\n )\n ;; Emit a print event\n (print {\n event: \"withdraw-ft\",\n l1-contract-id: (as-contract ft-contract),\n ft-name: ft-name,\n ft-amount: amount,\n recipient: recipient,\n })\n )\n\n (ok true)\n )\n)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; FOR STX TRANSFERS\n\n\n;; Helper function that transfers the given amount from the specified fungible token from the given sender to the given recipient.\n;; Returns response\n(define-private (inner-transfer-stx (amount uint) (sender principal) (recipient principal))\n (let (\n (call-result (stx-transfer? amount sender recipient))\n (transfer-result (unwrap! call-result (err ERR_TRANSFER_FAILED)))\n )\n ;; Check that the transfer succeeded\n (asserts! transfer-result (err ERR_TRANSFER_FAILED))\n\n (ok true)\n )\n)\n\n;; A user calls this function to deposit STX into the contract.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (deposit-stx (amount uint) (sender principal))\n (begin\n ;; Try to transfer the STX to this contract\n (asserts! (try! (inner-transfer-stx amount sender CONTRACT_ADDRESS)) (err ERR_TRANSFER_FAILED))\n\n ;; Emit a print event - the node consumes this\n (print { event: \"deposit-stx\", sender: sender, amount: amount })\n\n (ok true)\n )\n)\n\n(define-read-only (leaf-hash-withdraw-stx\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"stx\",\n amount: amount,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n(define-read-only (leaf-hash-withdraw-nft\n (asset-contract principal)\n (nft-id uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"nft\",\n nft-id: nft-id,\n asset-contract: asset-contract,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n(define-read-only (leaf-hash-withdraw-ft\n (asset-contract principal)\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n )\n (sha512/256 (concat 0x00 (unwrap-panic (to-consensus-buff?\n {\n type: \"ft\",\n amount: amount,\n asset-contract: asset-contract,\n recipient: recipient,\n withdrawal-id: withdrawal-id,\n height: height\n })))\n )\n)\n\n;; A user calls this function to withdraw STX from this contract.\n;; In order for this withdrawal to go through, the given withdrawal must have been included\n;; in a withdrawal Merkle tree a subnet miner submitted. The user must provide the leaf\n;; hash of their withdrawal and the root hash of the specific Merkle tree their withdrawal\n;; is included in. They must also provide a list of sibling hashes. The withdraw function\n;; uses the provided hashes to ensure the requested withdrawal is valid.\n;; The function emits a print with details of this event.\n;; Returns response\n(define-public (withdraw-stx\n (amount uint)\n (recipient principal)\n (withdrawal-id uint)\n (height uint)\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (let ((hashes-are-valid (check-withdrawal-hashes withdrawal-root withdrawal-leaf-hash sibling-hashes)))\n\n (asserts! (try! hashes-are-valid) (err ERR_VALIDATION_FAILED))\n ;; check that the withdrawal request data matches the supplied leaf hash\n (asserts! (is-eq withdrawal-leaf-hash\n (leaf-hash-withdraw-stx amount recipient withdrawal-id height))\n (err ERR_VALIDATION_LEAF_FAILED))\n\n (asserts! (try! (as-contract (inner-transfer-stx amount tx-sender recipient))) (err ERR_TRANSFER_FAILED))\n\n (asserts!\n (finish-withdraw { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root })\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n ;; Emit a print event\n (print { event: \"withdraw-stx\", recipient: recipient, amount: amount })\n\n (ok true)\n )\n)\n\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; GENERAL WITHDRAWAL FUNCTIONS\n\n;; This function concats the two given hashes in the correct order. It also prepends the buff `0x01`, which is\n;; a tag denoting a node (versus a leaf).\n;; Returns a buff\n(define-private (create-node-hash\n (curr-hash (buff 32))\n (sibling-hash (buff 32))\n (is-sibling-left-side bool)\n )\n (let (\n (concatted-hash (if is-sibling-left-side\n (concat sibling-hash curr-hash)\n (concat curr-hash sibling-hash)\n ))\n )\n\n (concat 0x01 concatted-hash)\n )\n)\n\n;; This function hashes the curr hash with its sibling hash.\n;; Returns (buff 32)\n(define-private (hash-help\n (sibling {\n hash: (buff 32),\n is-left-side: bool,\n })\n (curr-node-hash (buff 32))\n )\n (let (\n (sibling-hash (get hash sibling))\n (is-sibling-left-side (get is-left-side sibling))\n (new-buff (create-node-hash curr-node-hash sibling-hash is-sibling-left-side))\n )\n (sha512/256 new-buff)\n )\n)\n\n;; This function checks:\n;; - That the provided withdrawal root matches a previously submitted one (passed to the function `commit-block`)\n;; - That the computed withdrawal root matches a previous valid withdrawal root\n;; - That the given withdrawal leaf hash has not been previously processed\n;; Returns response\n(define-private (check-withdrawal-hashes\n (withdrawal-root (buff 32))\n (withdrawal-leaf-hash (buff 32))\n (sibling-hashes (list 50 {\n hash: (buff 32),\n is-left-side: bool,\n }))\n )\n (begin\n ;; Check that the user submitted a valid withdrawal root\n (asserts! (is-some (map-get? withdrawal-roots-map withdrawal-root)) (err ERR_INVALID_MERKLE_ROOT))\n\n ;; Check that this withdrawal leaf has not been processed before\n (asserts!\n (is-none\n (map-get? processed-withdrawal-leaves-map\n { withdrawal-leaf-hash: withdrawal-leaf-hash, withdrawal-root-hash: withdrawal-root }))\n (err ERR_WITHDRAWAL_ALREADY_PROCESSED))\n\n (let ((calculated-withdrawal-root (fold hash-help sibling-hashes withdrawal-leaf-hash))\n (roots-match (is-eq calculated-withdrawal-root withdrawal-root)))\n (if roots-match\n (ok true)\n (err ERR_MERKLE_ROOT_DOES_NOT_MATCH))\n )\n )\n)\n\n;; This function should be called after the asset in question has been transferred.\n;; It adds the withdrawal leaf hash to a map of processed leaves. This ensures that\n;; this withdrawal leaf can't be used again to withdraw additional funds.\n;; Returns bool\n(define-private (finish-withdraw\n (withdraw-info {\n withdrawal-leaf-hash: (buff 32),\n withdrawal-root-hash: (buff 32)\n })\n )\n (map-insert processed-withdrawal-leaves-map withdraw-info true)\n)\n", + "contract_identifier": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1" + }, + "type": "ContractDeployment" + }, + "nonce": 33, + "position": { + "index": 1 + }, + "contract_abi": "some abi data", + "proof": null, + "raw_tx": "0x808000000004003e44ad50f99edc5e6cc5543b636284186894a008000000000000002100000000000b71b00000a84691e27fd2d46475230707a657ef7ddf6de7a4b06a1ac2049384c6474e73f85ee4ce205e0904160adaa160498df02db1782f898b5905db4d249b7025f6604c030100000000060218666169722d616d6172616e74682d7268696e6f6365726f73000005103b3b2068656c6c6f2d776f726c6420636f6e74726163740a0a28646566696e652d636f6e7374616e742073656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b51394836445052290a28646566696e652d636f6e7374616e7420726563697069656e742027534d324a365a593438475631455a35563256355242394d5036365357383650594b4b51565838583047290a0a28646566696e652d66756e6769626c652d746f6b656e206e6f76656c2d746f6b656e2d3139290a28626567696e202866742d6d696e743f206e6f76656c2d746f6b656e2d3139207531322073656e64657229290a28626567696e202866742d7472616e736665723f206e6f76656c2d746f6b656e2d31392075322073656e64657220726563697069656e7429290a0a28646566696e652d6e6f6e2d66756e6769626c652d746f6b656e2068656c6c6f2d6e66742075696e74290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075312073656e64657229290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075322073656e64657229290a28626567696e20286e66742d7472616e736665723f2068656c6c6f2d6e66742075312073656e64657220726563697069656e7429290a0a28646566696e652d7075626c69632028746573742d656d69742d6576656e74290a2020202028626567696e0a2020202020202020287072696e7420224576656e74212048656c6c6f20776f726c6422290a2020202020202020286f6b2075312929290a28626567696e2028746573742d656d69742d6576656e7429290a0a28646566696e652d7075626c69632028746573742d6576656e742d7479706573290a2020202028626567696e0a202020202020202028756e777261702d70616e6963202866742d6d696e743f206e6f76656c2d746f6b656e2d313920753320726563697069656e7429290a202020202020202028756e777261702d70616e696320286e66742d6d696e743f2068656c6c6f2d6e667420753220726563697069656e7429290a202020202020202028756e777261702d70616e696320287374782d7472616e736665723f207536302074782d73656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b5139483644505229290a202020202020202028756e777261702d70616e696320287374782d6275726e3f207532302074782d73656e64657229290a2020202020202020286f6b2075312929290a0a28646566696e652d6d61702073746f7265207b6b65793a202862756666203332297d207b76616c75653a202862756666203332297d290a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a2020202028626567696e0a2020202020202020286d6174636820286d61702d6765743f2073746f7265207b6b65793a206b65797d290a202020202020202020202020656e74727920286f6b20286765742076616c756520656e74727929290a202020202020202020202020286572722030292929290a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a2020202028626567696e0a2020202020202020286d61702d7365742073746f7265207b6b65793a206b65797d207b76616c75653a2076616c75657d290a2020202020202020286f6b207531292929", + "receipt": { + "contract_calls_stack": [], + "events": [], + "mutated_assets_radius": [], + "mutated_contracts_radius": [ + "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1" + ] + }, + "result": "(ok true)", + "sender": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9", + "success": true + }, + "operations": [], + "transaction_identifier": { + "hash": "0x93c89ffdac77ed2ba52611563bd491f56f5d558e23d311a105663ae32bdf18e5" + } +} diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index 6b9d3b9d3..0365b3544 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -14,7 +14,7 @@ use super::{ StacksTrait, }, }; -use crate::utils::Context; +use crate::{chainhooks::stacks::serialize_stacks_payload_to_json, utils::Context}; use crate::{ chainhooks::{ tests::fixtures::{get_expected_occurrence, get_test_event_by_type}, @@ -449,6 +449,108 @@ fn test_stacks_predicate_contract_deploy(predicate: StacksPredicate, expected_ap } } +#[test] +fn verify_optional_addition_of_contract_abi() { + // "mine" two blocks + // - one contract deploy (which should have a contract abi) and + // - one contract call (which should not) + let new_blocks = vec![ + StacksBlockUpdate { + block: fixtures::build_stacks_testnet_block_with_contract_deployment(), + parent_microblocks_to_apply: vec![], + parent_microblocks_to_rollback: vec![], + }, + StacksBlockUpdate { + block: fixtures::build_stacks_testnet_block_with_contract_call(), + parent_microblocks_to_apply: vec![], + parent_microblocks_to_rollback: vec![], + }, + ]; + let event: StacksChainEvent = + StacksChainEvent::ChainUpdatedWithBlocks(StacksChainUpdatedWithBlocksData { + new_blocks, + confirmed_blocks: vec![], + }); + let mut contract_deploy_chainhook = StacksChainhookSpecification { + uuid: "contract-deploy".to_string(), + owner_uuid: None, + name: "".to_string(), + network: StacksNetwork::Testnet, + version: 1, + blocks: None, + start_block: None, + end_block: None, + expire_after_occurrence: None, + capture_all_events: None, + decode_clarity_values: None, + include_contract_abi: false, + predicate: StacksPredicate::ContractDeployment( + StacksContractDeploymentPredicate::Deployer("*".to_string()), + ), + action: HookAction::Noop, + enabled: true, + }; + let contract_call_chainhook = StacksChainhookSpecification { + uuid: "contract-call".to_string(), + owner_uuid: None, + name: "".to_string(), + network: StacksNetwork::Testnet, + version: 1, + blocks: None, + start_block: None, + end_block: None, + expire_after_occurrence: None, + capture_all_events: None, + decode_clarity_values: None, + include_contract_abi: true, + predicate: StacksPredicate::ContractCall(StacksContractCallBasedPredicate { + contract_identifier: "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1".to_string(), + method: "commit-block".to_string(), + }), + action: HookAction::Noop, + enabled: true, + }; + let combinations = vec![ + (false, None), + (true, None), + (false, Some(true)), + (true, Some(true)), + ]; + // assert that for any combination of `include_contract_abi` and `decode_clarity_values` + // fields on the contract deployment chainhook, we only include the abi if the + // `include_contract_abi` field is `true`. + // Also assert that contract call transactions _never_ include an abi + for (include_abi, decode_cv) in combinations { + contract_deploy_chainhook.include_contract_abi = include_abi; + contract_deploy_chainhook.decode_clarity_values = decode_cv; + + let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; + let (triggered, _blocks) = + evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); + assert_eq!(triggered.len(), 2); + + for t in triggered.into_iter() { + let result = serialize_stacks_payload_to_json(t, &HashMap::new(), &Context::empty()); + let result = result.as_object().unwrap(); + let uuid = result.get("chainhook").unwrap().get("uuid").unwrap(); + let apply_blocks = result.get("apply").unwrap(); + for block in apply_blocks.as_array().unwrap() { + let transactions = block.get("transactions").unwrap(); + for transaction in transactions.as_array().unwrap() { + let contract_abi = transaction.get("metadata").unwrap().get("contract_abi"); + if uuid == "contract-call" || (uuid == "contract-deploy" && !include_abi) { + assert_eq!(contract_abi, None); + } else if uuid == "contract-deploy" && include_abi { + assert!(contract_abi.is_some()) + } else { + unreachable!() + } + } + } + } + } +} + #[test_case( StacksPredicate::ContractCall(StacksContractCallBasedPredicate { contract_identifier: "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1".to_string(), From 8820a5f42f06be00ee4ebeeb900042024f67578a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 17 Aug 2023 09:28:39 -0400 Subject: [PATCH 07/12] add todo --- components/chainhook-sdk/src/chainhooks/stacks/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 9ee461ad5..500c270f8 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -775,7 +775,7 @@ pub fn serialize_stacks_payload_to_json<'a>( "parent_block_identifier": block.get_parent_identifier(), "timestamp": block.get_timestamp(), "transactions": transactions.into_iter().map(|transaction| { - let mut transaction = transaction.clone(); + let mut transaction = transaction.clone(); // todo: this clone is pretty expensive, maybe there's a more performant way to do this if transaction.metadata.contract_abi.is_some() && !trigger.chainhook.include_contract_abi { transaction.metadata.contract_abi = None; } From 0d530e69975696859e9f957ef565b62c28a1dbd5 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Sun, 20 Aug 2023 14:47:52 -0400 Subject: [PATCH 08/12] simplify stacks block serialization --- .../src/chainhooks/stacks/mod.rs | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 500c270f8..542563dd5 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -508,8 +508,28 @@ pub fn evaluate_stacks_predicate_on_transaction<'a>( } } -fn encode_transaction_including_with_clarity_decoding( +fn serialize_stacks_block( + block: &dyn AbstractStacksBlock, + transactions: Vec<&StacksTransactionData>, + decode_clarity_values: bool, + include_contract_abi: bool, + ctx: &Context, +) -> serde_json::Value { + json!({ + "block_identifier": block.get_identifier(), + "parent_block_identifier": block.get_parent_identifier(), + "timestamp": block.get_timestamp(), + "transactions": transactions.into_iter().map(|transaction| { + serialize_stacks_transaction(&transaction, decode_clarity_values, include_contract_abi, ctx) + }).collect::>(), + "metadata": block.get_serialized_metadata(), + }) +} + +fn serialize_stacks_transaction( transaction: &StacksTransactionData, + decode_clarity_values: bool, + include_contract_abi: bool, ctx: &Context, ) -> serde_json::Value { let mut json = json!({ @@ -518,7 +538,11 @@ fn encode_transaction_including_with_clarity_decoding( "metadata": { "success": transaction.metadata.success, "raw_tx": transaction.metadata.raw_tx, - "result": serialized_decoded_clarity_value(&transaction.metadata.result, ctx), + "result": if decode_clarity_values { + serialized_decoded_clarity_value(&transaction.metadata.result, ctx) + } else { + json!(transaction.metadata.result) + }, "sender": transaction.metadata.sender, "fee": transaction.metadata.fee, "kind": transaction.metadata.kind, @@ -527,7 +551,7 @@ fn encode_transaction_including_with_clarity_decoding( "mutated_assets_radius": transaction.metadata.receipt.mutated_assets_radius, "contract_calls_stack": transaction.metadata.receipt.contract_calls_stack, "events": transaction.metadata.receipt.events.iter().map(|event| { - serialized_event_with_decoded_clarity_value(event, ctx) + if decode_clarity_values { serialized_event_with_decoded_clarity_value(event, ctx) } else { json!(event) } }).collect::>(), }, "description": transaction.metadata.description, @@ -536,8 +560,10 @@ fn encode_transaction_including_with_clarity_decoding( "position": transaction.metadata.position }, }); - if let Some(abi) = &transaction.metadata.contract_abi { - json["metadata"]["contract_abi"] = abi.to_owned(); + if include_contract_abi { + if let Some(abi) = &transaction.metadata.contract_abi { + json["metadata"]["contract_abi"] = json!(abi); + } } json } @@ -768,45 +794,13 @@ pub fn serialize_stacks_payload_to_json<'a>( ctx: &Context, ) -> JsonValue { let decode_clarity_values = trigger.should_decode_clarity_value(); + let include_contract_abi = trigger.chainhook.include_contract_abi; json!({ "apply": trigger.apply.into_iter().map(|(transactions, block)| { - json!({ - "block_identifier": block.get_identifier(), - "parent_block_identifier": block.get_parent_identifier(), - "timestamp": block.get_timestamp(), - "transactions": transactions.into_iter().map(|transaction| { - let mut transaction = transaction.clone(); // todo: this clone is pretty expensive, maybe there's a more performant way to do this - if transaction.metadata.contract_abi.is_some() && !trigger.chainhook.include_contract_abi { - transaction.metadata.contract_abi = None; - } - if decode_clarity_values { - encode_transaction_including_with_clarity_decoding(&transaction, ctx) - } else { - json!(transaction) - } - }).collect::>(), - "metadata": block.get_serialized_metadata(), - }) + serialize_stacks_block(block, transactions, decode_clarity_values, include_contract_abi, ctx) }).collect::>(), "rollback": trigger.rollback.into_iter().map(|(transactions, block)| { - json!({ - "block_identifier": block.get_identifier(), - "parent_block_identifier": block.get_parent_identifier(), - "timestamp": block.get_timestamp(), - "transactions": transactions.into_iter().map(|transaction| { - let mut transaction = transaction.clone(); - if transaction.metadata.contract_abi.is_some() && !trigger.chainhook.include_contract_abi { - transaction.metadata.contract_abi = None; - } - if decode_clarity_values { - encode_transaction_including_with_clarity_decoding(&transaction, ctx) - } else { - json!(transaction) - } - }).collect::>(), - "metadata": block.get_serialized_metadata(), - // "proof": proofs.get(&transaction.transaction_identifier), - }) + serialize_stacks_block(block, transactions, decode_clarity_values, include_contract_abi, ctx) }).collect::>(), "chainhook": { "uuid": trigger.chainhook.uuid, From 8856e7585b936b3d7ad2a279f1737b32f9b8725a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Sun, 20 Aug 2023 14:55:34 -0400 Subject: [PATCH 09/12] simplify test --- .../chainhook-sdk/src/chainhooks/tests/mod.rs | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index 0365b3544..6dea5279c 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -483,7 +483,7 @@ fn verify_optional_addition_of_contract_abi() { expire_after_occurrence: None, capture_all_events: None, decode_clarity_values: None, - include_contract_abi: false, + include_contract_abi: true, predicate: StacksPredicate::ContractDeployment( StacksContractDeploymentPredicate::Deployer("*".to_string()), ), @@ -510,45 +510,49 @@ fn verify_optional_addition_of_contract_abi() { action: HookAction::Noop, enabled: true, }; - let combinations = vec![ - (false, None), - (true, None), - (false, Some(true)), - (true, Some(true)), - ]; - // assert that for any combination of `include_contract_abi` and `decode_clarity_values` - // fields on the contract deployment chainhook, we only include the abi if the - // `include_contract_abi` field is `true`. - // Also assert that contract call transactions _never_ include an abi - for (include_abi, decode_cv) in combinations { - contract_deploy_chainhook.include_contract_abi = include_abi; - contract_deploy_chainhook.decode_clarity_values = decode_cv; - let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; - let (triggered, _blocks) = - evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); - assert_eq!(triggered.len(), 2); + let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; + let (triggered, _blocks) = + evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); + assert_eq!(triggered.len(), 2); - for t in triggered.into_iter() { - let result = serialize_stacks_payload_to_json(t, &HashMap::new(), &Context::empty()); - let result = result.as_object().unwrap(); - let uuid = result.get("chainhook").unwrap().get("uuid").unwrap(); - let apply_blocks = result.get("apply").unwrap(); - for block in apply_blocks.as_array().unwrap() { - let transactions = block.get("transactions").unwrap(); - for transaction in transactions.as_array().unwrap() { - let contract_abi = transaction.get("metadata").unwrap().get("contract_abi"); - if uuid == "contract-call" || (uuid == "contract-deploy" && !include_abi) { - assert_eq!(contract_abi, None); - } else if uuid == "contract-deploy" && include_abi { - assert!(contract_abi.is_some()) - } else { - unreachable!() - } + for t in triggered.into_iter() { + let result = serialize_stacks_payload_to_json(t, &HashMap::new(), &Context::empty()); + let result = result.as_object().unwrap(); + let uuid = result.get("chainhook").unwrap().get("uuid").unwrap(); + let apply_blocks = result.get("apply").unwrap(); + for block in apply_blocks.as_array().unwrap() { + let transactions = block.get("transactions").unwrap(); + for transaction in transactions.as_array().unwrap() { + let contract_abi = transaction.get("metadata").unwrap().get("contract_abi"); + if uuid == "contract-call" { + assert_eq!(contract_abi, None); + } else if uuid == "contract-deploy" { + assert!(contract_abi.is_some()) + } else { + unreachable!() } } } } + contract_deploy_chainhook.include_contract_abi = false; + let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; + let (triggered, _blocks) = + evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); + assert_eq!(triggered.len(), 2); + + for t in triggered.into_iter() { + let result = serialize_stacks_payload_to_json(t, &HashMap::new(), &Context::empty()); + let result = result.as_object().unwrap(); + let apply_blocks = result.get("apply").unwrap(); + for block in apply_blocks.as_array().unwrap() { + let transactions = block.get("transactions").unwrap(); + for transaction in transactions.as_array().unwrap() { + let contract_abi = transaction.get("metadata").unwrap().get("contract_abi"); + assert_eq!(contract_abi, None); + } + } + } } #[test_case( From 4ab225caf460d3ea0e09f1753240e8728dae2fa3 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Sun, 20 Aug 2023 15:09:35 -0400 Subject: [PATCH 10/12] use ContractInterface type for contract_abi --- Cargo.lock | 1 + .../base/transaction_contract_deploy.json | 40 ++++++++++++++++++- .../chainhook-sdk/src/indexer/stacks/mod.rs | 5 ++- components/chainhook-types-rs/Cargo.toml | 1 + components/chainhook-types-rs/src/rosetta.rs | 4 +- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 764fe81b8..6dc58a461 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,6 +489,7 @@ dependencies = [ name = "chainhook-types" version = "1.0.12" dependencies = [ + "clarity-vm", "hex", "schemars 0.8.12", "serde", diff --git a/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json b/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json index f6693dedc..6502f6428 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json +++ b/components/chainhook-sdk/src/chainhooks/tests/fixtures/stacks/testnet/base/transaction_contract_deploy.json @@ -20,7 +20,45 @@ "position": { "index": 1 }, - "contract_abi": "some abi data", + "contract_abi": { + "clarity_version": "Clarity2", + "epoch": "Epoch24", + "functions": [ + { + "access": "private", + "args": [{ "name": "tid", "type": "uint128" }], + "name": "airdrop", + "outputs": { "type": "bool" } + } + ], + "fungible_tokens": [{ "name": "MEME" }], + "maps": [ + { + "key": { + "tuple": [ + { + "name": "name", + "type": { "buffer": { "length": 48 } } + }, + { + "name": "namespace", + "type": { "buffer": { "length": 20 } } + } + ] + }, + "name": "map_claimed_bns_note", + "value": "bool" + } + ], + "non_fungible_tokens": [], + "variables": [ + { + "access": "constant", + "name": "AIRDROP_COUNT_PER_MEMBER", + "type": "uint128" + } + ] + }, "proof": null, "raw_tx": "0x808000000004003e44ad50f99edc5e6cc5543b636284186894a008000000000000002100000000000b71b00000a84691e27fd2d46475230707a657ef7ddf6de7a4b06a1ac2049384c6474e73f85ee4ce205e0904160adaa160498df02db1782f898b5905db4d249b7025f6604c030100000000060218666169722d616d6172616e74682d7268696e6f6365726f73000005103b3b2068656c6c6f2d776f726c6420636f6e74726163740a0a28646566696e652d636f6e7374616e742073656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b51394836445052290a28646566696e652d636f6e7374616e7420726563697069656e742027534d324a365a593438475631455a35563256355242394d5036365357383650594b4b51565838583047290a0a28646566696e652d66756e6769626c652d746f6b656e206e6f76656c2d746f6b656e2d3139290a28626567696e202866742d6d696e743f206e6f76656c2d746f6b656e2d3139207531322073656e64657229290a28626567696e202866742d7472616e736665723f206e6f76656c2d746f6b656e2d31392075322073656e64657220726563697069656e7429290a0a28646566696e652d6e6f6e2d66756e6769626c652d746f6b656e2068656c6c6f2d6e66742075696e74290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075312073656e64657229290a28626567696e20286e66742d6d696e743f2068656c6c6f2d6e66742075322073656e64657229290a28626567696e20286e66742d7472616e736665723f2068656c6c6f2d6e66742075312073656e64657220726563697069656e7429290a0a28646566696e652d7075626c69632028746573742d656d69742d6576656e74290a2020202028626567696e0a2020202020202020287072696e7420224576656e74212048656c6c6f20776f726c6422290a2020202020202020286f6b2075312929290a28626567696e2028746573742d656d69742d6576656e7429290a0a28646566696e652d7075626c69632028746573742d6576656e742d7479706573290a2020202028626567696e0a202020202020202028756e777261702d70616e6963202866742d6d696e743f206e6f76656c2d746f6b656e2d313920753320726563697069656e7429290a202020202020202028756e777261702d70616e696320286e66742d6d696e743f2068656c6c6f2d6e667420753220726563697069656e7429290a202020202020202028756e777261702d70616e696320287374782d7472616e736665723f207536302074782d73656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b5139483644505229290a202020202020202028756e777261702d70616e696320287374782d6275726e3f207532302074782d73656e64657229290a2020202020202020286f6b2075312929290a0a28646566696e652d6d61702073746f7265207b6b65793a202862756666203332297d207b76616c75653a202862756666203332297d290a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a2020202028626567696e0a2020202020202020286d6174636820286d61702d6765743f2073746f7265207b6b65793a206b65797d290a202020202020202020202020656e74727920286f6b20286765742076616c756520656e74727929290a202020202020202020202020286572722030292929290a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a2020202028626567696e0a2020202020202020286d61702d7365742073746f7265207b6b65793a206b65797d207b76616c75653a2076616c75657d290a2020202020202020286f6b207531292929", "receipt": { diff --git a/components/chainhook-sdk/src/indexer/stacks/mod.rs b/components/chainhook-sdk/src/indexer/stacks/mod.rs index 4225c1126..114b74efc 100644 --- a/components/chainhook-sdk/src/indexer/stacks/mod.rs +++ b/components/chainhook-sdk/src/indexer/stacks/mod.rs @@ -1,6 +1,7 @@ mod blocks_pool; pub use blocks_pool::StacksBlockPool; +use stacks_rpc_client::clarity::vm::analysis::contract_interface_builder::ContractInterface; use crate::chainhooks::stacks::try_decode_clarity_value; use crate::indexer::AssetClassCache; @@ -76,7 +77,7 @@ pub struct NewTransaction { pub raw_result: String, pub raw_tx: String, pub execution_cost: Option, - pub contract_abi: Option, + pub contract_abi: Option, } #[derive(Deserialize, Debug)] @@ -90,7 +91,7 @@ pub struct NewMicroblockTransaction { pub microblock_sequence: usize, pub microblock_hash: String, pub microblock_parent_hash: String, - pub contract_abi: Option, + pub contract_abi: Option, } #[derive(Debug, Deserialize, Serialize)] diff --git a/components/chainhook-types-rs/Cargo.toml b/components/chainhook-types-rs/Cargo.toml index de2f3263f..0f26e90b0 100644 --- a/components/chainhook-types-rs/Cargo.toml +++ b/components/chainhook-types-rs/Cargo.toml @@ -14,3 +14,4 @@ serde_derive = "1" strum = { version = "0.23.0", features = ["derive"] } schemars = { version = "0.8.10", git = "https://github.com/hirosystems/schemars.git", branch = "feat-chainhook-fixes" } hex = "0.4.3" +clarity-vm = "2.1.1" diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index 479d2279a..605c5bc1d 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -1,7 +1,7 @@ use super::bitcoin::{TxIn, TxOut}; use crate::events::*; +use clarity::vm::analysis::contract_interface_builder::ContractInterface; use schemars::JsonSchema; -use serde_json::Value; use std::cmp::Ordering; use std::collections::HashSet; use std::fmt::Display; @@ -221,7 +221,7 @@ pub struct StacksTransactionMetadata { pub position: StacksTransactionPosition, pub proof: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub contract_abi: Option, + pub contract_abi: Option, } /// TODO From a89c95aa52edc725fd77c7a3fe94b2f5bb64d525 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Sun, 3 Sep 2023 12:57:40 -0400 Subject: [PATCH 11/12] remove clarity-vm dep; copy ContractIdentifier type --- Cargo.lock | 1 - .../chainhook-sdk/src/indexer/stacks/mod.rs | 1 - components/chainhook-types-rs/Cargo.toml | 1 - .../src/contract_interface.rs | 171 ++++++++++++++++++ components/chainhook-types-rs/src/lib.rs | 2 + components/chainhook-types-rs/src/rosetta.rs | 2 +- 6 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 components/chainhook-types-rs/src/contract_interface.rs diff --git a/Cargo.lock b/Cargo.lock index 6dc58a461..764fe81b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,7 +489,6 @@ dependencies = [ name = "chainhook-types" version = "1.0.12" dependencies = [ - "clarity-vm", "hex", "schemars 0.8.12", "serde", diff --git a/components/chainhook-sdk/src/indexer/stacks/mod.rs b/components/chainhook-sdk/src/indexer/stacks/mod.rs index 114b74efc..49025e621 100644 --- a/components/chainhook-sdk/src/indexer/stacks/mod.rs +++ b/components/chainhook-sdk/src/indexer/stacks/mod.rs @@ -1,7 +1,6 @@ mod blocks_pool; pub use blocks_pool::StacksBlockPool; -use stacks_rpc_client::clarity::vm::analysis::contract_interface_builder::ContractInterface; use crate::chainhooks::stacks::try_decode_clarity_value; use crate::indexer::AssetClassCache; diff --git a/components/chainhook-types-rs/Cargo.toml b/components/chainhook-types-rs/Cargo.toml index 0f26e90b0..de2f3263f 100644 --- a/components/chainhook-types-rs/Cargo.toml +++ b/components/chainhook-types-rs/Cargo.toml @@ -14,4 +14,3 @@ serde_derive = "1" strum = { version = "0.23.0", features = ["derive"] } schemars = { version = "0.8.10", git = "https://github.com/hirosystems/schemars.git", branch = "feat-chainhook-fixes" } hex = "0.4.3" -clarity-vm = "2.1.1" diff --git a/components/chainhook-types-rs/src/contract_interface.rs b/components/chainhook-types-rs/src/contract_interface.rs new file mode 100644 index 000000000..07aeb827b --- /dev/null +++ b/components/chainhook-types-rs/src/contract_interface.rs @@ -0,0 +1,171 @@ +// NOTE: This module is a very slightly simplified version of the +// `clarity-vm` repository's [ContractInterface](https://github.com/stacks-network/stacks-blockchain/blob/eca1cfe81f0c0989ebd3e53c32e3e5d70ed83757/clarity/src/vm/analysis/contract_interface_builder/mod.rs#L368) type. +// We've copied it here rather than using `clarity-vm` as a dependency to avoid circular dependencies. + +use std::{fmt, str::FromStr}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterface { + pub functions: Vec, + pub variables: Vec, + pub maps: Vec, + pub fungible_tokens: Vec, + pub non_fungible_tokens: Vec, + pub epoch: String, + pub clarity_version: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceFunction { + pub name: String, + pub access: ContractInterfaceFunctionAccess, + pub args: Vec, + pub outputs: ContractInterfaceFunctionOutput, +} +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ContractInterfaceFunctionAccess { + private, + public, + read_only, +} +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ContractInterfaceVariableAccess { + constant, + variable, +} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceFunctionArg { + pub name: String, + #[serde(rename = "type")] + pub type_f: ContractInterfaceAtomType, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceFunctionOutput { + #[serde(rename = "type")] + pub type_f: ContractInterfaceAtomType, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceVariable { + pub name: String, + #[serde(rename = "type")] + pub type_f: ContractInterfaceAtomType, + pub access: ContractInterfaceVariableAccess, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceMap { + pub name: String, + pub key: ContractInterfaceAtomType, + pub value: ContractInterfaceAtomType, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ContractInterfaceAtomType { + none, + int128, + uint128, + bool, + principal, + buffer { + length: u32, + }, + #[serde(rename = "string-utf8")] + string_utf8 { + length: u32, + }, + #[serde(rename = "string-ascii")] + string_ascii { + length: u32, + }, + tuple(Vec), + optional(Box), + response { + ok: Box, + error: Box, + }, + list { + #[serde(rename = "type")] + type_f: Box, + length: u32, + }, + trait_reference, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceTupleEntryType { + pub name: String, + #[serde(rename = "type")] + pub type_f: ContractInterfaceAtomType, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceFungibleTokens { + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContractInterfaceNonFungibleTokens { + pub name: String, + #[serde(rename = "type")] + pub type_f: ContractInterfaceAtomType, +} +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, PartialOrd)] +pub enum ClarityVersion { + Clarity1, + Clarity2, +} + +impl fmt::Display for ClarityVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ClarityVersion::Clarity1 => write!(f, "Clarity 1"), + ClarityVersion::Clarity2 => write!(f, "Clarity 2"), + } + } +} + +impl FromStr for ClarityVersion { + type Err = String; + fn from_str(version: &str) -> Result { + let s = version.to_string().to_lowercase(); + if s == "clarity1" { + Ok(ClarityVersion::Clarity1) + } else if s == "clarity2" { + Ok(ClarityVersion::Clarity2) + } else { + Err(format!( + "Invalid clarity version. Valid versions are: Clarity1, Clarity2." + )) + } + } +} +#[repr(u32)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy, Serialize, Deserialize)] +pub enum StacksEpochId { + Epoch10 = 0x01000, + Epoch20 = 0x02000, + Epoch2_05 = 0x02005, + Epoch21 = 0x0200a, + Epoch22 = 0x0200f, + Epoch23 = 0x02014, + Epoch24 = 0x02019, +} + +impl std::fmt::Display for StacksEpochId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StacksEpochId::Epoch10 => write!(f, "1.0"), + StacksEpochId::Epoch20 => write!(f, "2.0"), + StacksEpochId::Epoch2_05 => write!(f, "2.05"), + StacksEpochId::Epoch21 => write!(f, "2.1"), + StacksEpochId::Epoch22 => write!(f, "2.2"), + StacksEpochId::Epoch23 => write!(f, "2.3"), + StacksEpochId::Epoch24 => write!(f, "2.4"), + } + } +} diff --git a/components/chainhook-types-rs/src/lib.rs b/components/chainhook-types-rs/src/lib.rs index b4baf2a3d..cd8ef7b02 100644 --- a/components/chainhook-types-rs/src/lib.rs +++ b/components/chainhook-types-rs/src/lib.rs @@ -4,10 +4,12 @@ extern crate serde; extern crate serde_derive; pub mod bitcoin; +mod contract_interface; mod events; mod processors; mod rosetta; +pub use contract_interface::*; pub use events::*; pub use processors::*; pub use rosetta::*; diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index 605c5bc1d..543994e17 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -1,6 +1,6 @@ use super::bitcoin::{TxIn, TxOut}; +use crate::contract_interface::ContractInterface; use crate::events::*; -use clarity::vm::analysis::contract_interface_builder::ContractInterface; use schemars::JsonSchema; use std::cmp::Ordering; use std::collections::HashSet; From f367e222aea613c27eea6538af93820fe4c601d8 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 18 Sep 2023 11:53:34 -0400 Subject: [PATCH 12/12] fix tests from rebase --- components/chainhook-cli/src/service/tests/helpers.rs | 1 + components/chainhook-sdk/src/chainhooks/tests/mod.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/chainhook-cli/src/service/tests/helpers.rs b/components/chainhook-cli/src/service/tests/helpers.rs index d42b34788..8f5b022fd 100644 --- a/components/chainhook-cli/src/service/tests/helpers.rs +++ b/components/chainhook-cli/src/service/tests/helpers.rs @@ -11,6 +11,7 @@ fn create_stacks_tsv_transaction(index: u64) -> NewTransaction { raw_result: format!("0x0703"), raw_tx: format!("0x00000000010400e2cd0871da5bdd38c4d5569493dc3b14aac4e0a10000000000000019000000000000000000008373b16e4a6f9d87864c314dd77bbd8b27a2b1805e96ec5a6509e7e4f833cd6a7bdb2462c95f6968a867ab6b0e8f0a6498e600dbc46cfe9f84c79709da7b9637010200000000040000000000000000000000000000000000000000000000000000000000000000"), execution_cost: None, + contract_abi: None } } diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index 6dea5279c..6fb06e65a 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -489,6 +489,7 @@ fn verify_optional_addition_of_contract_abi() { ), action: HookAction::Noop, enabled: true, + expired_at: None, }; let contract_call_chainhook = StacksChainhookSpecification { uuid: "contract-call".to_string(), @@ -509,10 +510,11 @@ fn verify_optional_addition_of_contract_abi() { }), action: HookAction::Noop, enabled: true, + expired_at: None, }; let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; - let (triggered, _blocks) = + let (triggered, _blocks, _) = evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); assert_eq!(triggered.len(), 2); @@ -537,7 +539,7 @@ fn verify_optional_addition_of_contract_abi() { } contract_deploy_chainhook.include_contract_abi = false; let predicates = vec![&contract_deploy_chainhook, &contract_call_chainhook]; - let (triggered, _blocks) = + let (triggered, _blocks, _) = evaluate_stacks_chainhooks_on_chain_event(&event, predicates, &Context::empty()); assert_eq!(triggered.len(), 2);