diff --git a/Cargo.lock b/Cargo.lock index a31d476e81..dc6ee4bae6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,7 @@ dependencies = [ "ic-stable-structures", "ic-wasi-polyfill", "include_dir", + "open_value_sharing", "serde", "serde_json", "sha2", @@ -1063,6 +1064,16 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "open_value_sharing" +version = "0.0.0" +dependencies = [ + "candid", + "ic-cdk", + "serde", + "serde_json", +] + [[package]] name = "parking_lot" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 9644d94590..9c996c1e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,6 @@ [workspace] -members = ["src/compiler/rust/canister", "src/compiler/rust/canister_methods"] +members = [ + "src/compiler/rust/canister", + "src/compiler/rust/canister_methods", + "src/compiler/rust/open_value_sharing", +] diff --git a/Dockerfile b/Dockerfile index ae06ca79f8..5aab07f7cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,13 +24,13 @@ RUN mkdir /global_target_dir # The following is to pre-compile as many dependencies as possible for Azle projects # We simply copy over the current Rust setup and do a cargo build -COPY rust/ /azle_rust_dependencies +COPY src/compiler/rust/ /azle_rust_dependencies RUN touch /azle_rust_dependencies/canister/src/main.js RUN touch /azle_rust_dependencies/canister/src/candid.did RUN mkdir /azle_rust_dependencies/canister/src/assets RUN echo "{\"canister_methods\":{\"queries\":[],\"updates\":[],\"callbacks\":{}},\"env_vars\":[],\"assets\":[]}" > /azle_rust_dependencies/canister/src/compiler_info.json -RUN echo "[workspace]\nmembers = [\"canister\"]\n\n[profile.release]\nopt-level = 'z'" > /azle_rust_dependencies/Cargo.toml +RUN echo "[workspace]\nmembers = [\"canister\", \"canister_methods\", \"open_value_sharing\"]\n\n[profile.release]\nopt-level = 'z'" > /azle_rust_dependencies/Cargo.toml COPY Cargo.lock /azle_rust_dependencies WORKDIR /azle_rust_dependencies diff --git a/src/compiler/generate_cargo_toml_files.ts b/src/compiler/generate_cargo_toml_files.ts index d09ffabf97..1c21a0ea30 100644 --- a/src/compiler/generate_cargo_toml_files.ts +++ b/src/compiler/generate_cargo_toml_files.ts @@ -28,7 +28,9 @@ export function generateWorkspaceCargoToml(optLevel: OptLevel): Toml { [workspace] members = [ - "canister" + "canister", + "canister_methods", + "open_value_sharing" ] [profile.release] diff --git a/src/compiler/prepare_docker_image.ts b/src/compiler/prepare_docker_image.ts index af92de6955..3790469858 100644 --- a/src/compiler/prepare_docker_image.ts +++ b/src/compiler/prepare_docker_image.ts @@ -135,7 +135,7 @@ function buildAndLoadImageWithDockerfile( } execSyncPretty( - `podman build -f ${__dirname}/Dockerfile -t ${dockerImageName} ${require.main?.path}`, + `podman build -f ${require.main.path}/Dockerfile -t ${dockerImageName} ${require.main.path}`, 'inherit' ); diff --git a/src/compiler/prepare_rust_staging_area.ts b/src/compiler/prepare_rust_staging_area.ts index b80c2c8921..9ad2e843a9 100644 --- a/src/compiler/prepare_rust_staging_area.ts +++ b/src/compiler/prepare_rust_staging_area.ts @@ -45,6 +45,15 @@ export function prepareRustStagingArea( `${canisterPath}/canister_methods` ); + if (!existsSync(`${canisterPath}/open_value_sharing`)) { + mkdirSync(`${canisterPath}/open_value_sharing`); + } + + copySync( + `${__dirname}/rust/open_value_sharing`, + `${canisterPath}/open_value_sharing` + ); + writeFileSync(`${canisterPath}/canister/src/main.js`, canisterJavaScript); if ( diff --git a/src/compiler/rust/canister/Cargo.toml b/src/compiler/rust/canister/Cargo.toml index 48863fa8be..821091eba9 100644 --- a/src/compiler/rust/canister/Cargo.toml +++ b/src/compiler/rust/canister/Cargo.toml @@ -15,6 +15,7 @@ candid = "0.10.2" candid_parser = "0.1.2" ic-stable-structures = "0.6.2" canister_methods = { path = "../canister_methods" } +open_value_sharing = { path = "../open_value_sharing" } include_dir = "0.7.3" slotmap = "=1.0.6" wasmi = "0.31.2" diff --git a/src/compiler/rust/canister/src/lib.rs b/src/compiler/rust/canister/src/lib.rs index a81476fe94..0f297fb0e7 100644 --- a/src/compiler/rust/canister/src/lib.rs +++ b/src/compiler/rust/canister/src/lib.rs @@ -8,6 +8,7 @@ use ic_stable_structures::{ DefaultMemoryImpl, StableBTreeMap, Storable, }; use include_dir::{include_dir, Dir}; +use open_value_sharing::open_value_sharing_periodic_payment; use std::fs; use wasmedge_quickjs::AsObject; @@ -193,185 +194,11 @@ fn run_event_loop(context: &mut wasmedge_quickjs::Context) { } } -// TODO do we need error handling?? -// TODO it is much more scalable without error handling -// TODO also it's quick and nimble if it's just best effort -// TODO we can also just spawn off timers until they are all done -// TODO we need some way to store state...across upgrades -// TODO the timer always needs to be set across upgrades -// TODO notify runs out after 500 because of the canister outgoing queue -// TODO calling wallet_receive seems to have no limit, takes few cycles, at least within the same subnet -// TODO management canister, which might be across subnets as an emulation, takes the longest -// TODO not sure if there's any practical limit though - -// TODO figure out how to get this to work on mainnet and locally well -#[ic_cdk_macros::update] -pub async fn _azle_open_value_sharing_periodic_payment() { - ic_cdk::println!("_azle_open_value_sharing_periodic_payment"); - - let compiler_info = get_compiler_info("compiler_info.json").unwrap(); - - let total_periodic_payment_amount = calculate_total_periodic_payment_amount().await; - - ic_cdk::println!( - "total_periodic_payment_amount: {}", - total_periodic_payment_amount - ); - - for (depth, dependency_level) in compiler_info.dependency_info.iter().enumerate() { - ic_cdk::println!("depth: {}", depth); - ic_cdk::println!("dependency_level: {:#?}", dependency_level); - - for dependency in dependency_level { - ic_cdk::println!("dependency: {:#?}", dependency); - - let dependency_periodic_payment_amount = calculate_dependency_periodic_payment_amount( - dependency, - dependency_level, - depth, - total_periodic_payment_amount, - depth == compiler_info.dependency_info.len() - 1, - ); - - if dependency.platform == "icp" { - handle_icp_platform(dependency, dependency_periodic_payment_amount).await; - } - } - } -} - // TODO will this work for queries as well? #[ic_cdk_macros::update] pub fn _azle_chunk() {} -#[derive(Debug, serde::Serialize, serde::Deserialize)] -struct CanisterMethods { - init: Option, - post_upgrade: Option, - pre_upgrade: Option, - inspect_message: Option, - heartbeat: Option, - queries: Vec, - updates: Vec, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -struct CanisterMethod { - name: String, - composite: Option, - guard_name: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -struct CompilerInfo { - canister_methods: CanisterMethods, - env_vars: Vec<(String, String)>, - dependency_info: Vec>, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -struct Dependency { - name: String, - weight: u32, - platform: String, - asset: String, - payment_mechanism: String, - custom: std::collections::HashMap, -} - -fn get_compiler_info(compiler_info_path: &str) -> Result { - let compiler_info_string = fs::read_to_string(compiler_info_path) - .map_err(|err| format!("Error reading {compiler_info_path}: {err}"))?; - let compiler_info: CompilerInfo = serde_json::from_str(&compiler_info_string) - .map_err(|err| format!("Error parsing {compiler_info_path}: {err}"))?; - - Ok(compiler_info) -} - -// TODO do all of the balance and previous calculation stuff here -async fn calculate_total_periodic_payment_amount() -> u128 { - 1_000_000 -} - -// TODO we also need to take into account the total number of levels -// TODO for example if there is only one level you don't need to cut anything in half -// TODO double-check the weight calculation -fn calculate_dependency_periodic_payment_amount( - dependency: &Dependency, - dependency_level: &Vec, - depth: usize, - total_periodic_payment_amount: u128, - bottom: bool, -) -> u128 { - let adjusted_depth = depth + if bottom { 0 } else { 1 }; - - let dependency_level_periodic_payment_amount = - total_periodic_payment_amount / 2_u128.pow(adjusted_depth as u32); - - ic_cdk::println!( - "dependency_level_periodic_payment_amount: {}", - dependency_level_periodic_payment_amount - ); - - let total_dependency_level_weight: u32 = dependency_level - .iter() - .map(|dependency| dependency.weight) - .sum(); - - let dependency_ratio = dependency.weight as f64 / total_dependency_level_weight as f64; - - (dependency_level_periodic_payment_amount as f64 * dependency_ratio) as u128 -} - -async fn handle_icp_platform(dependency: &Dependency, payment_amount: u128) { - if dependency.asset == "cycles" { - handle_icp_platform_asset_cycles(dependency, payment_amount).await; - } -} - -async fn handle_icp_platform_asset_cycles(dependency: &Dependency, payment_amount: u128) { - let principal_string = dependency - .custom - .get("principal") - .unwrap() - .as_str() - .unwrap() - .to_string(); - let principal = candid::Principal::from_text(principal_string).unwrap(); - - if dependency.payment_mechanism == "wallet" { - ic_cdk::println!("wallet"); - - ic_cdk::api::call::call_with_payment128::<(Option<()>,), ()>( - principal, - "wallet_receive", - (None,), - payment_amount, - ) // TODO do we need to specify None? - .await - .unwrap(); - } - - if dependency.payment_mechanism == "deposit" { - ic_cdk::println!("deposit"); - - ic_cdk::api::management_canister::main::deposit_cycles( - ic_cdk::api::management_canister::main::CanisterIdRecord { - canister_id: principal, - }, - payment_amount, - ) - .await - .unwrap(); - } - - ic_cdk::println!("successfully sent {} cycles\n\n", payment_amount); - - // TODO add ledger - - // TODO should we error out or just log if this is not supported? - // ic_cdk::println!( - // "payment_mechanism \"{}\" is not supported", - // dependency.payment_mechanism - // ); +#[ic_cdk_macros::update] +pub async fn _azle_open_value_sharing_periodic_payment() { + open_value_sharing_periodic_payment().await; } diff --git a/src/compiler/rust/open_value_sharing/Cargo.toml b/src/compiler/rust/open_value_sharing/Cargo.toml new file mode 100644 index 0000000000..a59d663b2b --- /dev/null +++ b/src/compiler/rust/open_value_sharing/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "open_value_sharing" +version = "0.0.0" +edition = "2018" + +[dependencies] +serde = "1.0.202" +serde_json = "1.0.107" +candid = "0.10.2" + +# TODO this is just for logging +ic-cdk = "0.12.1" diff --git a/src/compiler/rust/open_value_sharing/src/lib.rs b/src/compiler/rust/open_value_sharing/src/lib.rs new file mode 100644 index 0000000000..1d0016053a --- /dev/null +++ b/src/compiler/rust/open_value_sharing/src/lib.rs @@ -0,0 +1,179 @@ +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct Dependency { + name: String, + weight: u32, + platform: String, + asset: String, + payment_mechanism: String, + custom: std::collections::HashMap, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct CompilerInfo { + canister_methods: CanisterMethods, + env_vars: Vec<(String, String)>, + dependency_info: Vec>, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct CanisterMethods { + init: Option, + post_upgrade: Option, + pre_upgrade: Option, + inspect_message: Option, + heartbeat: Option, + queries: Vec, + updates: Vec, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct CanisterMethod { + name: String, + composite: Option, + guard_name: Option, +} + +// TODO we should probably keep a log of payments right? +// TODO so that devs can check that it's been done correctly? +// TODO do we need error handling?? +// TODO it is much more scalable without error handling +// TODO also it's quick and nimble if it's just best effort +// TODO we can also just spawn off timers until they are all done +// TODO we need some way to store state...across upgrades +// TODO the timer always needs to be set across upgrades +// TODO notify runs out after 500 because of the canister outgoing queue +// TODO calling wallet_receive seems to have no limit, takes few cycles, at least within the same subnet +// TODO management canister, which might be across subnets as an emulation, takes the longest +// TODO not sure if there's any practical limit though + +// TODO figure out how to get this to work on mainnet and locally well +pub async fn open_value_sharing_periodic_payment() { + ic_cdk::println!("open_value_sharing_periodic_payment"); + + let compiler_info = get_compiler_info("compiler_info.json").unwrap(); + + let total_periodic_payment_amount = calculate_total_periodic_payment_amount().await; + + ic_cdk::println!( + "total_periodic_payment_amount: {}", + total_periodic_payment_amount + ); + + for (depth, dependency_level) in compiler_info.dependency_info.iter().enumerate() { + ic_cdk::println!("depth: {}", depth); + ic_cdk::println!("dependency_level: {:#?}", dependency_level); + + for dependency in dependency_level { + ic_cdk::println!("dependency: {:#?}", dependency); + + let dependency_periodic_payment_amount = calculate_dependency_periodic_payment_amount( + dependency, + dependency_level, + depth, + total_periodic_payment_amount, + depth == compiler_info.dependency_info.len() - 1, + ); + + if dependency.platform == "icp" { + handle_icp_platform(dependency, dependency_periodic_payment_amount).await; + } + } + } +} + +fn get_compiler_info(compiler_info_path: &str) -> Result { + let compiler_info_string = std::fs::read_to_string(compiler_info_path) + .map_err(|err| format!("Error reading {compiler_info_path}: {err}"))?; + let compiler_info: CompilerInfo = serde_json::from_str(&compiler_info_string) + .map_err(|err| format!("Error parsing {compiler_info_path}: {err}"))?; + + Ok(compiler_info) +} + +// TODO do all of the balance and previous calculation stuff here +async fn calculate_total_periodic_payment_amount() -> u128 { + 1_000_000 +} + +// TODO we also need to take into account the total number of levels +// TODO for example if there is only one level you don't need to cut anything in half +// TODO double-check the weight calculation +fn calculate_dependency_periodic_payment_amount( + dependency: &Dependency, + dependency_level: &Vec, + depth: usize, + total_periodic_payment_amount: u128, + bottom: bool, +) -> u128 { + let adjusted_depth = depth + if bottom { 0 } else { 1 }; + + let dependency_level_periodic_payment_amount = + total_periodic_payment_amount / 2_u128.pow(adjusted_depth as u32); + + ic_cdk::println!( + "dependency_level_periodic_payment_amount: {}", + dependency_level_periodic_payment_amount + ); + + let total_dependency_level_weight: u32 = dependency_level + .iter() + .map(|dependency| dependency.weight) + .sum(); + + let dependency_ratio = dependency.weight as f64 / total_dependency_level_weight as f64; + + (dependency_level_periodic_payment_amount as f64 * dependency_ratio) as u128 +} + +async fn handle_icp_platform(dependency: &Dependency, payment_amount: u128) { + if dependency.asset == "cycles" { + handle_icp_platform_asset_cycles(dependency, payment_amount).await; + } +} + +async fn handle_icp_platform_asset_cycles(dependency: &Dependency, payment_amount: u128) { + let principal_string = dependency + .custom + .get("principal") + .unwrap() + .as_str() + .unwrap() + .to_string(); + let principal = candid::Principal::from_text(principal_string).unwrap(); + + if dependency.payment_mechanism == "wallet" { + ic_cdk::println!("wallet"); + + ic_cdk::api::call::call_with_payment128::<(Option<()>,), ()>( + principal, + "wallet_receive", + (None,), + payment_amount, + ) // TODO do we need to specify None? + .await + .unwrap(); + } + + if dependency.payment_mechanism == "deposit" { + ic_cdk::println!("deposit"); + + ic_cdk::api::management_canister::main::deposit_cycles( + ic_cdk::api::management_canister::main::CanisterIdRecord { + canister_id: principal, + }, + payment_amount, + ) + .await + .unwrap(); + } + + ic_cdk::println!("successfully sent {} cycles\n\n", payment_amount); + + // TODO add ledger + + // TODO should we error out or just log if this is not supported? + // ic_cdk::println!( + // "payment_mechanism \"{}\" is not supported", + // dependency.payment_mechanism + // ); +}