Skip to content

Commit

Permalink
Simplify call_contract() in Rust canister (#48)
Browse files Browse the repository at this point in the history
* Simplify

* Adjust readme

* Refactor for cleaner 'call_contract()' input args

* Support overloaded functions

* Simplify
  • Loading branch information
rvanasa committed Aug 24, 2023
1 parent cd4eb48 commit a7a7c7d
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 17 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Here is one way to acquire tokens and NFTs on the [Sepolia](https://www.alchemy.

**Ethereum Integration:**
- [Rust](https://www.rust-lang.org/): a secure, high-performance canister programming language
- [ethers-core](https://github.com/gakonst/ethers-rs): a popular Rust library for working with Ethereum data structures
- [MetaMask](https://metamask.io/): a wallet and browser extension for interacting with Ethereum dapps

## 📚 Documentation
Expand Down
29 changes: 22 additions & 7 deletions canisters/ic_eth/src/eth_rpc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ethers_core::abi::{Function, Token};
use ethers_core::abi::{Contract, FunctionExt, Token};
use ic_cdk::api::management_canister::http_request::{
http_request, CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse, TransformArgs,
TransformContext,
Expand Down Expand Up @@ -47,11 +47,10 @@ macro_rules! include_abi {
}};
}

thread_local! {
static NEXT_ID: RefCell<u64> = RefCell::default();
}

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;
Expand All @@ -73,9 +72,25 @@ fn get_rpc_endpoint(network: &str) -> &'static str {
pub async fn call_contract(
network: &str,
contract_address: String,
f: &Function,
abi: &Contract,
function_name: &str,
args: &[Token],
) -> 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");
Expand Down Expand Up @@ -140,6 +155,6 @@ pub fn transform(args: TransformArgs) -> HttpResponse {
body: args.response.body,
// Strip headers as they contain the Date which is not necessarily the same
// and will prevent consensus on the result.
headers: Vec::<HttpHeader>::new(),
headers: Vec::new(),
}
}
19 changes: 9 additions & 10 deletions canisters/ic_eth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ use util::to_hex;
mod eth_rpc;
mod util;

/// Required for HTTPS outcalls.
pub use eth_rpc::transform;

// Load relevant ABIs (Ethereum equivalent of Candid interfaces)
thread_local! {
static ERC_721: Rc<Contract> = Rc::new(include_abi!("../abi/erc721.json"));
Expand All @@ -37,20 +34,21 @@ pub fn verify_ecdsa(eth_address: String, message: String, signature: String) ->
#[ic_cdk_macros::update]
#[candid_method]
pub async fn erc721_owner_of(network: String, contract_address: String, token_id: u64) -> String {
// TODO: whitelist / access control
// TODO: access control
// TODO: cycles estimation for HTTP outcalls

let abi = ERC_721.with(Rc::clone);
let abi = &ERC_721.with(Rc::clone);
let result = call_contract(
&network,
contract_address,
abi.function("ownerOf").unwrap(),
abi,
"ownerOf",
&[Token::Uint(token_id.into())],
)
.await;
match result.get(0) {
Some(Token::Address(a)) => to_hex(a.as_bytes()),
_ => panic!("Unexpected JSON output"),
_ => panic!("Unexpected result"),
}
}

Expand All @@ -68,11 +66,12 @@ pub async fn erc1155_balance_of(
let owner_address =
ethers_core::types::Address::from_str(&owner_address).expect("Invalid owner address");

let abi = ERC_1155.with(Rc::clone);
let abi = &ERC_1155.with(Rc::clone);
let result = call_contract(
&network,
contract_address,
abi.function("balanceOf").unwrap(),
abi,
"balanceOf",
&[
Token::Address(owner_address.into()),
Token::Uint(token_id.into()),
Expand All @@ -81,6 +80,6 @@ pub async fn erc1155_balance_of(
.await;
match result.get(0) {
Some(Token::Uint(n)) => n.as_u64(),
_ => panic!("Unexpected JSON output"),
_ => panic!("Unexpected result"),
}
}

0 comments on commit a7a7c7d

Please sign in to comment.