use crate::utils::{from_hex, to_hex};
use ethers_core::abi::{Contract, FunctionExt, Token};
use ic_cdk::api::management_canister::http_request::{
http_request, CanisterHttpRequestArgument, HttpHeader, HttpMethod, TransformContext,
};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JsonRpcRequest<'a, T> {
pub id: u64,
pub jsonrpc: &'static str,
pub method: &'a str,
pub params: T,
}
impl<'a, T> JsonRpcRequest<'a, T> {
pub fn new(method: &'a str, params: T) -> Self {
Self {
id: next_id(),
jsonrpc: "2.0",
method,
params,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct EthCall {
to: String,
data: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct JsonRpcResponse {
result: Option<String>,
error: Option<JsonRpcError>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct JsonRpcError {
code: isize,
message: String,
}
fn next_id() -> u64 {
thread_local! {
static NEXT_ID: RefCell<u64> = RefCell::default();
}
NEXT_ID.with(|next_id| {
let mut next_id = next_id.borrow_mut();
let id = *next_id;
*next_id = next_id.wrapping_add(1);
id
})
}
pub async fn request<'a, T: Serialize>(
rpc_url: &str,
json_request: &JsonRpcRequest<'a, T>,
cycles: u128,
max_response_bytes: Option<u64>,
) -> Vec<u8> {
let json_rpc_payload =
serde_json::to_string(json_request).expect("Error while encoding JSON-RPC request");
let parsed_url = url::Url::parse(&rpc_url).expect("Service URL parse error");
let host = parsed_url
.host_str()
.expect("Invalid JSON-RPC host")
.to_string();
let request_headers = vec![
HttpHeader {
name: "Content-Type".to_string(),
value: "application/json".to_string(),
},
HttpHeader {
name: "Host".to_string(),
value: host.to_string(),
},
];
let request = CanisterHttpRequestArgument {
url: rpc_url.to_string(),
max_response_bytes,
method: HttpMethod::POST,
headers: request_headers,
body: Some(json_rpc_payload.as_bytes().to_vec()),
transform: Some(TransformContext::from_name(
"__transform_eth_rpc".to_string(),
vec![],
)),
};
let response = match http_request(request, cycles).await {
Ok((r,)) => r,
Err((r, m)) => panic!("{:?} {:?}", r, m),
};
let json: JsonRpcResponse =
serde_json::from_str(std::str::from_utf8(&response.body).expect("utf8"))
.expect("JSON was not well-formatted");
if let Some(err) = json.error {
panic!("JSON-RPC error code {}: {}", err.code, err.message);
}
from_hex(&json.result.expect("Unexpected JSON response")).unwrap()
}
pub async fn call_contract(
rpc_url: &str,
contract_address: String,
abi: &Contract,
function_name: &str,
args: &[Token],
cycles: u128,
max_response_bytes: Option<u64>,
) -> Vec<Token> {
let f = match abi.functions_by_name(function_name).map(|v| &v[..]) {
Ok([f]) => f,
Ok(fs) => panic!(
"Found {} function overloads. Please pass one of the following: {}",
fs.len(),
fs.iter()
.map(|f| format!("{:?}", f.abi_signature()))
.collect::<Vec<_>>()
.join(", ")
),
Err(_) => abi
.functions()
.find(|f| function_name == f.abi_signature())
.expect("Function not found"),
};
let data = f
.encode_input(args)
.expect("Error while encoding input args");
let json_request = JsonRpcRequest::new(
"eth_call",
(
EthCall {
to: contract_address,
data: to_hex(&data),
},
"latest",
),
);
let result = request(rpc_url, &json_request, cycles, max_response_bytes).await;
f.decode_output(&result).expect("Error decoding output")
}