From 46dcca8ebf45b6a264734f479fff1adbff69ad25 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Tue, 29 Aug 2023 13:48:27 +0200 Subject: [PATCH 1/3] In seal0, the balance API function is supposed to return the free balance Signed-off-by: Cyrill Leutwiler --- .../frame/contracts/fixtures/balance.wat | 42 +++++++++++++++ substrate/frame/contracts/fixtures/drain.wat | 35 ++++++++++-- substrate/frame/contracts/src/exec.rs | 8 ++- substrate/frame/contracts/src/tests.rs | 53 +++++++++++++++++++ 4 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 substrate/frame/contracts/fixtures/balance.wat diff --git a/substrate/frame/contracts/fixtures/balance.wat b/substrate/frame/contracts/fixtures/balance.wat new file mode 100644 index 000000000000..d86d5c4b1c60 --- /dev/null +++ b/substrate/frame/contracts/fixtures/balance.wat @@ -0,0 +1,42 @@ +(module + (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) reserved for $seal_balance output + + ;; [8, 16) length of the buffer for $seal_balance + (data (i32.const 8) "\08") + + ;; [16, inf) zero initialized + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + (call $seal_balance (i32.const 0) (i32.const 8)) + + ;; Balance should be encoded as a u64. + (call $assert + (i32.eq + (i32.load (i32.const 8)) + (i32.const 8) + ) + ) + + ;; Assert the free balance to be zero. + (call $assert + (i64.eq + (i64.load (i32.const 0)) + (i64.const 0) + ) + ) + ) +) diff --git a/substrate/frame/contracts/fixtures/drain.wat b/substrate/frame/contracts/fixtures/drain.wat index 9f126898fac8..cb8ff0aed61f 100644 --- a/substrate/frame/contracts/fixtures/drain.wat +++ b/substrate/frame/contracts/fixtures/drain.wat @@ -1,14 +1,20 @@ (module (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) + (import "seal0" "seal_minimum_balance" (func $seal_minimum_balance (param i32 i32))) (import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; [0, 8) reserved for $seal_balance output - ;; [8, 16) length of the buffer + ;; [8, 16) length of the buffer for $seal_balance (data (i32.const 8) "\08") - ;; [16, inf) zero initialized + ;; [16, 24) reserved for $seal_minimum_balance + + ;; [24, 32) length of the buffer for $seal_minimum_balance + (data (i32.const 24) "\08") + + ;; [32, inf) zero initialized (func $assert (param i32) (block $ok @@ -33,13 +39,32 @@ ) ) - ;; Try to self-destruct by sending full balance to the 0 address. + ;; Get the minimum balance. + (call $seal_minimum_balance (i32.const 16) (i32.const 24)) + + ;; Minimum balance should be encoded as a u64. + (call $assert + (i32.eq + (i32.load (i32.const 24)) + (i32.const 8) + ) + ) + + ;; Make the transferred value exceed the balance by adding the minimum balance. + (i64.store (i32.const 0) + (i64.add + (i64.load (i32.const 0)) + (i64.load (i32.const 16)) + ) + ) + + ;; Try to self-destruct by sending more balance to the 0 address. ;; The call will fail because a contract transfer has a keep alive requirement (call $assert (i32.eq (call $seal_transfer - (i32.const 16) ;; Pointer to destination address - (i32.const 32) ;; Length of destination address + (i32.const 32) ;; Pointer to destination address + (i32.const 48) ;; Length of destination address (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer ) diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 1ba44220ff8d..e50d94066818 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -32,7 +32,7 @@ use frame_support::{ storage::{with_transaction, TransactionOutcome}, traits::{ fungible::{Inspect, Mutate}, - tokens::Preservation, + tokens::{Fortitude, Preservation}, Contains, OriginTrait, Randomness, Time, }, weights::Weight, @@ -1367,7 +1367,11 @@ where } fn balance(&self) -> BalanceOf { - T::Currency::balance(&self.top_frame().account_id) + T::Currency::reducible_balance( + &self.top_frame().account_id, + Preservation::Preserve, + Fortitude::Polite, + ) } fn value_transferred(&self) -> BalanceOf { diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 8cc6d00b3d45..a666e04f3b31 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -5891,3 +5891,56 @@ fn root_cannot_instantiate() { ); }); } + +#[test] +fn balance_api_returns_free_balance() { + let (wasm, _code_hash) = compile_module::("balance").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract without any extra balance. + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm.to_vec()), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + let value = 0; + // Call BOB which makes it call the balance runtime API. + // The contract code asserts that the returned balance is 0. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + value, + GAS_LIMIT, + None, + vec![] + )); + + let value = 1; + // Calling with value will trap the contract. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + value, + GAS_LIMIT, + None, + vec![] + ), + >::ContractTrapped + ); + }); +} From 625902648a8dd9f4613d98eca29253f9290c7308 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Tue, 29 Aug 2023 15:14:59 +0200 Subject: [PATCH 2/3] Update substrate/frame/contracts/src/tests.rs Co-authored-by: PG Herveou --- substrate/frame/contracts/src/tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index a666e04f3b31..ee5920a5fcc5 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -5914,8 +5914,6 @@ fn balance_api_returns_free_balance() { .unwrap() .account_id; - // Check that the BOB contract has been instantiated. - get_contract(&addr); let value = 0; // Call BOB which makes it call the balance runtime API. From 84155b5199ed70bd2f378c06a39b7a46a1c04256 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Fri, 1 Sep 2023 10:48:21 +0000 Subject: [PATCH 3/3] ".git/.scripts/commands/fmt/fmt.sh" --- substrate/frame/contracts/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index a3b39bc2c646..0c0a2f7f9327 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -5914,7 +5914,6 @@ fn balance_api_returns_free_balance() { .unwrap() .account_id; - let value = 0; // Call BOB which makes it call the balance runtime API. // The contract code asserts that the returned balance is 0.