Skip to content

EPROD-1192 upgrade to ic-cdk 0.18 #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ homepage = "https://github.com/bitfinity-network/bitfinity-evm-sdk"
include = ["src/**/*", "LICENSE", "README.md"]
license = "MIT"
repository = "https://github.com/bitfinity-network/bitfinity-evm-sdk"
version = "0.52.0"
version = "0.53.0"

[workspace.dependencies]
did = { path = "src/did" }
Expand All @@ -42,7 +42,7 @@ alloy = { version = "1", default-features = false, features = [
"rlp",
"serde",
] }
anyhow = "1.0"
anyhow = "1"
bincode = "1.3"
bytes = "1"
candid = "0.10"
Expand All @@ -51,11 +51,11 @@ chrono = { version = "0.4", default-features = false }
derive_more = { version = "2", features = ["display", "from", "into"] }
env_logger = { version = "0.11.4", default-features = false }
futures = { version = "0.3", default-features = false }
ic-canister = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-canister", tag = "v0.24.x" }
ic-canister-client = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-canister-client", tag = "v0.24.x" }
ic-exports = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-exports", tag = "v0.24.x" }
ic-log = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-log", tag = "v0.24.x" }
ic-stable-structures = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-stable-structures", tag = "v0.24.x" }
ic-canister = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-canister", branch = "EPROD-1192-upgrade-to-ic-cdk-0-18" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be updated

