Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit e8178c9

Browse files
atheixermicusagryaznov
authored
contracts: Don't rely on reserved balances keeping an account alive (#13369)
* Move storage deposits to their own account * Take ed for contract's account from origin * Apply suggestions from code review Co-authored-by: Cyrill Leutwiler <[email protected]> Co-authored-by: Sasha Gryaznov <[email protected]> * Update stale docs * Use 16 bytes prefix for address derivation * Update frame/contracts/src/address.rs Co-authored-by: Cyrill Leutwiler <[email protected]> * Fix merge * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * Update frame/contracts/primitives/src/lib.rs Co-authored-by: Sasha Gryaznov <[email protected]> --------- Co-authored-by: Cyrill Leutwiler <[email protected]> Co-authored-by: Sasha Gryaznov <[email protected]> Co-authored-by: command-bot <>
1 parent 46c1bd5 commit e8178c9

File tree

11 files changed

+2098
-2173
lines changed

11 files changed

+2098
-2173
lines changed

frame/contracts/fixtures/caller_contract.wat

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,11 @@
1616
)
1717
)
1818

19-
(func $current_balance (param $sp i32) (result i64)
20-
(i32.store
21-
(i32.sub (get_local $sp) (i32.const 16))
22-
(i32.const 8)
23-
)
24-
(call $seal_balance
25-
(i32.sub (get_local $sp) (i32.const 8))
26-
(i32.sub (get_local $sp) (i32.const 16))
27-
)
28-
(call $assert
29-
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 16))) (i32.const 8))
30-
)
31-
(i64.load (i32.sub (get_local $sp) (i32.const 8)))
32-
)
33-
3419
(func (export "deploy"))
3520

3621
(func (export "call")
3722
(local $sp i32)
3823
(local $exit_code i32)
39-
(local $balance i64)
4024

4125
;; Length of the buffer
4226
(i32.store (i32.const 20) (i32.const 32))
@@ -54,9 +38,6 @@
5438

5539
;; Read current balance into local variable.
5640
(set_local $sp (i32.const 1024))
57-
(set_local $balance
58-
(call $current_balance (get_local $sp))
59-
)
6041

6142
;; Fail to deploy the contract since it returns a non-zero exit status.
6243
(set_local $exit_code
@@ -82,11 +63,6 @@
8263
(i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
8364
)
8465

85-
;; Check that balance has not changed.
86-
(call $assert
87-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
88-
)
89-
9066
;; Fail to deploy the contract due to insufficient gas.
9167
(set_local $exit_code
9268
(call $seal_instantiate
@@ -112,11 +88,6 @@
11288
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
11389
)
11490

115-
;; Check that balance has not changed.
116-
(call $assert
117-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
118-
)
119-
12091
;; Length of the output buffer
12192
(i32.store
12293
(i32.sub (get_local $sp) (i32.const 4))
@@ -153,14 +124,6 @@
153124
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 32))
154125
)
155126

156-
;; Check that balance has been deducted.
157-
(set_local $balance
158-
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
159-
)
160-
(call $assert
161-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
162-
)
163-
164127
;; Zero out destination buffer of output
165128
(i32.store
166129
(i32.sub (get_local $sp) (i32.const 4))
@@ -204,11 +167,6 @@
204167
)
205168
)
206169

207-
;; Check that balance has not changed.
208-
(call $assert
209-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
210-
)
211-
212170
;; Fail to call the contract due to insufficient gas.
213171
(set_local $exit_code
214172
(call $seal_call
@@ -229,11 +187,6 @@
229187
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
230188
)
231189

232-
;; Check that balance has not changed.
233-
(call $assert
234-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
235-
)
236-
237190
;; Zero out destination buffer of output
238191
(i32.store
239192
(i32.sub (get_local $sp) (i32.const 4))
@@ -276,14 +229,6 @@
276229
(i32.const 0x77665544)
277230
)
278231
)
279-
280-
;; Check that balance has been deducted.
281-
(set_local $balance
282-
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
283-
)
284-
(call $assert
285-
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
286-
)
287232
)
288233

289234
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.

frame/contracts/fixtures/drain.wat

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
)
3535

