Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: include contract_abi in stacks transaction #378

Merged
merged 13 commits into from
Sep 18, 2023
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions components/chainhook-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand All @@ -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()
})
Expand Down
2 changes: 1 addition & 1 deletion components/chainhook-cli/src/scan/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ fn create_stacks_new_transaction(index: u64) -> NewTransaction {
raw_result: format!("0x0703"),
raw_tx: format!("0x00000000010400e2cd0871da5bdd38c4d5569493dc3b14aac4e0a10000000000000019000000000000000000008373b16e4a6f9d87864c314dd77bbd8b27a2b1805e96ec5a6509e7e4f833cd6a7bdb2462c95f6968a867ab6b0e8f0a6498e600dbc46cfe9f84c79709da7b9637010200000000040000000000000000000000000000000000000000000000000000000000000000"),
execution_cost: None,
contract_abi: None
}
}

Expand Down
72 changes: 39 additions & 33 deletions components/chainhook-sdk/src/chainhooks/stacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,17 +508,41 @@ pub fn evaluate_stacks_predicate_on_transaction<'a>(
}
}

fn encode_transaction_including_with_clarity_decoding(
transaction: &StacksTransactionData,
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::<Vec<_>>(),
"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!({
"transaction_identifier": transaction.transaction_identifier,
"operations": transaction.operations,
"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,
Expand All @@ -527,15 +551,21 @@ 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::<Vec<serde_json::Value>>(),
},
"description": transaction.metadata.description,
"sponsor": transaction.metadata.sponsor,
"execution_cost": transaction.metadata.execution_cost,
"position": transaction.metadata.position,
"position": transaction.metadata.position
},
})
});
if include_contract_abi {
if let Some(abi) = &transaction.metadata.contract_abi {
json["metadata"]["contract_abi"] = json!(abi);
}
}
json
}

pub fn serialized_event_with_decoded_clarity_value(
Expand Down Expand Up @@ -764,37 +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.iter().map(|transaction| {
if decode_clarity_values {
encode_transaction_including_with_clarity_decoding(transaction, ctx)
} else {
json!(transaction)
}
}).collect::<Vec<_>>(),
"metadata": block.get_serialized_metadata(),
})
serialize_stacks_block(block, transactions, decode_clarity_values, include_contract_abi, ctx)
}).collect::<Vec<_>>(),
"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.iter().map(|transaction| {
if decode_clarity_values {
encode_transaction_including_with_clarity_decoding(transaction, ctx)
} else {
json!(transaction)
}
}).collect::<Vec<_>>(),
"metadata": block.get_serialized_metadata(),
// "proof": proofs.get(&transaction.transaction_identifier),
})
serialize_stacks_block(block, transactions, decode_clarity_values, include_contract_abi, ctx)
}).collect::<Vec<_>>(),
"chainhook": {
"uuid": trigger.chainhook.uuid,
Expand Down

Large diffs are not rendered by default.

115 changes: 114 additions & 1 deletion components/chainhook-sdk/src/chainhooks/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -447,6 +449,114 @@ 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: true,
predicate: StacksPredicate::ContractDeployment(
StacksContractDeploymentPredicate::Deployer("*".to_string()),
),
action: HookAction::Noop,
enabled: true,
expired_at: None,
};
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,
expired_at: None,
};

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" {
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(
StacksPredicate::ContractCall(StacksContractCallBasedPredicate {
contract_identifier: "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1".to_string(),
Expand Down Expand Up @@ -512,6 +622,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,
Expand Down Expand Up @@ -546,6 +657,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(),
)),
Expand Down Expand Up @@ -603,6 +715,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(),
)),
Expand Down
4 changes: 4 additions & 0 deletions components/chainhook-sdk/src/chainhooks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -432,6 +433,8 @@ pub struct StacksChainhookNetworkSpecification {
pub capture_all_events: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decode_clarity_values: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_contract_abi: Option<bool>,
#[serde(rename = "if_this")]
pub predicate: StacksPredicate,
#[serde(rename = "then_that")]
Expand Down Expand Up @@ -732,6 +735,7 @@ pub struct StacksChainhookSpecification {
pub capture_all_events: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decode_clarity_values: Option<bool>,
pub include_contract_abi: bool,
#[serde(rename = "predicate")]
pub predicate: StacksPredicate,
pub action: HookAction,
Expand Down
4 changes: 4 additions & 0 deletions components/chainhook-sdk/src/indexer/stacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub struct NewTransaction {
pub raw_result: String,
pub raw_tx: String,
pub execution_cost: Option<StacksTransactionExecutionCost>,
pub contract_abi: Option<ContractInterface>,
}

#[derive(Deserialize, Debug)]
Expand All @@ -89,6 +90,7 @@ pub struct NewMicroblockTransaction {
pub microblock_sequence: usize,
pub microblock_hash: String,
pub microblock_parent_hash: String,
pub contract_abi: Option<ContractInterface>,
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -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(),
},
});
}
Expand Down Expand Up @@ -452,6 +455,7 @@ pub fn standardize_stacks_microblock_trail(
tx.tx_index,
),
proof: None,
contract_abi: tx.contract_abi.clone(),
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
}
Expand Down
1 change: 1 addition & 0 deletions components/chainhook-sdk/src/observer/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading