ccnext_query_builder/
test_helpers.rs

1use ccnext_abi_encoding::common::{compute_v, compute_y_parity};
2use alloy::consensus::Transaction as _;
3use alloy::rpc::types::eth::transaction::Transaction;
4use alloy::eips::eip2930::AccessList;
5use alloy::{
6    primitives::{Address, B256, U256},
7    providers::{Provider, ProviderBuilder},
8    rpc::types::TransactionReceipt,
9};
10use async_trait::async_trait;
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: {}, Expected: {:?}, Actual: {:?}",
125            field_number, expected_padded, segment_bytes
126        );
127    }
128}
129
130pub fn get_vrs(tx: &Transaction) -> (U256, [u8; 32], [u8; 32]) {
131    let signed_tx = tx
132        .inner
133        .as_legacy()
134        .expect("Already checked that tx is legacy");
135    let signature = signed_tx.signature();
136    let chain_id = tx.chain_id();
137    let v = compute_v(signature, chain_id);
138    let r: [u8; 32] = signature.r().to_be_bytes::<32>()[0..32].try_into().unwrap();
139    let s: [u8; 32] = signature.s().to_be_bytes::<32>()[0..32].try_into().unwrap();
140    (v, r, s)
141}
142
143pub fn get_y_parity(tx: &Transaction) -> u8 {
144    let signature = tx
145        .inner
146        .signature();
147    compute_y_parity(signature)
148}
149
150pub struct TestAbiProvider();
151
152#[async_trait]
153impl AbiProvider for TestAbiProvider {
154    async fn get_abi(&self, _contract_address: String) -> Result<String, QueryBuilderError> {
155        // hard coded G-CRE's ABI
156        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"}]"#;
157
158        Ok(json_str.into())
159    }
160}