3636
;; Try to self-destruct by sending full balance to the 0 address.
37-
;; All the *free* balance will be send away, which is a valid thing to do
38-
;; because the storage deposits will keep the account alive.
37+
;; The call will fail because a contract transfer has a keep alive requirement
3938
(call $assert
4039
(i32.eq
4140
(call $seal_transfer
@@ -44,7 +43,7 @@
4443
(i32.const 0) ;; Pointer to the buffer with value to transfer
4544
(i32.const 8) ;; Length of the buffer with value to transfer
4645
)
47-
(i32.const 0) ;; ReturnCode::Success
46+
(i32.const 5) ;; ReturnCode::TransferFailed
4847
)
4948
)
5049
)

frame/contracts/primitives/src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ pub struct ContractResult<R, Balance> {
4747
/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
4848
/// when a non-zero `gas_limit` argument is supplied.
4949
pub gas_required: Weight,
50-
/// How much balance was deposited and reserved during execution in order to pay for storage.
50+
/// How much balance was paid by the origin into the contract's deposit account in order to
51+
/// pay for storage.
5152
///
52-
/// The storage deposit is never actually charged from the caller in case of [`Self::result`]
53-
/// is `Err`. This is because on error all storage changes are rolled back.
53+
/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
54+
/// is `Err`. This is because on error all storage changes are rolled back including the
55+
/// payment of the deposit.
5456
pub storage_deposit: StorageDeposit<Balance>,
5557
/// An optional debug message. This message is only filled when explicitly requested
5658
/// by the code that calls into the contract. Otherwise it is empty.
@@ -159,12 +161,12 @@ pub enum StorageDeposit<Balance> {
159161
/// The transaction reduced storage consumption.
160162
///
161163
/// This means that the specified amount of balance was transferred from the involved
162-
/// contracts to the call origin.
164+
/// deposit accounts to the origin.
163165
Refund(Balance),
164-
/// The transaction increased overall storage usage.
166+
/// The transaction increased storage consumption.
165167
///
166-
/// This means that the specified amount of balance was transferred from the call origin
167-
/// to the contracts involved.
168+
/// This means that the specified amount of balance was transferred from the origin
169+
/// to the involved deposit accounts.
168170
Charge(Balance),
169171
}
170172

frame/contracts/src/address.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! Functions that deal with address derivation.
19+
20+
use crate::{CodeHash, Config};
21+
use codec::{Decode, Encode};
22+
use sp_runtime::traits::{Hash, TrailingZeroInput};
23+
24+
/// Provides the contract address generation method.
25+
///
26+
/// See [`DefaultAddressGenerator`] for the default implementation.
27+
///
28+
/// # Note for implementors
29+
///
30+
/// 1. Make sure that there are no collisions, different inputs never lead to the same output.
31+
/// 2. Make sure that the same inputs lead to the same output.
32+
pub trait AddressGenerator<T: Config> {
33+
/// The address of a contract based on the given instantiate parameters.
34+
///
35+
/// Changing the formular for an already deployed chain is fine as long as no collisons
36+
/// with the old formular. Changes only affect existing contracts.
37+
fn contract_address(
38+
deploying_address: &T::AccountId,
39+
code_hash: &CodeHash<T>,
40+
input_data: &[u8],
41+
salt: &[u8],
42+
) -> T::AccountId;
43+
44+
/// The address of the deposit account of `contract_addr`.
45+
///
46+
/// The address is generated once on instantiation and then stored in the contracts
47+
/// metadata. Hence changes do only affect newly created contracts.
48+
fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId;
49+
}
50+
51+
/// Default address generator.
52+
///
53+
/// This is the default address generator used by contract instantiation. Its result
54+
/// is only dependent on its inputs. It can therefore be used to reliably predict the
55+
/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There
56+
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
57+
/// Formula:
58+
/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
59+
pub struct DefaultAddressGenerator;
60+
61+
impl<T: Config> AddressGenerator<T> for DefaultAddressGenerator {
62+
/// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
63+
fn contract_address(
64+
deploying_address: &T::AccountId,
65+
code_hash: &CodeHash<T>,
66+
input_data: &[u8],
67+
salt: &[u8],
68+
) -> T::AccountId {
69+
let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt)
70+
.using_encoded(T::Hashing::hash);
71+
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
72+
.expect("infinite length input; no invalid inputs for type; qed")
73+
}
74+
75+
/// Formula: `hash("contract_depo_v1" ++ contract_addr)`
76+
fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId {
77+
let entropy = (b"contract_depo_v1", contract_addr).using_encoded(T::Hashing::hash);
78+
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
79+
.expect("infinite length input; no invalid inputs for type; qed")
80+
}
81+
}

0 commit comments

Comments
 (0)