contracts: Move Schedule from Storage to Config (#8773)

* Move `Schedule` from Storage to Config

* Updated CHANGELOG

* Fix nits from review

* Fix migration

* Print the debug buffer as tracing message

* Use `debug` instead of `trace` and update README

* Add additional assert to test

* Rename `schedule_version` to `instruction_weights_version`

* Fixed typo

* Added more comments to wat fixtures

* Add clarification for the `debug_message` field
This commit is contained in:
Alexander Theißen
2021-05-13 21:56:11 +02:00
committed by GitHub
parent 3c0270fe57
commit 1ac95b6ba6
23 changed files with 1465 additions and 1056 deletions
+161 -29
View File
@@ -315,6 +315,16 @@ pub trait Ext: sealing::Sealed {
/// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
/// Append a string to the debug buffer.
///
/// It is added as-is without any additional new line.
///
/// This is a no-op if debug message recording is disabled which is always the case
/// when the code is executing on-chain.
///
/// Returns `true` if debug message recording is enabled. Otherwise `false` is returned.
fn append_debug_buffer(&mut self, msg: &str) -> bool;
}
/// Describes the different functions that can be exported by an [`Executable`].
@@ -434,6 +444,11 @@ pub struct Stack<'a, T: Config, E> {
frames: SmallVec<T::CallStack>,
/// Statically guarantee that each call stack has at least one frame.
first_frame: Frame<T>,
/// A text buffer used to output human readable information.
///
/// 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>>,
/// No executable is held by the struct but influences its behaviour.
_phantom: PhantomData<E>,
}
@@ -442,6 +457,11 @@ pub struct Stack<'a, T: Config, E> {
///
/// For each nested contract call or instantiate one frame is created. It holds specific
/// information for the said call and caches the in-storage `ContractInfo` data structure.
///
/// # Note
///
/// This is an internal data structure. It is exposed to the public for the sole reason
/// of specifying [`Config::CallStack`].
pub struct Frame<T: Config> {
/// The account id of the executing contract.
account_id: T::AccountId,
@@ -574,6 +594,11 @@ where
{
/// Create an run a new call stack by calling into `dest`.
///
/// # Note
///
/// `debug_message` should only ever be set to `Some` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
///
/// # Return Value
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
@@ -584,6 +609,7 @@ where
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
let (mut stack, executable) = Self::new(
FrameArgs::Call{dest, cached_info: None},
@@ -591,12 +617,18 @@ where
gas_meter,
schedule,
value,
debug_message,
)?;
stack.run(executable, input_data)
}
/// Create and run a new call stack by instantiating a new contract.
///
/// # Note
///
/// `debug_message` should only ever be set to `Some` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
///
/// # Return Value
///
/// Result<(NewContractAccountId, ExecReturnValue), ExecError)>
@@ -608,6 +640,7 @@ where
value: BalanceOf<T>,
input_data: Vec<u8>,
salt: &[u8],
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
let (mut stack, executable) = Self::new(
FrameArgs::Instantiate {
@@ -620,6 +653,7 @@ where
gas_meter,
schedule,
value,
debug_message,
).map_err(|(e, _code_len)| e)?;
let account_id = stack.top_frame().account_id.clone();
stack.run(executable, input_data)
@@ -634,6 +668,7 @@ where
gas_meter: &'a mut GasMeter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(Self, E), (ExecError, u32)> {
let (first_frame, executable) = Self::new_frame(args, value, gas_meter, 0, &schedule)?;
let stack = Self {
@@ -645,6 +680,7 @@ where
account_counter: None,
first_frame,
frames: Default::default(),
debug_message,
_phantom: Default::default(),
};
@@ -841,6 +877,7 @@ where
// Pop the current frame from the stack and return it in case it needs to interact
// with duplicates that might exist on the stack.
// A `None` means that we are returning from the `first_frame`.
let frame = self.frames.pop();
if let Some(frame) = frame {
@@ -872,6 +909,13 @@ where
}
}
} else {
if let Some(message) = &self.debug_message {
log::debug!(
target: "runtime::contracts",
"Debug Message: {}",
core::str::from_utf8(message).unwrap_or("<Invalid UTF8>"),
);
}
// Write back to the root gas meter.
self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_meter));
// Only gas counter changes are persisted in case of a failure.
@@ -1181,7 +1225,7 @@ where
fn block_number(&self) -> T::BlockNumber { self.block_number }
fn max_value_size(&self) -> u32 {
T::MaxValueSize::get()
T::Schedule::get().limits.payload_len
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
@@ -1199,6 +1243,17 @@ where
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_meter
}
fn append_debug_buffer(&mut self, msg: &str) -> bool {
if let Some(buffer) = &mut self.debug_message {
if !msg.is_empty() {
buffer.extend(msg.as_bytes());
}
true
} else {
false
}
}
}
fn deposit_event<T: Config>(
@@ -1241,7 +1296,7 @@ mod tests {
test_utils::{place_contract, set_balance, get_balance},
},
exec::ExportedFunction::*,
Error, Weight, CurrentSchedule,
Error, Weight,
};
use sp_core::Bytes;
use sp_runtime::DispatchError;
@@ -1436,12 +1491,12 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, exec_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut gas_meter, &schedule, value, vec![],
ALICE, BOB, &mut gas_meter, &schedule, value, vec![], None,
),
Ok(_)
);
@@ -1487,7 +1542,7 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
@@ -1499,6 +1554,7 @@ mod tests {
&schedule,
55,
vec![],
None,
).unwrap();
assert!(!output.0.is_success());
@@ -1548,7 +1604,7 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
@@ -1558,6 +1614,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
let output = result.unwrap();
@@ -1578,8 +1635,8 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
place_contract(&dest, return_ch);
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
origin,
@@ -1588,6 +1645,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
let output = result.unwrap();
@@ -1605,7 +1663,7 @@ mod tests {
// This one tests passing the input data into a contract via call.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, input_data_ch);
let result = MockStack::run_call(
@@ -1615,6 +1673,7 @@ mod tests {
&schedule,
0,
vec![1, 2, 3, 4],
None,
);
assert_matches!(result, Ok(_));
});
@@ -1629,7 +1688,7 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
@@ -1646,6 +1705,7 @@ mod tests {
subsistence * 3,
vec![1, 2, 3, 4],
&[],
None,
);
assert_matches!(result, Ok(_));
});
@@ -1683,7 +1743,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&BOB, 1);
place_contract(&BOB, recurse_ch);
@@ -1694,6 +1754,7 @@ mod tests {
&schedule,
value,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1732,7 +1793,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
@@ -1743,6 +1804,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1771,7 +1833,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
@@ -1782,6 +1844,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1793,7 +1856,7 @@ mod tests {
let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success());
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1808,6 +1871,7 @@ mod tests {
0, // <- zero endowment
vec![],
&[],
None,
),
Err(_)
);
@@ -1822,7 +1886,7 @@ mod tests {
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1838,6 +1902,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == Bytes(vec![80, 65, 83, 83]) => address
);
@@ -1859,7 +1924,7 @@ mod tests {
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1875,6 +1940,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == Bytes(vec![70, 65, 73, 76]) => address
);
@@ -1908,13 +1974,13 @@ mod tests {
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, Contracts::<Test>::subsistence_threshold() * 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![],
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![], None,
),
Ok(_)
);
@@ -1958,14 +2024,14 @@ mod tests {
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![],
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![], None,
),
Ok(_)
);
@@ -1987,7 +2053,7 @@ mod tests {
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
terminate_ch, &schedule, &mut gas_meter
@@ -2003,6 +2069,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Err(Error::<Test>::TerminatedInConstructor.into())
);
@@ -2027,7 +2094,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
rent_allowance_ch, &schedule, &mut gas_meter
@@ -2042,6 +2109,7 @@ mod tests {
subsistence * 5,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
@@ -2060,7 +2128,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
@@ -2071,6 +2139,7 @@ mod tests {
&schedule,
0,
vec![],
None,
).unwrap();
});
}
@@ -2109,7 +2178,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 100);
place_contract(&BOB, code_hash);
@@ -2120,6 +2189,7 @@ mod tests {
&schedule,
subsistence * 50,
vec![],
None,
).unwrap();
});
}
@@ -2156,7 +2226,7 @@ mod tests {
// This one tests passing the input data into a contract via call.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
@@ -2167,6 +2237,7 @@ mod tests {
&schedule,
0,
vec![0],
None,
);
assert_matches!(result, Ok(_));
});
@@ -2174,10 +2245,9 @@ mod tests {
#[test]
fn recursive_call_during_constructor_fails() {
let code = MockLoader::insert(Constructor, |ctx, executable| {
let my_hash = <Contracts<Test>>::contract_address(&ALICE, &executable.code_hash, &[]);
let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!(
ctx.ext.call(0, my_hash, 0, vec![]),
ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![]),
Err((ExecError{error, ..}, _)) if error == <Error<Test>>::NotCallable.into()
);
exec_success()
@@ -2185,7 +2255,7 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
@@ -2202,8 +2272,70 @@ mod tests {
subsistence * 3,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn printing_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_success()
});
let mut debug_buffer = Vec::new();
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
).unwrap();
});
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
}
#[test]
fn printing_works_on_fail() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_trapped()
});
let mut debug_buffer = Vec::new();
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
let result = MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
);
assert!(result.is_err());
});
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
}
}