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 let rpc_url = "https://sepolia-proxy-rpc.creditcoin.network";
43 let provider = ProviderBuilder::new().on_http(rpc_url.parse().unwrap());
44
45 let tx_hash = B256::from_str(tx_hash_str).unwrap();
47
48 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 let expected_padded: Vec<u8> = match expected {
74 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 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 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 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 let segment_bytes = &abi[offset..offset + size];
120 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 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}