1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! This module contains functions for interacting with Ethereum contracts using JSON-RPC requests.
use ethers_core::abi::Token;
use ethers_core::types::U256;
use ethers_core::utils::hex;
use hex::FromHexError;
use serde::{Deserialize, Serialize};
use evm_rpc_canister_types::{EvmRpcCanister, RequestResult, RpcService};
use crate::eth_send_raw_transaction::{get_data, get_function, ContractDetails};
use crate::request::{request, JsonRpcResult};
/// Represents the parameters for an Ethereum call.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EthCallParams {
pub to: String,
pub data: String,
}
/// Represents a JSON-RPC request for an Ethereum call.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EthCallJsonRpcRequest {
pub id: u64,
pub jsonrpc: String,
pub method: String,
pub params: (EthCallParams, String),
}
/// Executes an Ethereum call.
///
/// # Arguments
///
/// * `contract_details` - The details of the contract to call.
/// * `block_number` - The block number to execute the call on.
/// * `rpc_service` - The RPC service to use for the call.
/// * `max_response_bytes` - The maximum number of response bytes to accept.
/// * `evm_rpc` - The EVM RPC canister.
///
/// # Returns
///
/// The decoded output of the call as a vector of tokens.
pub async fn eth_call(
contract_details: ContractDetails<'_>,
block_number: &str,
rpc_service: RpcService,
max_response_bytes: u64,
evm_rpc: EvmRpcCanister,
) -> Vec<Token> {
let function = get_function(&contract_details);
let data = get_data(function, &contract_details);
let json_rpc_payload = serde_json::to_string(&EthCallJsonRpcRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "eth_call".to_string(),
params: (
EthCallParams {
to: contract_details.contract_address.clone(),
data: to_hex(&data),
},
block_number.to_string(),
),
})
.expect("Error while encoding JSON-RPC request");
let res = request(rpc_service, json_rpc_payload, max_response_bytes, evm_rpc).await;
match res {
RequestResult::Ok(ok) => {
let json: JsonRpcResult =
serde_json::from_str(&ok).expect("JSON was not well-formatted");
let result = from_hex(&json.result.expect("Unexpected JSON response")).unwrap();
function
.decode_output(&result)
.expect("Error decoding output")
}
RequestResult::Err(err) => panic!("Response error: {err:?}"),
}
}
/// Retrieves the balance of an ERC20 token for a given account.
///
/// # Arguments
///
/// * `contract_address` - The address of the ERC20 token contract.
/// * `account` - The account to retrieve the balance for.
/// * `rpc_service` - The RPC service to use for the call.
/// * `evm_rpc` - The EVM RPC canister.
///
/// # Returns
///
/// The balance of the ERC20 token for the given account.
pub async fn erc20_balance_of(
contract_address: String,
account: String,
rpc_service: RpcService,
evm_rpc: EvmRpcCanister,
) -> U256 {
let max_response_bytes = 2048;
// Define the ABI JSON as a string literal
let abi_json = r#"
[
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"type": "function"
}
]
"#;
let abi =
serde_json::from_str::<ethers_core::abi::Contract>(abi_json).expect("should serialise");
let contract_details = ContractDetails {
contract_address,
abi: &abi,
function_name: "balanceOf",
args: &[Token::Address(
account.parse().expect("address should be valid"),
)],
};
let Token::Uint(balance) = eth_call(
contract_details,
"latest",
rpc_service,
max_response_bytes,
evm_rpc,
)
.await
.first()
.unwrap()
.clone() else {
panic!("oops")
};
balance
}
/// Converts a byte slice to a hexadecimal string representation.
///
/// # Arguments
///
/// * `data` - The byte slice to convert.
///
/// # Returns
///
/// The hexadecimal string representation of the byte slice.
fn to_hex(data: &[u8]) -> String {
format!("0x{}", hex::encode(data))
}
/// Converts a hexadecimal string representation to a byte slice.
///
/// # Arguments
///
/// * `data` - The hexadecimal string to convert.
///
/// # Returns
///
/// The byte slice representation of the hexadecimal string, or an error if the conversion fails.
fn from_hex(data: &str) -> Result<Vec<u8>, FromHexError> {
hex::decode(&data[2..])
}