ccnext_query_builder/
test_helpers.rs

1use alloy::consensus::Transaction as _;
2use alloy::eips::eip2930::AccessList;
3use alloy::rpc::types::eth::transaction::Transaction;
4use alloy::{
5    primitives::{Address, B256, U256},
6    providers::{Provider, ProviderBuilder},
7    rpc::types::TransactionReceipt,
8};
9use async_trait::async_trait;
10use ccnext_abi_encoding::common::{compute_v, compute_y_parity};
11use std::str::FromStr;
12
13use crate::abi::models::QueryBuilderError;
14use crate::abi::query_builder::AbiProvider;
15
16pub enum ResultField {
17    TxType(u8),
18    TxChainId(u64),
19    TxNonce(u64),
20    TxGasPrice(u128),
21    TxMaxPriorityFeePerGas(u128),
22    TxMaxFeePerGas(u128),
23    TxGasLimit(u64),
24    EthAddress(Address),
25    TxValue(U256),
26    TxAccessList(AccessList),
27    TxV(U256),
28    TxYParity(u8),
29    TxR([u8; 32]),
30    TxS([u8; 32]),
31    RxStatus(u8),
32    RxGasUsed(u64),
33    RxLogBlooms(Vec<u8>),
34    EventTopic([u8; 32]),
35    EventDataField([u8; 32]),
36    FunctionSignifier([u8; 4]),
37    CallDataField([u8; 32]),
38}
39
40pub async fn get_transaction_and_receipt(tx_hash_str: &str) -> (Transaction, TransactionReceipt) {
41    // RPC.
42    let rpc_url = "https://sepolia-proxy-rpc.creditcoin.network";
43    let provider = ProviderBuilder::new().on_http(rpc_url.parse().unwrap());
44
45    // which transaction.
46    let tx_hash = B256::from_str(tx_hash_str).unwrap();
47
48    // get the transaction & receipt.
49    let tx = provider
50        .get_transaction_by_hash(tx_hash)
51        .await
52        .unwrap()
53        .unwrap();
54    let rx = provider
55        .get_transaction_receipt(tx_hash)
56        .await
57        .unwrap()
58        .unwrap();
59    (tx, rx)
60}
61
62pub fn check_results(
63    expected_results: Vec<ResultField>,
64    result_segments: Vec<(usize, usize)>,
65    abi: Vec<u8>,
66) {
67    assert_eq!(expected_results.len(), result_segments.len(), "Number of expected results doesn't match segment count. Expected results: {}, Result segments: {}", expected_results.len(), result_segments.len());
68
69    for (field_number, (expected, (offset, size))) in
70        expected_results.iter().zip(result_segments).enumerate()
71    {
72        // Pad expected result according to type
73        let expected_padded: Vec<u8> = match expected {
74            // All cases where 1 byte is padded to 32 bytes
75            ResultField::TxType(value)
76            | ResultField::TxYParity(value)
77            | ResultField::RxStatus(value) => {
78                let mut value_padded: Vec<u8> = vec![0; 31];
79                value_padded.push(*value);
80                value_padded
81            }
82            // All cases where 8 bytes are padded to 32 bytes
83            ResultField::RxGasUsed(value)
84            | ResultField::TxNonce(value)
85            | ResultField::TxChainId(value)
86            | ResultField::TxGasLimit(value) => {
87                let mut value_padded: Vec<u8> = vec![0; 24];
88                value_padded.append(&mut Vec::from(value.to_be_bytes()));
89                value_padded
90            }
91            // All cases where 16 bytes are padded to 32 bytes
92            ResultField::TxGasPrice(value)
93            | ResultField::TxMaxPriorityFeePerGas(value)
94            | ResultField::TxMaxFeePerGas(value) => {
95                let mut value_padded: Vec<u8> = vec![0; 16];
96                value_padded.append(&mut Vec::from(value.to_be_bytes()));
97                value_padded
98            }
99            ResultField::EthAddress(address) => {
100                let mut address_padded: Vec<u8> = vec![0; 12];
101                address_padded.append(&mut Vec::from(address.0 .0));
102                address_padded
103            }
104            ResultField::TxValue(value) => value.to_be_bytes_vec(),
105            ResultField::TxAccessList(_list) => {
106                //TODO: figure out how access list should look when encoded
107                vec![]
108            }
109            ResultField::TxV(v) => v.to_be_bytes_vec(),
110            ResultField::TxR(r) => Vec::from(r),
111            ResultField::TxS(s) => Vec::from(s),
112            ResultField::RxLogBlooms(blooms) => blooms.clone(),
113            ResultField::EventTopic(topic) => Vec::from(topic),
114            ResultField::EventDataField(field) => Vec::from(field),
115            ResultField::FunctionSignifier(signifier) => Vec::from(signifier),
116            ResultField::CallDataField(field) => Vec::from(field),
117        };
118        // Get segment bytes from abi
119        let segment_bytes = &abi[offset..offset + size];
120        // Compare, printing which field number and type on error.
121        // TODO: Possibly allow field names to be added to ResultFields, just to make errors more intelligible.
122        assert_eq!(
123            &expected_padded, segment_bytes,
124            "Expected and actual didn't match. Field num: {field_number}, Expected: {expected_padded:?}, Actual: {segment_bytes:?}",
125        );
126    }
127}
128
129pub fn get_vrs(tx: &Transaction) -> (U256, [u8; 32], [u8; 32]) {
130    let signed_tx = tx
131        .inner
132        .as_legacy()
133        .expect("Already checked that tx is legacy");
134    let signature = signed_tx.signature();
135    let chain_id = tx.chain_id();
136    let v = compute_v(signature, chain_id);
137    let r: [u8; 32] = signature.r().to_be_bytes::<32>()[0..32].try_into().unwrap();
138    let s: [u8; 32] = signature.s().to_be_bytes::<32>()[0..32].try_into().unwrap();
139    (v, r, s)
140}
141
142pub fn get_y_parity(tx: &Transaction) -> u8 {
143    let signature = tx.inner.signature();
144    compute_y_parity(signature)
145}
146
147pub struct TestAbiProvider();
148
149#[async_trait]
150impl AbiProvider for TestAbiProvider {
151    async fn get_abi(&self, _contract_address: String) -> Result<String, QueryBuilderError> {
152        // hard coded G-CRE's ABI
153        let json_str = r#"[{"constant":false,"inputs":[{"name":"tokenHolders","type":"address[]"},{"name":"amounts","type":"uint256[]"}],"name":"recordSales730Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"VestingStartDate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"amount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf365Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"},{"name":"sighash","type":"string"}],"name":"exchange","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf183Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolders","type":"address[]"},{"name":"amounts","type":"uint256[]"}],"name":"recordSales1095Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf365Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"burn","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf2190Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolders","type":"address[]"},{"name":"amounts","type":"uint256[]"}],"name":"recordSales183Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolder","type":"address"},{"name":"numCoins","type":"uint256"}],"name":"recordSale365Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf730Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finalizeSales","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"value","type":"uint256"}],"name":"burnFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf2190Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf730Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolder","type":"address"},{"name":"numCoins","type":"uint256"}],"name":"recordSale183Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"creditcoinSalesLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"vestedBalanceOf1095Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"creditcoinLimitInFrac","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolder","type":"address"},{"name":"numCoins","type":"uint256"}],"name":"recordSale2190Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolder","type":"address"},{"name":"numCoins","type":"uint256"}],"name":"recordSale730Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf1095Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"startVesting","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolders","type":"address[]"},{"name":"amounts","type":"uint256[]"}],"name":"recordSales2190Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenHolder","type":"address"}],"name":"purchasedBalanceOf183Days","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"IsSalesFinalized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolders","type":"address[]"},{"name":"amounts","type":"uint256[]"}],"name":"recordSales365Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokenHolder","type":"address"},{"name":"numCoins","type":"uint256"}],"name":"recordSale1095Days","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"creditcoinFoundation","type":"address"},{"name":"devCost","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"sighash","type":"string"}],"name":"Exchange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]"#;
154
155        Ok(json_str.into())
156    }
157}