ic-canister-client = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-canister-client", branch = "EPROD-1192-upgrade-to-ic-cdk-0-18" }
ic-exports = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-exports", branch = "EPROD-1192-upgrade-to-ic-cdk-0-18" }
ic-log = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-log", branch = "EPROD-1192-upgrade-to-ic-cdk-0-18" }
ic-stable-structures = { git = "https://github.com/bitfinity-network/canister-sdk", package = "ic-stable-structures", branch = "EPROD-1192-upgrade-to-ic-cdk-0-18" }
itertools = "0.14"
jsonrpsee = { version = "0.25", features = ["server", "macros"] }
lightspeed_scheduler = "0.64"
Expand All @@ -72,7 +72,7 @@ serde_json = "1.0"
serde_with = "3.3"
sha2 = "0.10"
sha3 = "0.10"
sqlx = { version = "0.8.1", default-features = false, features = [
sqlx = { version = "0.8", default-features = false, features = [
"macros",
"migrate",
"json",
Expand Down
2 changes: 1 addition & 1 deletion src/did/src/evm_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub enum EvmResetState {
/// End of the reset process.
/// It sets the state to the given block.
/// If the block state hash is not equal to the current state hash, it will fail.
End(Block<H256>),
End(Box<Block<H256>>),
}

/// The EVM global state
Expand Down
36 changes: 17 additions & 19 deletions src/eth-signer/src/ic_sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use alloy::signers::utils::public_key_to_address;
use candid::{CandidType, Principal};
use did::transaction::{Parity, Signature as DidSignature};
use ic_canister::virtual_canister_call;
use ic_exports::ic_cdk::api::call::RejectionCode;
use ic_exports::ic_cdk::api::management_canister::ecdsa::{
EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgument, EcdsaPublicKeyResponse, SignWithEcdsaArgument,
SignWithEcdsaResponse,
use ic_exports::ic_cdk::call::Error as CallError;
use ic_exports::ic_cdk::management_canister::{
EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, EcdsaPublicKeyResult, SignWithEcdsaArgs,
SignWithEcdsaResult,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
Expand All @@ -19,8 +19,8 @@ pub type DerivationPath = Vec<Vec<u8>>;

#[derive(Debug, Error)]
pub enum IcSignerError {
#[error("IC failed to sign data with rejection code {0:?}: {1}")]
SigningFailed(RejectionCode, String),
#[error("IC failed to sign data: {0}")]
SigningFailed(#[from] CallError),

#[error("from address is not specified in transaction")]
FromAddressNotPresent,
Expand Down Expand Up @@ -128,7 +128,7 @@ impl IcSigner {
key_id: SigningKeyId,
derivation_path: DerivationPath,
) -> Result<DidSignature, IcSignerError> {
let request = SignWithEcdsaArgument {
let request = SignWithEcdsaArgs {
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: key_id.to_string(),
Expand All @@ -141,11 +141,10 @@ impl IcSigner {
Principal::management_canister(),
"sign_with_ecdsa",
(request,),
SignWithEcdsaResponse,
SignWithEcdsaResult,
100_000_000_000
)
.await
.map_err(|(code, msg)| IcSignerError::SigningFailed(code, msg))?
.await?
.signature;

// IC doesn't support recovery id signature parameter, so we
Expand Down Expand Up @@ -175,7 +174,7 @@ impl IcSigner {
key_id: SigningKeyId,
derivation_path: DerivationPath,
) -> Result<Vec<u8>, IcSignerError> {
let request = EcdsaPublicKeyArgument {
let request = EcdsaPublicKeyArgs {
canister_id: None,
derivation_path,
key_id: EcdsaKeyId {
Expand All @@ -187,10 +186,10 @@ impl IcSigner {
Principal::management_canister(),
"ecdsa_public_key",
(request,),
EcdsaPublicKeyResponse
EcdsaPublicKeyResult
)
.await
.map_err(|(code, msg)| IcSignerError::SigningFailed(code, msg))
.map_err(IcSignerError::from)
.map(|response| response.public_key)
}

Expand All @@ -212,9 +211,8 @@ mod tests {
use candid::Principal;
use did::H256;
use ic_canister::register_virtual_responder;
use ic_exports::ic_cdk::api::management_canister::ecdsa::{
EcdsaPublicKeyArgument, EcdsaPublicKeyResponse, SignWithEcdsaArgument,
SignWithEcdsaResponse,
use ic_exports::ic_cdk::management_canister::{
EcdsaPublicKeyArgs, EcdsaPublicKeyResult, SignWithEcdsaArgs, SignWithEcdsaResult,
};
use ic_exports::ic_kit::MockContext;

Expand All @@ -232,19 +230,19 @@ mod tests {
register_virtual_responder(
Principal::management_canister(),
"sign_with_ecdsa",
move |args: (SignWithEcdsaArgument,)| {
move |args: (SignWithEcdsaArgs,)| {
let hash = args.0.message_hash;
let h256 = H256::from_slice(&hash);
let signature = wallet_to_sign.sign_hash_sync(&h256.0).unwrap();
let signature: Vec<u8> = signature.as_bytes().into_iter().take(64).collect();
SignWithEcdsaResponse { signature }
SignWithEcdsaResult { signature }
},
);

register_virtual_responder(
Principal::management_canister(),
"ecdsa_public_key",
move |_: (EcdsaPublicKeyArgument,)| EcdsaPublicKeyResponse {
move |_: (EcdsaPublicKeyArgs,)| EcdsaPublicKeyResult {
public_key: pubkey.as_bytes().to_vec(),
chain_code: vec![],
},
Expand Down
3 changes: 0 additions & 3 deletions src/ethereum-json-rpc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ pocket-ic-tests-client = [
]
reqwest = ["dep:reqwest"]
http-outcall = ["dep:url"]
# Adds an API method `sanitize_http_response` to the canister and `HttpOutcallClient::new_sanitized` method to use it.
# We feature-gate it because it changes the API of the canister which is not always necessary.
sanitize-http-outcall = []

[dependencies]
alloy = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion src/ethereum-json-rpc-client/src/canister_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<T: CanisterClient + Sync + 'static> Client for T {
}
.map_err(|e| {
log::warn!("failed to send RPC request: {e}");
JsonRpcError::CanisterClient(e)
JsonRpcError::CanisterClient(e.into())
})?;

let response = serde_json::from_slice(&http_response.body)?;
Expand Down
13 changes: 4 additions & 9 deletions src/ethereum-json-rpc-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use did::H256;
use did::rpc::response::Failure;
use ic_exports::ic_kit::RejectionCode;
use ic_exports::ic_cdk::call::Error as CallError;
use thiserror::Error;

/// Result type for the Ethereum JSON-RPC client.
Expand All @@ -14,14 +14,9 @@ pub enum JsonRpcError {
/// Canister client error [`ic_canister_client::CanisterClientError`]
#[cfg(feature = "ic-canister-client")]
#[error("Canister client error: {0}")]
CanisterClient(#[from] ic_canister_client::CanisterClientError),
#[error("Canister call failed with code: {rejection_code:?}: {message}")]
CanisterCall {
/// Canister [`RejectionCode`].
rejection_code: RejectionCode,
/// Canister rejection message.
message: String,
},
CanisterClient(#[from] Box<ic_canister_client::CanisterClientError>),
#[error("Canister call failed: {0}")]
CanisterCall(#[from] CallError),
/// Error while parsing the JSON response.
#[error("Invalid JSON response: {0}")]
Json(#[from] serde_json::Error),
Expand Down
118 changes: 7 additions & 111 deletions src/ethereum-json-rpc-client/src/http_outcall.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use std::future::Future;
use std::pin::Pin;

use crate::{Client, JsonRpcError, JsonRpcResult};
use did::rpc::request::RpcRequest;
use did::rpc::response::RpcResponse;
use ic_cdk::api::management_canister::http_request::{
self, CanisterHttpRequestArgument, HttpHeader, HttpMethod, TransformContext,
use ic_exports::ic_cdk::management_canister::{
self, HttpHeader, HttpMethod, HttpRequestArgs, TransformContext,
};
#[cfg(feature = "sanitize-http-outcall")]
use ic_cdk::api::management_canister::http_request::{HttpResponse, TransformArgs};
use ic_exports::ic_cdk;

use crate::{Client, JsonRpcError, JsonRpcResult};

/// EVM client that uses HTTPS Outcalls to communicate with EVM.
///
Expand Down Expand Up @@ -52,30 +48,13 @@ impl HttpOutcallClient {
///
/// Transform context is used to sanitize HTTP responses before checking for consensus.
///
/// You can use [`sanitized`] method to set up default transform context. (Available with
/// Cargo feature `sanitize-http-outcall`)
///
/// # Arguments
/// * `transform_context` - method to use to sanitize HTTP response
pub fn with_transform(mut self, transform_context: TransformContext) -> Self {
self.transform_context = Some(transform_context);
self
}

/// Sets default transform context for the client.
///
/// The default sanitize drops most of HTTP headers that may prevent consensus on the response.
///
/// Only available with Cargo feature `sanitize-http-outcall`.
#[cfg(feature = "sanitize-http-outcall")]
pub fn sanitized(mut self) -> Self {
self.transform_context = Some(TransformContext::from_name(
"sanitize_http_response".into(),
vec![],
));
self
}

/// The maximal size of the response in bytes. If None, 2MiB will be the
/// limit.
/// This value affects the cost of the http request and it is highly
Expand All @@ -89,18 +68,6 @@ impl HttpOutcallClient {
}
}

#[cfg(feature = "sanitize-http-outcall")]
#[ic_cdk::query]
fn sanitize_http_response(raw_response: TransformArgs) -> HttpResponse {
const USE_HEADERS: &[&str] = &["content-encoding", "content-length", "content-type", "host"];
let TransformArgs { mut response, .. } = raw_response;
response
.headers
.retain(|header| USE_HEADERS.iter().any(|v| v == &header.name.to_lowercase()));

response
}

impl Client for HttpOutcallClient {
fn send_rpc_request(
&self,
Expand Down Expand Up @@ -133,7 +100,7 @@ impl Client for HttpOutcallClient {
log::trace!("Making http request to {url} with headers: {headers:?}");
log::trace!("Request body is: {}", String::from_utf8_lossy(&body));

let request = CanisterHttpRequestArgument {
let request = HttpRequestArgs {
url,
max_response_bytes,
method: HttpMethod::POST,
Expand All @@ -142,23 +109,17 @@ impl Client for HttpOutcallClient {
transform,
};

let cost = http_request_required_cycles(&request);
let cost = management_canister::cost_http_request(&request);

let cycles_available = ic_exports::ic_cdk::api::canister_balance128();
let cycles_available = ic_exports::ic_cdk::api::canister_cycle_balance();
if cycles_available < cost {
return Err(JsonRpcError::InsufficientCycles {
available: cycles_available,
cost,
});
}

let http_response = http_request::http_request(request, cost)
.await
.map(|(res,)| res)
.map_err(|(r, m)| JsonRpcError::CanisterCall {
rejection_code: r,
message: m,
})?;
let http_response = management_canister::http_request(&request).await?;

log::trace!(
"CanisterClient - Response from http_outcall'. Response: {} {:?}. Body: {}",
Expand All @@ -175,68 +136,3 @@ impl Client for HttpOutcallClient {
})
}
}

// Calculate cycles for http_request
// NOTE:
// https://github.com/dfinity/cdk-rs/blob/710a6cdcc3eb03d2392df1dfd5f047dff9deee80/examples/management_canister/src/caller/lib.rs#L7-L19
pub fn http_request_required_cycles(arg: &CanisterHttpRequestArgument) -> u128 {
let max_response_bytes = match arg.max_response_bytes {
Some(ref n) => *n as u128,
None => 2 * 1024 * 1024u128, // default 2MiB
};
let arg_raw = candid::utils::encode_args((arg,)).expect("Failed to encode arguments.");
// The fee is for a 13-node subnet to demonstrate a typical usage.
(3_000_000u128
+ 60_000u128 * 13
+ (arg_raw.len() as u128 + "http_request".len() as u128) * 400
+ max_response_bytes * 800)
* 13
}

#[cfg(test)]
#[cfg(feature = "sanitize-http-outcall")]
mod tests {
use candid::Nat;

use super::*;

#[test]
fn sanitize_http_response_removes_extra_headers() {
let transform_args = TransformArgs {
response: HttpResponse {
status: 200u128.into(),
headers: vec![
HttpHeader {
name: "content-type".to_string(),
value: "application/json".to_string(),
},
HttpHeader {
name: "content-length".to_string(),
value: "42".to_string(),
},
HttpHeader {
name: "content-encoding".to_string(),
value: "gzip".to_string(),
},
HttpHeader {
name: "date".to_string(),
value: "Fri, 11 Oct 2024 10:25:08 GMT".to_string(),
},
],
body: vec![],
},
context: vec![],
};

let sanitized: HttpResponse = sanitize_http_response(transform_args);
assert_eq!(sanitized.headers.len(), 3);
assert_eq!(sanitized.status, Nat::from(200u128));
assert!(
sanitized
.headers
.iter()
.any(|header| header.name == "content-type")
);
assert!(!sanitized.headers.iter().any(|header| header.name == "date"));
}
}