diff --git a/cross-contract-calls-advanced/.gitignore b/cross-contract-calls-advanced/.gitignore new file mode 100644 index 00000000..bf910de1 --- /dev/null +++ b/cross-contract-calls-advanced/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/cross-contract-calls-advanced/Cargo.toml b/cross-contract-calls-advanced/Cargo.toml new file mode 100755 index 00000000..534d2dfc --- /dev/null +++ b/cross-contract-calls-advanced/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "cross-contract-calls" +version = "6.0.0-beta.1" +authors = ["Use Ink "] +edition = "2024" +publish = false + +[dependencies] +ink = { version = "6.0.0-beta.1", default-features = false } + +# Note: We **need** to specify the `ink-as-dependency` feature. +# +# If we don't we will end up with linking errors! +other-contract = { path = "other-contract", default-features = false, features = ["ink-as-dependency"] } +pallet-revive-uapi = { version = "0.8.0", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "6.0.0-beta.1" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + # Note: The metadata generation step requires `std`. If we don't specify this the metadata + # generation for our contract will fail! + "other-contract/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" diff --git a/cross-contract-calls-advanced/e2e_tests.rs b/cross-contract-calls-advanced/e2e_tests.rs new file mode 100644 index 00000000..dd0061c0 --- /dev/null +++ b/cross-contract-calls-advanced/e2e_tests.rs @@ -0,0 +1,143 @@ +use super::cross_contract_calls::*; +use ink_e2e::ContractsBackend; + +type E2EResult = std::result::Result>; + +#[ink_e2e::test] +async fn instantiate_with_insufficient_storage_deposit_limit( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500; + const PROOF_SIZE_LIMIT: u64 = 100_000_000_000; + let storage_deposit_limit = ink::U256::from(100_000_000_000_000u64); + + let mut constructor = CrossContractCallsRef::new_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_SIZE_LIMIT, + storage_deposit_limit, + ); + let call_result = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .dry_run() + .await?; + + assert!(call_result.did_revert()); + let err_msg = String::from_utf8_lossy(call_result.return_data()); + assert!(err_msg.contains( + "Cross-contract instantiation failed with ReturnError(OutOfResources)" + )); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_with_sufficient_limits( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500_000_000_000_000; + const PROOF_SIZE_LIMIT: u64 = 100_000_000_000; + // todo remove the last group of `000` to get an `OutOfGas` error in + // `pallet-revive`. but they should throw an error about `StorageLimitExhausted`. + let storage_deposit_limit = ink::U256::from(100_000_000_000_000u64); + + let mut constructor = CrossContractCallsRef::new_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_SIZE_LIMIT, + storage_deposit_limit, + ); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_no_limits(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = + CrossContractCallsRef::new_no_limits(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); + + Ok(()) +} + +#[ink_e2e::test] +async fn flip_and_get(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = + CrossContractCallsRef::new_no_limits(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("cross-contract-calls instantiate failed"); + let mut call_builder = contract.call_builder::(); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_SIZE_LIMIT: u64 = 100_000; + let storage_deposit_limit = ink::U256::from(1_000_000_000); + + // when + let call = call_builder.flip_and_get_invoke_with_limits( + REF_TIME_LIMIT, + PROOF_SIZE_LIMIT, + storage_deposit_limit, + ); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get_invoke_with_limits` failed") + .return_value(); + + assert!(!result); + + let call = call_builder.flip_and_get_invoke_no_weight_limit(); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get_invoke_no_weight_limit` failed") + .return_value(); + + assert!(result); + + Ok(()) +} diff --git a/cross-contract-calls-advanced/lib.rs b/cross-contract-calls-advanced/lib.rs new file mode 100755 index 00000000..2106df75 --- /dev/null +++ b/cross-contract-calls-advanced/lib.rs @@ -0,0 +1,90 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod cross_contract_calls { + use ink::codegen::TraitCallBuilder; + use other_contract::OtherContractRef; + + #[ink(storage)] + pub struct CrossContractCalls { + other_contract: OtherContractRef, + } + + impl CrossContractCalls { + /// Initializes the contract by instantiating the code at the given code hash via + /// `instantiate` host function with the supplied weight and storage + /// limits. + #[ink(constructor)] + pub fn new_with_limits( + other_contract_code_hash: ink::H256, + ref_time_limit: u64, + proof_size_limit: u64, + storage_deposit_limit: ink::U256, + ) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0.into()) + .salt_bytes(Some([1u8; 32])) + .ref_time_limit(ref_time_limit) + .proof_size_limit(proof_size_limit) + .storage_deposit_limit(storage_deposit_limit) + .instantiate(); + + Self { other_contract } + } + + /// Initializes the contract by instantiating the code at the given code hash via + /// the `instantiate` host function with no weight or storage limits. + #[ink(constructor)] + pub fn new_no_limits(other_contract_code_hash: ink::H256) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0.into()) + .salt_bytes(Some([1u8; 32])) + .instantiate(); + + Self { other_contract } + } + + /// Use the `call` host function via the call builder to forward calls to + /// the other contract, initially calling `flip` and then `get` to return the + /// result. + /// + /// This demonstrates how to set the new weight and storage limit parameters via + /// the call builder API. + #[ink(message)] + pub fn flip_and_get_invoke_with_limits( + &mut self, + ref_time_limit: u64, + proof_size_limit: u64, + storage_deposit_limit: ink::U256, + ) -> bool { + let call_builder = self.other_contract.call_mut(); + + call_builder + .flip() + .ref_time_limit(ref_time_limit) + .proof_size_limit(proof_size_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke(); + + call_builder + .get() + .ref_time_limit(ref_time_limit) + .proof_size_limit(proof_size_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke() + } + + /// Demonstrate that the `call` succeeds without having specified the weight + /// and storage limit parameters + #[ink(message)] + pub fn flip_and_get_invoke_no_weight_limit(&mut self) -> bool { + self.other_contract.flip(); + self.other_contract.get() + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests; diff --git a/cross-contract-calls-advanced/other-contract/Cargo.toml b/cross-contract-calls-advanced/other-contract/Cargo.toml new file mode 100755 index 00000000..3ac88b1e --- /dev/null +++ b/cross-contract-calls-advanced/other-contract/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "other-contract" +version = "6.0.0-beta.1" +authors = ["Use Ink "] +edition = "2024" +publish = false + +[dependencies] +ink = { version = "6.0.0-beta.1", default-features = false } +pallet-revive-uapi = { version = "0.8.0", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "6.0.0-beta.1" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" diff --git a/cross-contract-calls-advanced/other-contract/lib.rs b/cross-contract-calls-advanced/other-contract/lib.rs new file mode 100755 index 00000000..132ecbec --- /dev/null +++ b/cross-contract-calls-advanced/other-contract/lib.rs @@ -0,0 +1,27 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod other_contract { + + #[ink(storage)] + pub struct OtherContract { + value: bool, + } + + impl OtherContract { + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } +} diff --git a/cross-contract-calls/e2e_tests.rs b/cross-contract-calls/e2e_tests.rs index dd0061c0..bb4fe2e4 100644 --- a/cross-contract-calls/e2e_tests.rs +++ b/cross-contract-calls/e2e_tests.rs @@ -1,92 +1,28 @@ use super::cross_contract_calls::*; +use other_contract::OtherContractRef; use ink_e2e::ContractsBackend; type E2EResult = std::result::Result>; -#[ink_e2e::test] -async fn instantiate_with_insufficient_storage_deposit_limit( - mut client: Client, -) -> E2EResult<()> { - // given - let other_contract_code = client - .upload("other-contract", &ink_e2e::alice()) - .submit() - .await - .expect("other_contract upload failed"); - - const REF_TIME_LIMIT: u64 = 500; - const PROOF_SIZE_LIMIT: u64 = 100_000_000_000; - let storage_deposit_limit = ink::U256::from(100_000_000_000_000u64); - - let mut constructor = CrossContractCallsRef::new_with_limits( - other_contract_code.code_hash, - REF_TIME_LIMIT, - PROOF_SIZE_LIMIT, - storage_deposit_limit, - ); - let call_result = client - .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) - .dry_run() - .await?; - - assert!(call_result.did_revert()); - let err_msg = String::from_utf8_lossy(call_result.return_data()); - assert!(err_msg.contains( - "Cross-contract instantiation failed with ReturnError(OutOfResources)" - )); - - Ok(()) -} - -#[ink_e2e::test] -async fn instantiate_with_sufficient_limits( - mut client: Client, -) -> E2EResult<()> { - // given - let other_contract_code = client - .upload("other-contract", &ink_e2e::alice()) - .submit() - .await - .expect("other_contract upload failed"); - - const REF_TIME_LIMIT: u64 = 500_000_000_000_000; - const PROOF_SIZE_LIMIT: u64 = 100_000_000_000; - // todo remove the last group of `000` to get an `OutOfGas` error in - // `pallet-revive`. but they should throw an error about `StorageLimitExhausted`. - let storage_deposit_limit = ink::U256::from(100_000_000_000_000u64); - - let mut constructor = CrossContractCallsRef::new_with_limits( - other_contract_code.code_hash, - REF_TIME_LIMIT, - PROOF_SIZE_LIMIT, - storage_deposit_limit, - ); - let contract = client - .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) - .submit() - .await; - - assert!(contract.is_ok(), "{}", contract.err().unwrap()); - - Ok(()) -} - #[ink_e2e::test] async fn instantiate_no_limits(mut client: Client) -> E2EResult<()> { // given - let other_contract_code = client - .upload("other-contract", &ink_e2e::alice()) + let mut other_constructor = OtherContractRef::new(true); + let other_contract = client + .instantiate("other-contract", &ink_e2e::alice(), &mut other_constructor) .submit() .await - .expect("other_contract upload failed"); + .expect("other-contract instantiate failed"); + // when let mut constructor = - CrossContractCallsRef::new_no_limits(other_contract_code.code_hash); + CrossContractCallsRef::new(other_contract.addr); let contract = client .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) .submit() .await; + // then assert!(contract.is_ok(), "{}", contract.err().unwrap()); Ok(()) @@ -95,14 +31,15 @@ async fn instantiate_no_limits(mut client: Client) -> E2ERes #[ink_e2e::test] async fn flip_and_get(mut client: Client) -> E2EResult<()> { // given - let other_contract_code = client - .upload("other-contract", &ink_e2e::alice()) + let mut other_constructor = OtherContractRef::new(true); + let other_contract = client + .instantiate("other-contract", &ink_e2e::alice(), &mut other_constructor) .submit() .await - .expect("other_contract upload failed"); + .expect("other-contract instantiate failed"); let mut constructor = - CrossContractCallsRef::new_no_limits(other_contract_code.code_hash); + CrossContractCallsRef::new(other_contract.addr); let contract = client .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) .submit() @@ -110,34 +47,29 @@ async fn flip_and_get(mut client: Client) -> E2EResult<()> { .expect("cross-contract-calls instantiate failed"); let mut call_builder = contract.call_builder::(); - const REF_TIME_LIMIT: u64 = 500_000_000; - const PROOF_SIZE_LIMIT: u64 = 100_000; - let storage_deposit_limit = ink::U256::from(1_000_000_000); - // when - let call = call_builder.flip_and_get_invoke_with_limits( - REF_TIME_LIMIT, - PROOF_SIZE_LIMIT, - storage_deposit_limit, - ); + let call = call_builder.flip_and_get(); let result = client .call(&ink_e2e::alice(), &call) .submit() .await - .expect("Calling `flip_and_get_invoke_with_limits` failed") + .expect("Calling `flip_and_get` failed") .return_value(); + // then assert!(!result); - let call = call_builder.flip_and_get_invoke_no_weight_limit(); + // when + let call = call_builder.flip_and_get(); let result = client .call(&ink_e2e::alice(), &call) .submit() .await - .expect("Calling `flip_and_get_invoke_no_weight_limit` failed") + .expect("Calling `flip_and_get` failed") .return_value(); + // then assert!(result); Ok(()) -} +} \ No newline at end of file diff --git a/cross-contract-calls/lib.rs b/cross-contract-calls/lib.rs index 2106df75..8885915b 100755 --- a/cross-contract-calls/lib.rs +++ b/cross-contract-calls/lib.rs @@ -2,84 +2,29 @@ #[ink::contract] mod cross_contract_calls { - use ink::codegen::TraitCallBuilder; use other_contract::OtherContractRef; #[ink(storage)] pub struct CrossContractCalls { + /// Store a contract ref derived from the `address` of an instance `OtherContract`. other_contract: OtherContractRef, } impl CrossContractCalls { - /// Initializes the contract by instantiating the code at the given code hash via - /// `instantiate` host function with the supplied weight and storage - /// limits. + /// Initializes the contract with a contract ref + /// derived from the `address` of an instance `OtherContract`. #[ink(constructor)] - pub fn new_with_limits( - other_contract_code_hash: ink::H256, - ref_time_limit: u64, - proof_size_limit: u64, - storage_deposit_limit: ink::U256, - ) -> Self { - let other_contract = OtherContractRef::new(true) - .code_hash(other_contract_code_hash) - .endowment(0.into()) - .salt_bytes(Some([1u8; 32])) - .ref_time_limit(ref_time_limit) - .proof_size_limit(proof_size_limit) - .storage_deposit_limit(storage_deposit_limit) - .instantiate(); - - Self { other_contract } - } - - /// Initializes the contract by instantiating the code at the given code hash via - /// the `instantiate` host function with no weight or storage limits. - #[ink(constructor)] - pub fn new_no_limits(other_contract_code_hash: ink::H256) -> Self { - let other_contract = OtherContractRef::new(true) - .code_hash(other_contract_code_hash) - .endowment(0.into()) - .salt_bytes(Some([1u8; 32])) - .instantiate(); - + pub fn new(other_contract_address: ink::Address) -> Self { + // Note: In the future, this will be replaced by: + // let other_contract = OtherContractRef::from(other_contract_address); + let other_contract = ink::env::call::FromAddr::from_addr(other_contract_address); Self { other_contract } } - /// Use the `call` host function via the call builder to forward calls to - /// the other contract, initially calling `flip` and then `get` to return the - /// result. - /// - /// This demonstrates how to set the new weight and storage limit parameters via - /// the call builder API. - #[ink(message)] - pub fn flip_and_get_invoke_with_limits( - &mut self, - ref_time_limit: u64, - proof_size_limit: u64, - storage_deposit_limit: ink::U256, - ) -> bool { - let call_builder = self.other_contract.call_mut(); - - call_builder - .flip() - .ref_time_limit(ref_time_limit) - .proof_size_limit(proof_size_limit) - .storage_deposit_limit(storage_deposit_limit) - .invoke(); - - call_builder - .get() - .ref_time_limit(ref_time_limit) - .proof_size_limit(proof_size_limit) - .storage_deposit_limit(storage_deposit_limit) - .invoke() - } - - /// Demonstrate that the `call` succeeds without having specified the weight - /// and storage limit parameters + /// Calls `flip` and `get` on the instance of `OtherContract` + /// and returns the result of `get`. #[ink(message)] - pub fn flip_and_get_invoke_no_weight_limit(&mut self) -> bool { + pub fn flip_and_get(&mut self) -> bool { self.other_contract.flip(); self.other_contract.get() }