mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
contracts: Allow indeterministic instructions off-chain (#12469)
* Allow indetermistic instructions off-chain * Apply suggestions from code review Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * fmt Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
committed by
GitHub
parent
d0dcf008ec
commit
3ae4be8662
@@ -1959,7 +1959,16 @@ impl_runtime_apis! {
|
||||
input_data: Vec<u8>,
|
||||
) -> pallet_contracts_primitives::ContractExecResult<Balance> {
|
||||
let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block);
|
||||
Contracts::bare_call(origin, dest, value, gas_limit, storage_deposit_limit, input_data, true)
|
||||
Contracts::bare_call(
|
||||
origin,
|
||||
dest,
|
||||
value,
|
||||
gas_limit,
|
||||
storage_deposit_limit,
|
||||
input_data,
|
||||
true,
|
||||
pallet_contracts::Determinism::Deterministic,
|
||||
)
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
@@ -1973,23 +1982,41 @@ impl_runtime_apis! {
|
||||
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, Balance>
|
||||
{
|
||||
let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block);
|
||||
Contracts::bare_instantiate(origin, value, gas_limit, storage_deposit_limit, code, data, salt, true)
|
||||
Contracts::bare_instantiate(
|
||||
origin,
|
||||
value,
|
||||
gas_limit,
|
||||
storage_deposit_limit,
|
||||
code,
|
||||
data,
|
||||
salt,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
fn upload_code(
|
||||
origin: AccountId,
|
||||
code: Vec<u8>,
|
||||
storage_deposit_limit: Option<Balance>,
|
||||
determinism: pallet_contracts::Determinism,
|
||||
) -> pallet_contracts_primitives::CodeUploadResult<Hash, Balance>
|
||||
{
|
||||
Contracts::bare_upload_code(origin, code, storage_deposit_limit)
|
||||
Contracts::bare_upload_code(
|
||||
origin,
|
||||
code,
|
||||
storage_deposit_limit,
|
||||
determinism,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_storage(
|
||||
address: AccountId,
|
||||
key: Vec<u8>,
|
||||
) -> pallet_contracts_primitives::GetStorageResult {
|
||||
Contracts::get_storage(address, key)
|
||||
Contracts::get_storage(
|
||||
address,
|
||||
key
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,37 @@ changes still persist.
|
||||
One gas is equivalent to one [weight](https://docs.substrate.io/v3/runtime/weights-and-fees)
|
||||
which is defined as one picosecond of execution time on the runtime's reference machine.
|
||||
|
||||
### Notable Scenarios
|
||||
### Revert Behaviour
|
||||
|
||||
Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
|
||||
Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up",
|
||||
and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
|
||||
fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
|
||||
|
||||
### Offchain Execution
|
||||
|
||||
In general, a contract execution needs to be deterministic so that all nodes come to the same
|
||||
conclusion when executing it. To that end we disallow any instructions that could cause
|
||||
indeterminism. Most notable are any floating point arithmetic. That said, sometimes contracts
|
||||
are executed off-chain and hence are not subject to consensus. If code is only executed by a
|
||||
single node and implicitly trusted by other actors is such a case. Trusted execution environments
|
||||
come to mind. To that end we allow the execution of indeterminstic code for offchain usages
|
||||
with the following constraints:
|
||||
|
||||
1. No contract can ever be instantiated from an indeterministic code. The only way to execute
|
||||
the code is to use a delegate call from a deterministic contract.
|
||||
2. The code that wants to use this feature needs to depend on `pallet-contracts` and use `bare_call`
|
||||
directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism.
|
||||
|
||||
## How to use
|
||||
|
||||
When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback`
|
||||
to a non zero value. The default is `0` and prevents the upload of any non deterministic code.
|
||||
|
||||
An indeterministic code can be deployed on-chain by passing `Determinism::AllowIndeterministic`
|
||||
to `upload_code`. A determinstic contract can then delegate call into it if and only if it
|
||||
is ran by using `bare_call` and passing `Determinism::AllowIndeterministic` to it. **Never use
|
||||
this argument when the contract is called from an on-chain transaction.**
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable functions
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
;; Just delegate call into the passed code hash and assert success.
|
||||
(module
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 3 3))
|
||||
|
||||
;; [0, 32) buffer where input is copied
|
||||
|
||||
;; [32, 36) size of the input buffer
|
||||
(data (i32.const 32) "\20")
|
||||
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "call")
|
||||
;; Reading "callee" code_hash
|
||||
(call $seal_input (i32.const 0) (i32.const 32))
|
||||
|
||||
;; assert input size == 32
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(i32.load (i32.const 32))
|
||||
(i32.const 32)
|
||||
)
|
||||
)
|
||||
|
||||
;; Delegate call into passed code hash
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(call $seal_delegate_call
|
||||
(i32.const 0) ;; Set no call flags
|
||||
(i32.const 0) ;; Pointer to "callee" code_hash.
|
||||
(i32.const 0) ;; Input is ignored
|
||||
(i32.const 0) ;; Length of the input
|
||||
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
|
||||
(i32.const 0) ;; Length is ignored in this case
|
||||
)
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
;; Module that contains a float instruction which is illegal in deterministic mode
|
||||
(module
|
||||
(func (export "call")
|
||||
f32.const 1
|
||||
drop
|
||||
)
|
||||
(func (export "deploy")
|
||||
f32.const 2
|
||||
drop
|
||||
)
|
||||
)
|
||||
@@ -24,7 +24,7 @@
|
||||
//! we define this simple definition of a contract that can be passed to `create_code` that
|
||||
//! compiles it down into a `WasmModule` that can be used as a contract's code.
|
||||
|
||||
use crate::Config;
|
||||
use crate::{Config, Determinism};
|
||||
use frame_support::traits::Get;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::traits::Hash;
|
||||
@@ -554,7 +554,7 @@ where
|
||||
|
||||
fn inject_gas_metering<T: Config>(module: Module) -> Module {
|
||||
let schedule = T::Schedule::get();
|
||||
let gas_rules = schedule.rules(&module);
|
||||
let gas_rules = schedule.rules(&module, Determinism::Deterministic);
|
||||
wasm_instrument::gas_metering::inject(module, &gas_rules, "seal0").unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ benchmarks! {
|
||||
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
|
||||
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
|
||||
let origin = RawOrigin::Signed(caller.clone());
|
||||
}: _(origin, code, None)
|
||||
}: _(origin, code, None, Determinism::Deterministic)
|
||||
verify {
|
||||
// uploading the code reserves some balance in the callers account
|
||||
assert!(T::Currency::reserved_balance(&caller) > 0u32.into());
|
||||
@@ -386,7 +386,7 @@ benchmarks! {
|
||||
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
|
||||
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy();
|
||||
let origin = RawOrigin::Signed(caller.clone());
|
||||
let uploaded = <Contracts<T>>::bare_upload_code(caller.clone(), code, None)?;
|
||||
let uploaded = <Contracts<T>>::bare_upload_code(caller.clone(), code, None, Determinism::Deterministic)?;
|
||||
assert_eq!(uploaded.code_hash, hash);
|
||||
assert_eq!(uploaded.deposit, T::Currency::reserved_balance(&caller));
|
||||
assert!(<Contract<T>>::code_exists(&hash));
|
||||
@@ -2894,6 +2894,7 @@ benchmarks! {
|
||||
None,
|
||||
data,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result?;
|
||||
}
|
||||
@@ -2941,6 +2942,7 @@ benchmarks! {
|
||||
None,
|
||||
data,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result?;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
use crate::{
|
||||
gas::GasMeter,
|
||||
storage::{self, Storage, WriteOutcome},
|
||||
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Nonce,
|
||||
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Determinism, Error, Event, Nonce,
|
||||
Pallet as Contracts, Schedule,
|
||||
};
|
||||
use frame_support::{
|
||||
@@ -355,6 +355,9 @@ pub trait Executable<T: Config>: Sized {
|
||||
|
||||
/// Size of the instrumented code in bytes.
|
||||
fn code_len(&self) -> u32;
|
||||
|
||||
/// The code does not contain any instructions which could lead to indeterminism.
|
||||
fn is_deterministic(&self) -> bool;
|
||||
}
|
||||
|
||||
/// The complete call stack of a contract execution.
|
||||
@@ -395,6 +398,8 @@ pub struct Stack<'a, T: Config, E> {
|
||||
/// All the bytes added to this field should be valid UTF-8. The buffer has no defined
|
||||
/// structure and is intended to be shown to users as-is for debugging purposes.
|
||||
debug_message: Option<&'a mut Vec<u8>>,
|
||||
/// The determinism requirement of this call stack.
|
||||
determinism: Determinism,
|
||||
/// No executable is held by the struct but influences its behaviour.
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
@@ -601,6 +606,7 @@ where
|
||||
value: BalanceOf<T>,
|
||||
input_data: Vec<u8>,
|
||||
debug_message: Option<&'a mut Vec<u8>>,
|
||||
determinism: Determinism,
|
||||
) -> Result<ExecReturnValue, ExecError> {
|
||||
let (mut stack, executable) = Self::new(
|
||||
FrameArgs::Call { dest, cached_info: None, delegated_call: None },
|
||||
@@ -610,6 +616,7 @@ where
|
||||
schedule,
|
||||
value,
|
||||
debug_message,
|
||||
determinism,
|
||||
)?;
|
||||
stack.run(executable, input_data)
|
||||
}
|
||||
@@ -648,6 +655,7 @@ where
|
||||
schedule,
|
||||
value,
|
||||
debug_message,
|
||||
Determinism::Deterministic,
|
||||
)?;
|
||||
let account_id = stack.top_frame().account_id.clone();
|
||||
stack.run(executable, input_data).map(|ret| (account_id, ret))
|
||||
@@ -662,9 +670,17 @@ where
|
||||
schedule: &'a Schedule<T>,
|
||||
value: BalanceOf<T>,
|
||||
debug_message: Option<&'a mut Vec<u8>>,
|
||||
determinism: Determinism,
|
||||
) -> Result<(Self, E), ExecError> {
|
||||
let (first_frame, executable, nonce) =
|
||||
Self::new_frame(args, value, gas_meter, storage_meter, Weight::zero(), schedule)?;
|
||||
let (first_frame, executable, nonce) = Self::new_frame(
|
||||
args,
|
||||
value,
|
||||
gas_meter,
|
||||
storage_meter,
|
||||
Weight::zero(),
|
||||
schedule,
|
||||
determinism,
|
||||
)?;
|
||||
let stack = Self {
|
||||
origin,
|
||||
schedule,
|
||||
@@ -676,6 +692,7 @@ where
|
||||
first_frame,
|
||||
frames: Default::default(),
|
||||
debug_message,
|
||||
determinism,
|
||||
_phantom: Default::default(),
|
||||
};
|
||||
|
||||
@@ -693,6 +710,7 @@ where
|
||||
storage_meter: &mut storage::meter::GenericMeter<T, S>,
|
||||
gas_limit: Weight,
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
|
||||
let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) =
|
||||
match frame_args {
|
||||
@@ -729,6 +747,15 @@ where
|
||||
},
|
||||
};
|
||||
|
||||
// `AllowIndeterminism` will only be ever set in case of off-chain execution.
|
||||
// Instantiations are never allowed even when executing off-chain.
|
||||
if !(executable.is_deterministic() ||
|
||||
(matches!(determinism, Determinism::AllowIndeterminism) &&
|
||||
matches!(entry_point, ExportedFunction::Call)))
|
||||
{
|
||||
return Err(Error::<T>::Indeterministic.into())
|
||||
}
|
||||
|
||||
let frame = Frame {
|
||||
delegate_caller,
|
||||
value_transferred,
|
||||
@@ -775,6 +802,7 @@ where
|
||||
nested_storage,
|
||||
gas_limit,
|
||||
self.schedule,
|
||||
self.determinism,
|
||||
)?;
|
||||
self.frames.push(frame);
|
||||
Ok(executable)
|
||||
@@ -1328,15 +1356,18 @@ where
|
||||
}
|
||||
|
||||
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
|
||||
let frame = top_frame_mut!(self);
|
||||
if !E::from_storage(hash, self.schedule, &mut frame.nested_gas)?.is_deterministic() {
|
||||
return Err(<Error<T>>::Indeterministic.into())
|
||||
}
|
||||
E::add_user(hash)?;
|
||||
let top_frame = self.top_frame_mut();
|
||||
let prev_hash = top_frame.contract_info().code_hash;
|
||||
let prev_hash = frame.contract_info().code_hash;
|
||||
E::remove_user(prev_hash);
|
||||
top_frame.contract_info().code_hash = hash;
|
||||
frame.contract_info().code_hash = hash;
|
||||
Contracts::<Self::T>::deposit_event(
|
||||
vec![T::Hashing::hash_of(&top_frame.account_id), hash, prev_hash],
|
||||
vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash],
|
||||
Event::ContractCodeUpdated {
|
||||
contract: top_frame.account_id.clone(),
|
||||
contract: frame.account_id.clone(),
|
||||
new_code_hash: hash,
|
||||
old_code_hash: prev_hash,
|
||||
},
|
||||
@@ -1513,6 +1544,10 @@ mod tests {
|
||||
fn code_len(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn is_deterministic(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_success() -> ExecResult {
|
||||
@@ -1551,6 +1586,7 @@ mod tests {
|
||||
value,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
@@ -1604,6 +1640,7 @@ mod tests {
|
||||
value,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1645,6 +1682,7 @@ mod tests {
|
||||
value,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1680,6 +1718,7 @@ mod tests {
|
||||
55,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1731,6 +1770,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
let output = result.unwrap();
|
||||
@@ -1763,6 +1803,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
let output = result.unwrap();
|
||||
@@ -1793,6 +1834,7 @@ mod tests {
|
||||
0,
|
||||
vec![1, 2, 3, 4],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -1873,6 +1915,7 @@ mod tests {
|
||||
value,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
assert_matches!(result, Ok(_));
|
||||
@@ -1918,6 +1961,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
assert_matches!(result, Ok(_));
|
||||
@@ -1951,6 +1995,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -1980,6 +2025,7 @@ mod tests {
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -2007,6 +2053,7 @@ mod tests {
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -2042,6 +2089,7 @@ mod tests {
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -2077,6 +2125,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
assert_matches!(result, Ok(_));
|
||||
@@ -2235,6 +2284,7 @@ mod tests {
|
||||
min_balance * 10,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
@@ -2299,6 +2349,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
@@ -2384,6 +2435,7 @@ mod tests {
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
@@ -2450,6 +2502,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
Some(&mut debug_buffer),
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
@@ -2483,6 +2536,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
Some(&mut debug_buffer),
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
});
|
||||
@@ -2516,6 +2570,7 @@ mod tests {
|
||||
0,
|
||||
CHARLIE.encode(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
|
||||
// Calling into oneself fails
|
||||
@@ -2529,6 +2584,7 @@ mod tests {
|
||||
0,
|
||||
BOB.encode(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
)
|
||||
.map_err(|e| e.error),
|
||||
<Error<Test>>::ReentranceDenied,
|
||||
@@ -2567,6 +2623,7 @@ mod tests {
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
)
|
||||
.map_err(|e| e.error),
|
||||
<Error<Test>>::ReentranceDenied,
|
||||
@@ -2601,6 +2658,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2683,6 +2741,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2886,6 +2945,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3012,6 +3072,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3047,6 +3108,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3082,6 +3144,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3143,6 +3206,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3204,6 +3268,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -3235,6 +3300,7 @@ mod tests {
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
|
||||
@@ -132,6 +132,7 @@ pub use crate::{
|
||||
migration::Migration,
|
||||
pallet::*,
|
||||
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
|
||||
wasm::Determinism,
|
||||
};
|
||||
|
||||
type CodeHash<T> = <T as frame_system::Config>::Hash;
|
||||
@@ -206,7 +207,7 @@ pub mod pallet {
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
/// The current storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(9);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
@@ -456,6 +457,10 @@ pub mod pallet {
|
||||
/// the in storage version to the current
|
||||
/// [`InstructionWeights::version`](InstructionWeights).
|
||||
///
|
||||
/// - `determinism`: If this is set to any other value but [`Determinism::Deterministic`]
|
||||
/// then the only way to use this code is to delegate call into it from an offchain
|
||||
/// execution. Set to [`Determinism::Deterministic`] if in doubt.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Anyone can instantiate a contract from any uploaded code and thus prevent its removal.
|
||||
@@ -467,9 +472,11 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
code: Vec<u8>,
|
||||
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
|
||||
determinism: Determinism,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into)).map(|_| ())
|
||||
Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Remove the code stored under `code_hash` and refund the deposit to its owner.
|
||||
@@ -562,6 +569,7 @@ pub mod pallet {
|
||||
storage_deposit_limit.map(Into::into),
|
||||
data,
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
if let Ok(retval) = &output.result {
|
||||
if retval.did_revert() {
|
||||
@@ -825,6 +833,8 @@ pub mod pallet {
|
||||
/// A more detailed error can be found on the node console if debug messages are enabled
|
||||
/// or in the debug buffer which is returned to RPC clients.
|
||||
CodeRejected,
|
||||
/// An indetermistic code was used in a context where this is not permitted.
|
||||
Indeterministic,
|
||||
}
|
||||
|
||||
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
||||
@@ -921,6 +931,7 @@ where
|
||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||
data: Vec<u8>,
|
||||
debug: bool,
|
||||
determinism: Determinism,
|
||||
) -> ContractExecResult<BalanceOf<T>> {
|
||||
let mut debug_message = if debug { Some(Vec::new()) } else { None };
|
||||
let output = Self::internal_call(
|
||||
@@ -931,6 +942,7 @@ where
|
||||
storage_deposit_limit,
|
||||
data,
|
||||
debug_message.as_mut(),
|
||||
determinism,
|
||||
);
|
||||
ContractExecResult {
|
||||
result: output.result.map_err(|r| r.error),
|
||||
@@ -994,10 +1006,11 @@ where
|
||||
origin: T::AccountId,
|
||||
code: Vec<u8>,
|
||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||
determinism: Determinism,
|
||||
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
|
||||
let schedule = T::Schedule::get();
|
||||
let module =
|
||||
PrefabWasmModule::from_code(code, &schedule, origin).map_err(|(err, _)| err)?;
|
||||
let module = PrefabWasmModule::from_code(code, &schedule, origin, determinism)
|
||||
.map_err(|(err, _)| err)?;
|
||||
let deposit = module.open_deposit();
|
||||
if let Some(storage_deposit_limit) = storage_deposit_limit {
|
||||
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
|
||||
@@ -1067,6 +1080,7 @@ where
|
||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||
data: Vec<u8>,
|
||||
debug_message: Option<&mut Vec<u8>>,
|
||||
determinism: Determinism,
|
||||
) -> InternalCallOutput<T> {
|
||||
let mut gas_meter = GasMeter::new(gas_limit);
|
||||
let mut storage_meter = match StorageMeter::new(&origin, storage_deposit_limit, value) {
|
||||
@@ -1088,6 +1102,7 @@ where
|
||||
value,
|
||||
data,
|
||||
debug_message,
|
||||
determinism,
|
||||
);
|
||||
InternalCallOutput {
|
||||
result,
|
||||
@@ -1115,11 +1130,16 @@ where
|
||||
let schedule = T::Schedule::get();
|
||||
let (extra_deposit, executable) = match code {
|
||||
Code::Upload(binary) => {
|
||||
let executable = PrefabWasmModule::from_code(binary, &schedule, origin.clone())
|
||||
.map_err(|(err, msg)| {
|
||||
debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes()));
|
||||
err
|
||||
})?;
|
||||
let executable = PrefabWasmModule::from_code(
|
||||
binary,
|
||||
&schedule,
|
||||
origin.clone(),
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.map_err(|(err, msg)| {
|
||||
debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes()));
|
||||
err
|
||||
})?;
|
||||
// The open deposit will be charged during execution when the
|
||||
// uploaded module does not already exist. This deposit is not part of the
|
||||
// storage meter because it is not transferred to the contract but
|
||||
@@ -1218,6 +1238,7 @@ sp_api::decl_runtime_apis! {
|
||||
origin: AccountId,
|
||||
code: Vec<u8>,
|
||||
storage_deposit_limit: Option<Balance>,
|
||||
determinism: Determinism,
|
||||
) -> CodeUploadResult<Hash, Balance>;
|
||||
|
||||
/// Query a given storage key in a given contract.
|
||||
|
||||
@@ -32,65 +32,54 @@ use sp_std::{marker::PhantomData, prelude::*};
|
||||
pub struct Migration<T: Config>(PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for Migration<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let version = StorageVersion::get::<Pallet<T>>();
|
||||
let version = <Pallet<T>>::on_chain_storage_version();
|
||||
let mut weight = Weight::zero();
|
||||
|
||||
if version < 4 {
|
||||
weight = weight.saturating_add(v4::migrate::<T>());
|
||||
StorageVersion::new(4).put::<Pallet<T>>();
|
||||
v4::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
if version < 5 {
|
||||
weight = weight.saturating_add(v5::migrate::<T>());
|
||||
StorageVersion::new(5).put::<Pallet<T>>();
|
||||
v5::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
if version < 6 {
|
||||
weight = weight.saturating_add(v6::migrate::<T>());
|
||||
StorageVersion::new(6).put::<Pallet<T>>();
|
||||
v6::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
if version < 7 {
|
||||
weight = weight.saturating_add(v7::migrate::<T>());
|
||||
StorageVersion::new(7).put::<Pallet<T>>();
|
||||
v7::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
if version < 8 {
|
||||
weight = weight.saturating_add(v8::migrate::<T>());
|
||||
StorageVersion::new(8).put::<Pallet<T>>();
|
||||
v8::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
if version < 9 {
|
||||
v9::migrate::<T>(&mut weight);
|
||||
}
|
||||
|
||||
StorageVersion::new(9).put::<Pallet<T>>();
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
|
||||
let version = StorageVersion::get::<Pallet<T>>();
|
||||
let version = <Pallet<T>>::on_chain_storage_version();
|
||||
|
||||
if version < 7 {
|
||||
return Ok(vec![])
|
||||
}
|
||||
|
||||
if version < 8 {
|
||||
if version == 8 {
|
||||
v8::pre_upgrade::<T>()?;
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
Ok(version.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
|
||||
let version = StorageVersion::get::<Pallet<T>>();
|
||||
|
||||
if version < 7 {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if version < 8 {
|
||||
v8::post_upgrade::<T>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), &'static str> {
|
||||
let version = Decode::decode(&mut state.as_ref()).map_err(|_| "Cannot decode version")?;
|
||||
post_checks::post_upgrade::<T>(version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +87,10 @@ impl<T: Config> OnRuntimeUpgrade for Migration<T> {
|
||||
mod v4 {
|
||||
use super::*;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
#[allow(deprecated)]
|
||||
migration::remove_storage_prefix(<Pallet<T>>::name().as_bytes(), b"CurrentSchedule", b"");
|
||||
T::DbWeight::get().writes(1)
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,11 +158,9 @@ mod v5 {
|
||||
#[storage_alias]
|
||||
type DeletionQueue<T: Config> = StorageValue<Pallet<T>, Vec<DeletedContract>>;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
let mut weight = Weight::zero();
|
||||
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
<ContractInfoOf<T>>::translate(|_key, old: OldContractInfo<T>| {
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
match old {
|
||||
OldContractInfo::Alive(old) => Some(ContractInfo::<T> {
|
||||
trie_id: old.trie_id,
|
||||
@@ -185,12 +172,10 @@ mod v5 {
|
||||
});
|
||||
|
||||
DeletionQueue::<T>::translate(|old: Option<Vec<OldDeletedContract>>| {
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect())
|
||||
})
|
||||
.ok();
|
||||
|
||||
weight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,14 +199,14 @@ mod v6 {
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
struct PrefabWasmModule {
|
||||
pub struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
instruction_weights_version: u32,
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
initial: u32,
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
maximum: u32,
|
||||
code: Vec<u8>,
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
}
|
||||
|
||||
use v5::ContractInfo as OldContractInfo;
|
||||
@@ -258,11 +243,9 @@ mod v6 {
|
||||
#[storage_alias]
|
||||
type OwnerInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T>>;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
let mut weight = Weight::zero();
|
||||
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
<ContractInfoOf<T>>::translate(|_key, old: OldContractInfo<T>| {
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(ContractInfo::<T> {
|
||||
trie_id: old.trie_id,
|
||||
code_hash: old.code_hash,
|
||||
@@ -274,7 +257,7 @@ mod v6 {
|
||||
.expect("Infinite input; no dead input space; qed");
|
||||
|
||||
<CodeStorage<T>>::translate(|key, old: OldPrefabWasmModule| {
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
|
||||
<OwnerInfoOf<T>>::insert(
|
||||
key,
|
||||
OwnerInfo {
|
||||
@@ -290,8 +273,6 @@ mod v6 {
|
||||
code: old.code,
|
||||
})
|
||||
});
|
||||
|
||||
weight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,14 +280,14 @@ mod v6 {
|
||||
mod v7 {
|
||||
use super::*;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
#[storage_alias]
|
||||
type AccountCounter<T: Config> = StorageValue<Pallet<T>, u64, ValueQuery>;
|
||||
#[storage_alias]
|
||||
type Nonce<T: Config> = StorageValue<Pallet<T>, u64, ValueQuery>;
|
||||
|
||||
Nonce::<T>::set(AccountCounter::<T>::take());
|
||||
T::DbWeight::get().reads_writes(1, 2)
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,23 +298,21 @@ mod v8 {
|
||||
use v6::ContractInfo as OldContractInfo;
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
struct ContractInfo<T: Config> {
|
||||
trie_id: TrieId,
|
||||
code_hash: CodeHash<T>,
|
||||
storage_bytes: u32,
|
||||
storage_items: u32,
|
||||
storage_byte_deposit: BalanceOf<T>,
|
||||
storage_item_deposit: BalanceOf<T>,
|
||||
storage_base_deposit: BalanceOf<T>,
|
||||
pub struct ContractInfo<T: Config> {
|
||||
pub trie_id: TrieId,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
pub storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type ContractInfoOf<T: Config, V> =
|
||||
StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, V>;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
let mut weight = Weight::zero();
|
||||
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
<ContractInfoOf<T, ContractInfo<T>>>::translate_values(|old: OldContractInfo<T>| {
|
||||
// Count storage items of this contract
|
||||
let mut storage_bytes = 0u32;
|
||||
@@ -359,8 +338,9 @@ mod v8 {
|
||||
|
||||
// Reads: One read for each storage item plus the contract info itself.
|
||||
// Writes: Only the new contract info.
|
||||
weight = weight
|
||||
.saturating_add(T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1));
|
||||
weight.saturating_accrue(
|
||||
T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1),
|
||||
);
|
||||
|
||||
Some(ContractInfo {
|
||||
trie_id: old.trie_id,
|
||||
@@ -372,8 +352,6 @@ mod v8 {
|
||||
storage_base_deposit,
|
||||
})
|
||||
});
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
@@ -385,9 +363,78 @@ mod v8 {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn post_upgrade<T: Config>() -> Result<(), &'static str> {
|
||||
/// Update `CodeStorage` with the new `determinism` field.
|
||||
mod v9 {
|
||||
use super::*;
|
||||
use crate::Determinism;
|
||||
use v6::PrefabWasmModule as OldPrefabWasmModule;
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
pub determinism: Determinism,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
|
||||
pub fn migrate<T: Config>(weight: &mut Weight) {
|
||||
<CodeStorage<T>>::translate_values(|old: OldPrefabWasmModule| {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(PrefabWasmModule {
|
||||
instruction_weights_version: old.instruction_weights_version,
|
||||
initial: old.initial,
|
||||
maximum: old.maximum,
|
||||
code: old.code,
|
||||
determinism: Determinism::Deterministic,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Post checks always need to be run against the latest storage version. This is why we
|
||||
// do not scope them in the per version modules. They always need to be ported to the latest
|
||||
// version.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
mod post_checks {
|
||||
use super::*;
|
||||
use crate::Determinism;
|
||||
use sp_io::default_child_storage as child;
|
||||
use v8::ContractInfo;
|
||||
use v9::PrefabWasmModule;
|
||||
|
||||
#[storage_alias]
|
||||
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
|
||||
#[storage_alias]
|
||||
type ContractInfoOf<T: Config, V> =
|
||||
StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, V>;
|
||||
|
||||
pub fn post_upgrade<T: Config>(old_version: StorageVersion) -> Result<(), &'static str> {
|
||||
if old_version < 7 {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if old_version < 8 {
|
||||
v8::<T>()?;
|
||||
}
|
||||
|
||||
if old_version < 9 {
|
||||
v9::<T>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn v8<T: Config>() -> Result<(), &'static str> {
|
||||
use frame_support::traits::ReservableCurrency;
|
||||
for (key, value) in ContractInfoOf::<T, ContractInfo<T>>::iter() {
|
||||
let reserved = T::Currency::reserved_balance(&key);
|
||||
@@ -413,4 +460,14 @@ mod v8 {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn v9<T: Config>() -> Result<(), &'static str> {
|
||||
for value in CodeStorage::<T>::iter_values() {
|
||||
ensure!(
|
||||
value.determinism == Determinism::Deterministic,
|
||||
"All pre-existing codes need to be deterministic."
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! This module contains the cost schedule and supporting code that constructs a
|
||||
//! sane default schedule from a `WeightInfo` implementation.
|
||||
|
||||
use crate::{weights::WeightInfo, Config};
|
||||
use crate::{wasm::Determinism, weights::WeightInfo, Config};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::DefaultNoBound;
|
||||
@@ -193,6 +193,13 @@ pub struct InstructionWeights<T: Config> {
|
||||
/// Changes to other parts of the schedule should not increment the version in
|
||||
/// order to avoid unnecessary re-instrumentations.
|
||||
pub version: u32,
|
||||
/// Weight to be used for instructions which don't have benchmarks assigned.
|
||||
///
|
||||
/// This weight is used whenever a code is uploaded with [`Determinism::AllowIndeterminism`]
|
||||
/// and an instruction (usually a float instruction) is encountered. This weight is **not**
|
||||
/// used if a contract is uploaded with [`Determinism::Deterministic`]. If this field is set to
|
||||
/// `0` (the default) only deterministic codes are allowed to be uploaded.
|
||||
pub fallback: u32,
|
||||
pub i64const: u32,
|
||||
pub i64load: u32,
|
||||
pub i64store: u32,
|
||||
@@ -526,6 +533,7 @@ impl<T: Config> Default for InstructionWeights<T> {
|
||||
let max_pages = Limits::default().memory_pages;
|
||||
Self {
|
||||
version: 3,
|
||||
fallback: 0,
|
||||
i64const: cost_instr!(instr_i64const, 1),
|
||||
i64load: cost_instr!(instr_i64load, 2),
|
||||
i64store: cost_instr!(instr_i64store, 2),
|
||||
@@ -659,10 +667,15 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
struct ScheduleRules<'a, T: Config> {
|
||||
schedule: &'a Schedule<T>,
|
||||
params: Vec<u32>,
|
||||
determinism: Determinism,
|
||||
}
|
||||
|
||||
impl<T: Config> Schedule<T> {
|
||||
pub(crate) fn rules(&self, module: &elements::Module) -> impl gas_metering::Rules + '_ {
|
||||
pub(crate) fn rules(
|
||||
&self,
|
||||
module: &elements::Module,
|
||||
determinism: Determinism,
|
||||
) -> impl gas_metering::Rules + '_ {
|
||||
ScheduleRules {
|
||||
schedule: self,
|
||||
params: module
|
||||
@@ -674,6 +687,7 @@ impl<T: Config> Schedule<T> {
|
||||
func.params().len() as u32
|
||||
})
|
||||
.collect(),
|
||||
determinism,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,7 +770,10 @@ impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> {
|
||||
I32Rotr | I64Rotr => w.i64rotr,
|
||||
|
||||
// Returning None makes the gas instrumentation fail which we intend for
|
||||
// unsupported or unknown instructions.
|
||||
// unsupported or unknown instructions. Offchain we might allow indeterminism and hence
|
||||
// use the fallback weight for those instructions.
|
||||
_ if matches!(self.determinism, Determinism::AllowIndeterminism) && w.fallback > 0 =>
|
||||
w.fallback,
|
||||
_ => return None,
|
||||
};
|
||||
Some(weight)
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
exec::{FixSizedKey, Frame},
|
||||
storage::Storage,
|
||||
tests::test_utils::{get_contract, get_contract_checked},
|
||||
wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
||||
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
||||
weights::WeightInfo,
|
||||
BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue,
|
||||
Error, Pallet, Schedule,
|
||||
@@ -340,6 +340,7 @@ parameter_types! {
|
||||
// We want stack height to be always enabled for tests so that this
|
||||
// instrumentation path is always tested implicitly.
|
||||
schedule.limits.stack_height = Some(512);
|
||||
schedule.instruction_weights.fallback = 1;
|
||||
schedule
|
||||
};
|
||||
pub static DepositPerByte: BalanceOf<Test> = 1;
|
||||
@@ -522,7 +523,12 @@ fn instantiate_and_call_and_deposit_event() {
|
||||
|
||||
// We determine the storage deposit limit after uploading because it depends on ALICEs free
|
||||
// balance which is changed by uploading a module.
|
||||
assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, None));
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
|
||||
// Drop previous events
|
||||
initialize_block(2);
|
||||
@@ -690,7 +696,13 @@ fn instantiate_unique_trie_id() {
|
||||
|
||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, None).unwrap();
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
None,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
|
||||
// Instantiate the contract and store its trie id for later comparison.
|
||||
@@ -940,6 +952,7 @@ fn delegate_call() {
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
callee_wasm,
|
||||
Some(codec::Compact(100_000)),
|
||||
Determinism::Deterministic,
|
||||
));
|
||||
|
||||
assert_ok!(Contracts::call(
|
||||
@@ -1376,10 +1389,18 @@ fn crypto_hashes() {
|
||||
// We offset data in the contract tables by 1.
|
||||
let mut params = vec![(n + 1) as u8];
|
||||
params.extend_from_slice(input);
|
||||
let result =
|
||||
<Pallet<Test>>::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, params, false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = <Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
params,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert!(!result.did_revert());
|
||||
let expected = hash_fn(input.as_ref());
|
||||
assert_eq!(&result.data[..*expected_size], &*expected);
|
||||
@@ -1407,9 +1428,18 @@ fn transfer_return_code() {
|
||||
|
||||
// Contract has only the minimal balance so any transfer will fail.
|
||||
Balances::make_free_balance_be(&addr, min_balance);
|
||||
let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![], false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
|
||||
// Contract has enough total balance in order to not go below the min balance
|
||||
@@ -1417,9 +1447,18 @@ fn transfer_return_code() {
|
||||
// the transfer still fails.
|
||||
Balances::make_free_balance_be(&addr, min_balance + 100);
|
||||
Balances::reserve(&addr, min_balance + 100).unwrap();
|
||||
let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, None, vec![], false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||
});
|
||||
}
|
||||
@@ -1454,6 +1493,7 @@ fn call_return_code() {
|
||||
None,
|
||||
AsRef::<[u8]>::as_ref(&DJANGO).to_vec(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1484,6 +1524,7 @@ fn call_return_code() {
|
||||
.cloned()
|
||||
.collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1506,6 +1547,7 @@ fn call_return_code() {
|
||||
.cloned()
|
||||
.collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1525,6 +1567,7 @@ fn call_return_code() {
|
||||
.cloned()
|
||||
.collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1543,6 +1586,7 @@ fn call_return_code() {
|
||||
.cloned()
|
||||
.collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1591,6 +1635,7 @@ fn instantiate_return_code() {
|
||||
None,
|
||||
callee_hash.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1609,6 +1654,7 @@ fn instantiate_return_code() {
|
||||
None,
|
||||
callee_hash.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1616,10 +1662,18 @@ fn instantiate_return_code() {
|
||||
|
||||
// Contract has enough balance but the passed code hash is invalid
|
||||
Balances::make_free_balance_be(&addr, min_balance + 10_000);
|
||||
let result =
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![0; 33], false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![0; 33],
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, RuntimeReturnCode::CodeNotFound);
|
||||
|
||||
// Contract has enough balance but callee reverts because "1" is passed.
|
||||
@@ -1631,6 +1685,7 @@ fn instantiate_return_code() {
|
||||
None,
|
||||
callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1645,6 +1700,7 @@ fn instantiate_return_code() {
|
||||
None,
|
||||
callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1717,8 +1773,16 @@ fn chain_extension_works() {
|
||||
|
||||
// 0 = read input buffer and pass it through as output
|
||||
let input: Vec<u8> = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into();
|
||||
let result =
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false);
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
input.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_eq!(TestExtension::last_seen_buffer(), input);
|
||||
assert_eq!(result.result.unwrap().data, input);
|
||||
|
||||
@@ -1731,6 +1795,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1746,6 +1811,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_ok!(result.result);
|
||||
let gas_consumed = result.gas_consumed;
|
||||
@@ -1757,6 +1823,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_ok!(result.result);
|
||||
assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42);
|
||||
@@ -1768,6 +1835,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_ok!(result.result);
|
||||
assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95);
|
||||
@@ -1781,6 +1849,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1798,6 +1867,7 @@ fn chain_extension_works() {
|
||||
None,
|
||||
ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
@@ -1847,8 +1917,17 @@ fn chain_extension_temp_storage_works() {
|
||||
);
|
||||
|
||||
assert_ok!(
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false)
|
||||
.result
|
||||
Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
input.clone(),
|
||||
false,
|
||||
Determinism::Deterministic
|
||||
)
|
||||
.result
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -2387,12 +2466,28 @@ fn reinstrument_does_charge() {
|
||||
|
||||
// Call the contract two times without reinstrument
|
||||
|
||||
let result0 =
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false);
|
||||
let result0 = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
zero.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert!(!result0.result.unwrap().did_revert());
|
||||
|
||||
let result1 =
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false);
|
||||
let result1 = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
zero.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert!(!result1.result.unwrap().did_revert());
|
||||
|
||||
// They should match because both where called with the same schedule.
|
||||
@@ -2405,8 +2500,16 @@ fn reinstrument_does_charge() {
|
||||
});
|
||||
|
||||
// This call should trigger reinstrumentation
|
||||
let result2 =
|
||||
Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false);
|
||||
let result2 = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
zero.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert!(!result2.result.unwrap().did_revert());
|
||||
assert!(result2.gas_consumed.ref_time() > result1.gas_consumed.ref_time());
|
||||
assert_eq!(
|
||||
@@ -2433,7 +2536,16 @@ fn debug_message_works() {
|
||||
vec![],
|
||||
),);
|
||||
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, None, vec![], true);
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
true,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
|
||||
assert_matches!(result.result, Ok(_));
|
||||
assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!");
|
||||
@@ -2457,7 +2569,16 @@ fn debug_message_logging_disabled() {
|
||||
),);
|
||||
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
// disable logging by passing `false`
|
||||
let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![], false);
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_matches!(result.result, Ok(_));
|
||||
// the dispatchables always run without debugging
|
||||
assert_ok!(Contracts::call(RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, None, vec![]));
|
||||
@@ -2481,7 +2602,16 @@ fn debug_message_invalid_utf8() {
|
||||
vec![],
|
||||
),);
|
||||
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, None, vec![], true);
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
true,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_err!(result.result, <Error<Test>>::DebugMessageInvalidUTF8);
|
||||
});
|
||||
}
|
||||
@@ -2532,6 +2662,7 @@ fn gas_estimation_nested_call_fixed_limit() {
|
||||
None,
|
||||
input.clone(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
assert_ok!(&result.result);
|
||||
|
||||
@@ -2548,6 +2679,7 @@ fn gas_estimation_nested_call_fixed_limit() {
|
||||
Some(result.storage_deposit.charge_or_zero()),
|
||||
input,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
);
|
||||
@@ -2604,6 +2736,7 @@ fn gas_estimation_call_runtime() {
|
||||
None,
|
||||
call.encode(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
);
|
||||
// contract encodes the result of the dispatch runtime
|
||||
let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap();
|
||||
@@ -2620,6 +2753,7 @@ fn gas_estimation_call_runtime() {
|
||||
None,
|
||||
call.encode(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
);
|
||||
@@ -2668,10 +2802,18 @@ fn ecdsa_recover() {
|
||||
params.extend_from_slice(&signature);
|
||||
params.extend_from_slice(&message_hash);
|
||||
assert!(params.len() == 65 + 32);
|
||||
let result =
|
||||
<Pallet<Test>>::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, params, false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = <Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
params,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert!(!result.did_revert());
|
||||
assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY);
|
||||
})
|
||||
@@ -2691,7 +2833,8 @@ fn upload_code_works() {
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
Some(codec::Compact(1_000))
|
||||
Some(codec::Compact(1_000)),
|
||||
Determinism::Deterministic,
|
||||
));
|
||||
assert!(<CodeStorage<Test>>::contains_key(code_hash));
|
||||
|
||||
@@ -2702,7 +2845,7 @@ fn upload_code_works() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -2727,7 +2870,12 @@ fn upload_code_limit_too_low() {
|
||||
initialize_block(2);
|
||||
|
||||
assert_noop!(
|
||||
Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(100))),
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
Some(codec::Compact(100)),
|
||||
Determinism::Deterministic
|
||||
),
|
||||
<Error<Test>>::StorageDepositLimitExhausted,
|
||||
);
|
||||
|
||||
@@ -2746,7 +2894,12 @@ fn upload_code_not_enough_balance() {
|
||||
initialize_block(2);
|
||||
|
||||
assert_noop!(
|
||||
Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000))),
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
Some(codec::Compact(1_000)),
|
||||
Determinism::Deterministic
|
||||
),
|
||||
<Error<Test>>::StorageDepositNotEnoughFunds,
|
||||
);
|
||||
|
||||
@@ -2767,7 +2920,8 @@ fn remove_code_works() {
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
Some(codec::Compact(1_000))
|
||||
Some(codec::Compact(1_000)),
|
||||
Determinism::Deterministic,
|
||||
));
|
||||
|
||||
assert!(<CodeStorage<Test>>::contains_key(code_hash));
|
||||
@@ -2781,7 +2935,7 @@ fn remove_code_works() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -2794,7 +2948,7 @@ fn remove_code_works() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -2821,7 +2975,8 @@ fn remove_code_wrong_origin() {
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
Some(codec::Compact(1_000))
|
||||
Some(codec::Compact(1_000)),
|
||||
Determinism::Deterministic,
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
@@ -2836,7 +2991,7 @@ fn remove_code_wrong_origin() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -2969,7 +3124,7 @@ fn instantiate_with_zero_balance_works() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -3071,7 +3226,7 @@ fn instantiate_with_below_existential_deposit_works() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||
who: ALICE,
|
||||
amount: 240,
|
||||
amount: 241,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -3261,7 +3416,12 @@ fn set_code_extrinsic() {
|
||||
));
|
||||
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
|
||||
assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), new_wasm, None,));
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
new_wasm,
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
|
||||
// Drop previous events
|
||||
initialize_block(2);
|
||||
@@ -3457,7 +3617,12 @@ fn contract_reverted() {
|
||||
let input = (flags.bits(), buffer).encode();
|
||||
|
||||
// We just upload the code for later use
|
||||
assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm.clone(), None));
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm.clone(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
|
||||
// Calling extrinsic: revert leads to an error
|
||||
assert_err_ignore_postinfo!(
|
||||
@@ -3536,9 +3701,18 @@ fn contract_reverted() {
|
||||
);
|
||||
|
||||
// Calling directly: revert leads to success but the flags indicate the error
|
||||
let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input, false)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
input,
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_eq!(result.flags, flags);
|
||||
assert_eq!(result.data, buffer);
|
||||
});
|
||||
@@ -3551,7 +3725,12 @@ fn code_rejected_error_works() {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
assert_noop!(
|
||||
Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm.clone(), None),
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm.clone(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
),
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
|
||||
@@ -3594,7 +3773,12 @@ fn set_code_hash() {
|
||||
vec![],
|
||||
));
|
||||
// upload new code
|
||||
assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), new_wasm.clone(), None));
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
new_wasm.clone(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
|
||||
System::reset_events();
|
||||
|
||||
@@ -3607,16 +3791,25 @@ fn set_code_hash() {
|
||||
None,
|
||||
new_code_hash.as_ref().to_vec(),
|
||||
true,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, 1);
|
||||
|
||||
// Second calls new contract code that returns 2
|
||||
let result =
|
||||
Contracts::bare_call(ALICE, contract_addr.clone(), 0, GAS_LIMIT, None, vec![], true)
|
||||
.result
|
||||
.unwrap();
|
||||
let result = Contracts::bare_call(
|
||||
ALICE,
|
||||
contract_addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
vec![],
|
||||
true,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
assert_return_code!(result, 2);
|
||||
|
||||
// Checking for the last event only
|
||||
@@ -3950,3 +4143,243 @@ fn deposit_limit_honors_min_leftover() {
|
||||
assert_eq!(Balances::free_balance(&BOB), 1_000);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_instantiate_indeterministic_code() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("float_instruction").unwrap();
|
||||
let (caller_wasm, _) = compile_module::<Test>("instantiate_return_code").unwrap();
|
||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Try to instantiate directly from code
|
||||
assert_err_ignore_postinfo!(
|
||||
Contracts::instantiate_with_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
wasm.clone(),
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
assert_err!(
|
||||
Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(wasm.clone()),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
|
||||
// Try to upload a non deterministic code as deterministic
|
||||
assert_err!(
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm.clone(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
),
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
|
||||
// Try to instantiate from already stored indeterministic code hash
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
None,
|
||||
Determinism::AllowIndeterminism,
|
||||
));
|
||||
assert_err_ignore_postinfo!(
|
||||
Contracts::instantiate(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash,
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
assert_err!(
|
||||
Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Existing(code_hash),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
|
||||
// Deploy contract which instantiates another contract
|
||||
let addr = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(caller_wasm),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.account_id;
|
||||
|
||||
// Try to instantiate `code_hash` from another contract in deterministic mode
|
||||
assert_err!(
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash.encode(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
|
||||
// Instantiations are not allowed even in non determinism mode
|
||||
assert_err!(
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash.encode(),
|
||||
false,
|
||||
Determinism::AllowIndeterminism,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_set_code_indeterministic_code() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("float_instruction").unwrap();
|
||||
let (caller_wasm, _) = compile_module::<Test>("set_code_hash").unwrap();
|
||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Put the non deterministic contract on-chain
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
None,
|
||||
Determinism::AllowIndeterminism,
|
||||
));
|
||||
|
||||
// Create the contract that will call `seal_set_code_hash`
|
||||
let caller_addr = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(caller_wasm),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.account_id;
|
||||
|
||||
// We do not allow to set the code hash to a non determinstic wasm
|
||||
assert_err!(
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
caller_addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash.encode(),
|
||||
false,
|
||||
Determinism::AllowIndeterminism,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegate_call_indeterministic_code() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("float_instruction").unwrap();
|
||||
let (caller_wasm, _) = compile_module::<Test>("delegate_call_simple").unwrap();
|
||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Put the non deterministic contract on-chain
|
||||
assert_ok!(Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm,
|
||||
None,
|
||||
Determinism::AllowIndeterminism,
|
||||
));
|
||||
|
||||
// Create the contract that will call `seal_delegate_call`
|
||||
let caller_addr = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(caller_wasm),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.account_id;
|
||||
|
||||
// The delegate call will fail in deterministic mode
|
||||
assert_err!(
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
caller_addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash.encode(),
|
||||
false,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.result,
|
||||
<Error<Test>>::Indeterministic,
|
||||
);
|
||||
|
||||
// The delegate call will work on non deterministic mode
|
||||
assert_ok!(
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
caller_addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code_hash.encode(),
|
||||
false,
|
||||
Determinism::AllowIndeterminism,
|
||||
)
|
||||
.result
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ pub fn reinstrument<T: Config>(
|
||||
// as the contract is already deployed and every change in size would be the result
|
||||
// of changes in the instrumentation algorithm controlled by the chain authors.
|
||||
prefab_module.code = WeakBoundedVec::force_from(
|
||||
prepare::reinstrument_contract::<T>(&original_code, schedule)
|
||||
prepare::reinstrument_contract::<T>(&original_code, schedule, prefab_module.determinism)
|
||||
.map_err(|_| <Error<T>>::CodeRejected)?,
|
||||
Some("Contract exceeds limit after re-instrumentation."),
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ use crate::{
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::dispatch::{DispatchError, DispatchResult};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory};
|
||||
use sp_std::prelude::*;
|
||||
#[cfg(test)]
|
||||
@@ -66,6 +67,10 @@ pub struct PrefabWasmModule<T: Config> {
|
||||
maximum: u32,
|
||||
/// Code instrumented with the latest schedule.
|
||||
code: RelaxedCodeVec<T>,
|
||||
/// A code that might contain non deterministic features and is therefore never allowed
|
||||
/// to be run on chain. Specifically this code can never be instantiated into a contract
|
||||
/// and can just be used through a delegate call.
|
||||
determinism: Determinism,
|
||||
/// The uninstrumented, pristine version of the code.
|
||||
///
|
||||
/// It is not stored because the pristine code has its own storage item. The value
|
||||
@@ -102,6 +107,27 @@ pub struct OwnerInfo<T: Config> {
|
||||
refcount: u64,
|
||||
}
|
||||
|
||||
/// Defines the required determinism level of a wasm blob when either running or uploading code.
|
||||
#[derive(
|
||||
Clone, Copy, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen, RuntimeDebug, PartialEq, Eq,
|
||||
)]
|
||||
pub enum Determinism {
|
||||
/// The execution should be deterministic and hence no indeterministic instructions are
|
||||
/// allowed.
|
||||
///
|
||||
/// Dispatchables always use this mode in order to make on-chain execution deterministic.
|
||||
Deterministic,
|
||||
/// Allow calling or uploading an indeterministic code.
|
||||
///
|
||||
/// This is only possible when calling into `pallet-contracts` directly via
|
||||
/// [`crate::Pallet::bare_call`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// **Never** use this mode for on-chain execution.
|
||||
AllowIndeterminism,
|
||||
}
|
||||
|
||||
impl ExportedFunction {
|
||||
/// The wasm export name for the function.
|
||||
fn identifier(&self) -> &str {
|
||||
@@ -124,11 +150,13 @@ where
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Self, (DispatchError, &'static str)> {
|
||||
let module = prepare::prepare_contract(
|
||||
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
schedule,
|
||||
owner,
|
||||
determinism,
|
||||
)?;
|
||||
Ok(module)
|
||||
}
|
||||
@@ -258,6 +286,10 @@ where
|
||||
fn code_len(&self) -> u32 {
|
||||
self.code.len() as u32
|
||||
}
|
||||
|
||||
fn is_deterministic(&self) -> bool {
|
||||
matches!(self.determinism, Determinism::Deterministic)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -551,8 +583,13 @@ mod tests {
|
||||
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let executable =
|
||||
PrefabWasmModule::<<MockExt as Ext>::T>::from_code(wasm, &schedule, ALICE).unwrap();
|
||||
let executable = PrefabWasmModule::<<MockExt as Ext>::T>::from_code(
|
||||
wasm,
|
||||
&schedule,
|
||||
ALICE,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
use crate::{
|
||||
chain_extension::ChainExtension,
|
||||
storage::meter::Diff,
|
||||
wasm::{env_def::ImportSatisfyCheck, OwnerInfo, PrefabWasmModule},
|
||||
wasm::{env_def::ImportSatisfyCheck, Determinism, OwnerInfo, PrefabWasmModule},
|
||||
AccountIdOf, CodeVec, Config, Error, Schedule,
|
||||
};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
@@ -182,8 +182,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module);
|
||||
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module, determinism);
|
||||
let contract_module =
|
||||
wasm_instrument::gas_metering::inject(self.module, &gas_rules, "seal0")
|
||||
.map_err(|_| "gas instrumentation failed")?;
|
||||
@@ -369,6 +369,7 @@ fn get_memory_limits<T: Config>(
|
||||
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
|
||||
let result = (|| {
|
||||
let contract_module = ContractModule::new(original_code, schedule)?;
|
||||
@@ -376,17 +377,20 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
|
||||
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
|
||||
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
|
||||
|
||||
if matches!(determinism, Determinism::Deterministic) {
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
}
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits =
|
||||
get_memory_limits(contract_module.scan_imports::<C>(&disallowed_imports)?, schedule)?;
|
||||
|
||||
let code = contract_module
|
||||
.inject_gas_metering()?
|
||||
.inject_gas_metering(determinism)?
|
||||
.inject_stack_height_metering()?
|
||||
.into_wasm_code()?;
|
||||
|
||||
@@ -404,9 +408,11 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
let (code, (initial, maximum)) = check_and_instrument::<C, T>(original_code.as_ref(), schedule)
|
||||
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
|
||||
let (code, (initial, maximum)) =
|
||||
check_and_instrument::<C, T>(original_code.as_ref(), schedule, determinism)
|
||||
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
|
||||
let original_code_len = original_code.len();
|
||||
|
||||
let mut module = PrefabWasmModule {
|
||||
@@ -414,6 +420,7 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
initial,
|
||||
maximum,
|
||||
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
determinism,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code),
|
||||
owner_info: None,
|
||||
@@ -449,8 +456,9 @@ pub fn prepare_contract<T: Config>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner)
|
||||
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner, determinism)
|
||||
}
|
||||
|
||||
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
|
||||
@@ -461,8 +469,9 @@ pub fn prepare_contract<T: Config>(
|
||||
pub fn reinstrument_contract<T: Config>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule)?.0)
|
||||
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule, determinism)?.0)
|
||||
}
|
||||
|
||||
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
|
||||
@@ -495,6 +504,7 @@ pub mod benchmarking {
|
||||
maximum: memory_limits.1,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
|
||||
determinism: Determinism::Deterministic,
|
||||
code: contract_module
|
||||
.into_wasm_code()?
|
||||
.try_into()
|
||||
@@ -572,7 +582,7 @@ mod tests {
|
||||
},
|
||||
.. Default::default()
|
||||
};
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE);
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE, Determinism::Deterministic);
|
||||
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2384,6 +2384,7 @@ pub mod env {
|
||||
/// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another
|
||||
/// way, when using this API you lose the guarantee that an address always identifies a specific
|
||||
/// code hash.
|
||||
///
|
||||
/// 3. If a contract calls into itself after changing its code the new call would use
|
||||
/// the new code. However, if the original caller panics after returning from the sub call it
|
||||
/// would revert the changes made by `seal_set_code_hash` and the next caller would use
|
||||
|
||||
Reference in New Issue
Block a user