feat: add RPC logic and config yaml parsing
This commit is contained in:
19
protocol-testing/src/abi/ERC20.json
Normal file
19
protocol-testing/src/abi/ERC20.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "balanceOf",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "balance",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
]
|
||||||
71
protocol-testing/src/config.rs
Normal file
71
protocol-testing/src/config.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use figment::providers::Format;
|
||||||
|
use hex::FromHex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Represents a hexadecimal byte string: Check if we already have a default impl for this
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(from = "String", into = "String")]
|
||||||
|
pub struct HexBytes(Vec<u8>);
|
||||||
|
|
||||||
|
impl From<String> for HexBytes {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
let s = s
|
||||||
|
.trim_start_matches("0x")
|
||||||
|
.to_lowercase();
|
||||||
|
HexBytes(Vec::from_hex(s).unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for HexBytes {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("0x{}", hex::encode(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a ProtocolComponent with its main attributes
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ProtocolComponentExpectation {
|
||||||
|
pub id: String,
|
||||||
|
pub tokens: Vec<HexBytes>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub static_attributes: HashMap<String, HexBytes>,
|
||||||
|
pub creation_tx: HexBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a ProtocolComponent with test configuration
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ProtocolComponentWithTestConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base: ProtocolComponentExpectation,
|
||||||
|
#[serde(default = "default_false")]
|
||||||
|
pub skip_simulation: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_false() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for an individual test
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct IntegrationTest {
|
||||||
|
pub name: String,
|
||||||
|
pub start_block: u64,
|
||||||
|
pub stop_block: u64,
|
||||||
|
pub initialized_accounts: Option<Vec<String>>,
|
||||||
|
pub expected_components: Vec<ProtocolComponentWithTestConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main integration test configuration
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct IntegrationTestsConfig {
|
||||||
|
pub substreams_yaml_path: String,
|
||||||
|
pub adapter_contract: String,
|
||||||
|
pub adapter_build_signature: Option<String>,
|
||||||
|
pub adapter_build_args: Option<String>,
|
||||||
|
pub initialized_accounts: Option<Vec<String>>,
|
||||||
|
pub skip_balance_check: bool,
|
||||||
|
pub protocol_type_names: Vec<String>,
|
||||||
|
pub tests: Vec<IntegrationTest>,
|
||||||
|
}
|
||||||
118
protocol-testing/src/rpc.rs
Normal file
118
protocol-testing/src/rpc.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
use alloy::{
|
||||||
|
contract::{ContractInstance, Interface},
|
||||||
|
dyn_abi::DynSolValue,
|
||||||
|
eips::eip1898::BlockId,
|
||||||
|
primitives::{address, Address, U256},
|
||||||
|
providers::{Provider, ProviderBuilder},
|
||||||
|
transports::http::reqwest::Url,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NATIVE_ALIASES: &[Address] = &[
|
||||||
|
address!("0x0000000000000000000000000000000000000000"),
|
||||||
|
address!("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const ERC_20_ABI: &str = r#"[{"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"}]"#;
|
||||||
|
|
||||||
|
struct RPCProvider {
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RPCProvider {
|
||||||
|
fn new(url: String) -> RPCProvider {
|
||||||
|
let url = url.as_str().parse().unwrap();
|
||||||
|
RPCProvider { url }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_token_balance(
|
||||||
|
self,
|
||||||
|
token_address: Address,
|
||||||
|
wallet_address: Address,
|
||||||
|
block_number: u64,
|
||||||
|
) -> U256 {
|
||||||
|
let provider = ProviderBuilder::new().on_http(self.url);
|
||||||
|
let block_id: BlockId = BlockId::from(block_number);
|
||||||
|
|
||||||
|
match NATIVE_ALIASES.contains(&token_address) {
|
||||||
|
true => provider
|
||||||
|
.get_balance(wallet_address)
|
||||||
|
.block_id(block_id)
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch token balance"),
|
||||||
|
false => {
|
||||||
|
let abi = serde_json::from_str(ERC_20_ABI).expect("invalid ABI");
|
||||||
|
|
||||||
|
let contract = ContractInstance::new(token_address, provider, Interface::new(abi));
|
||||||
|
|
||||||
|
let wallet_addr = DynSolValue::from(wallet_address);
|
||||||
|
|
||||||
|
let result_value = contract
|
||||||
|
.function("balanceOf", &[wallet_addr])
|
||||||
|
.expect("Failed to build function call")
|
||||||
|
.block(block_id)
|
||||||
|
.call()
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch ERC-20 Balance");
|
||||||
|
let result: U256 = result_value
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.as_uint()
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_block_header(self, block_number: u64) {
|
||||||
|
// TODO: Implement
|
||||||
|
// let provider = ProviderBuilder::new().on_http(self.url);
|
||||||
|
// let block_id: BlockId = BlockId::from(block_number);
|
||||||
|
//
|
||||||
|
// let block = provider.get_block(block_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{env, str::FromStr};
|
||||||
|
|
||||||
|
use alloy::primitives::address;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_token_balance_native_token() {
|
||||||
|
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
|
||||||
|
|
||||||
|
let rpc_provider = RPCProvider::new(eth_rpc_url);
|
||||||
|
let token_address = address!("0x0000000000000000000000000000000000000000");
|
||||||
|
let wallet_address = address!("0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA");
|
||||||
|
let block_number = 21998530;
|
||||||
|
|
||||||
|
let balance = rpc_provider
|
||||||
|
.get_token_balance(token_address, wallet_address, block_number)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
balance,
|
||||||
|
U256::from_str("1070041574684539264153").expect("Failed to convert ETH value")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_token_balance_erc20_token() {
|
||||||
|
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
|
||||||
|
|
||||||
|
let rpc_provider = RPCProvider::new(eth_rpc_url);
|
||||||
|
let token_address = address!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
|
||||||
|
let wallet_address = address!("0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA");
|
||||||
|
let block_number = 21998530;
|
||||||
|
|
||||||
|
let balance = rpc_provider
|
||||||
|
.get_token_balance(token_address, wallet_address, block_number)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(balance, U256::from(717250938432_u64));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user