|
@@ -7,6 +7,7 @@ use ink_metadata::InkProject;
|
|
|
use ink_primitives::Hash;
|
|
use ink_primitives::Hash;
|
|
|
use parity_scale_codec::Decode;
|
|
use parity_scale_codec::Decode;
|
|
|
use sha2::{Digest, Sha256};
|
|
use sha2::{Digest, Sha256};
|
|
|
|
|
+use std::collections::HashSet;
|
|
|
use std::{collections::HashMap, ffi::OsStr, fmt, fmt::Write};
|
|
use std::{collections::HashMap, ffi::OsStr, fmt, fmt::Write};
|
|
|
use tiny_keccak::{Hasher, Keccak};
|
|
use tiny_keccak::{Hasher, Keccak};
|
|
|
use wasmi::core::{HostError, Trap, TrapCode};
|
|
use wasmi::core::{HostError, Trap, TrapCode};
|
|
@@ -22,6 +23,21 @@ mod substrate_tests;
|
|
|
type StorageKey = [u8; 32];
|
|
type StorageKey = [u8; 32];
|
|
|
type Address = [u8; 32];
|
|
type Address = [u8; 32];
|
|
|
|
|
|
|
|
|
|
+#[derive(Clone, Copy)]
|
|
|
|
|
+enum CallFlags {
|
|
|
|
|
+ ForwardInput = 1,
|
|
|
|
|
+ CloneInput = 2,
|
|
|
|
|
+ TailCall = 4,
|
|
|
|
|
+ AllowReentry = 8,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl CallFlags {
|
|
|
|
|
+ /// Returns true if this flag is set in the given `flags`.
|
|
|
|
|
+ fn set(&self, flags: u32) -> bool {
|
|
|
|
|
+ flags & *self as u32 != 0
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Reason for halting execution. Same as in pallet contracts.
|
|
/// Reason for halting execution. Same as in pallet contracts.
|
|
|
#[derive(Default, Debug, Clone)]
|
|
#[derive(Default, Debug, Clone)]
|
|
|
enum HostReturn {
|
|
enum HostReturn {
|
|
@@ -201,7 +217,7 @@ struct Runtime {
|
|
|
/// Will hold the memory reference after a successful execution.
|
|
/// Will hold the memory reference after a successful execution.
|
|
|
memory: Option<Memory>,
|
|
memory: Option<Memory>,
|
|
|
/// The input for the contract execution.
|
|
/// The input for the contract execution.
|
|
|
- input: Vec<u8>,
|
|
|
|
|
|
|
+ input: Option<Vec<u8>>,
|
|
|
/// The output of the contract execution.
|
|
/// The output of the contract execution.
|
|
|
output: HostReturn,
|
|
output: HostReturn,
|
|
|
/// Descirbes how much value was given to the contract call.
|
|
/// Descirbes how much value was given to the contract call.
|
|
@@ -210,6 +226,8 @@ struct Runtime {
|
|
|
debug_buffer: String,
|
|
debug_buffer: String,
|
|
|
/// Stores all events emitted during contract execution.
|
|
/// Stores all events emitted during contract execution.
|
|
|
events: Vec<Event>,
|
|
events: Vec<Event>,
|
|
|
|
|
+ /// The set of called events, needed for reentrancy protection.
|
|
|
|
|
+ called_accounts: HashSet<usize>,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl Runtime {
|
|
impl Runtime {
|
|
@@ -237,8 +255,9 @@ impl Runtime {
|
|
|
runtime.account = callee;
|
|
runtime.account = callee;
|
|
|
runtime.transferred_value = value;
|
|
runtime.transferred_value = value;
|
|
|
runtime.accounts[callee].value += value;
|
|
runtime.accounts[callee].value += value;
|
|
|
- runtime.input = input;
|
|
|
|
|
|
|
+ runtime.input = Some(input);
|
|
|
runtime.output = Default::default();
|
|
runtime.output = Default::default();
|
|
|
|
|
+ runtime.called_accounts.insert(self.caller_account);
|
|
|
runtime
|
|
runtime
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -337,11 +356,12 @@ fn read_account(mem: &[u8], ptr: u32) -> Address {
|
|
|
impl Runtime {
|
|
impl Runtime {
|
|
|
#[seal(0)]
|
|
#[seal(0)]
|
|
|
fn input(dest_ptr: u32, len_ptr: u32) -> Result<(), Trap> {
|
|
fn input(dest_ptr: u32, len_ptr: u32) -> Result<(), Trap> {
|
|
|
- assert!(read_len(mem, len_ptr) >= vm.input.len());
|
|
|
|
|
- println!("seal_input: {}", hex::encode(&vm.input));
|
|
|
|
|
|
|
+ let data = vm.input.as_ref().expect("input was forwarded");
|
|
|
|
|
+ assert!(read_len(mem, len_ptr) >= data.len());
|
|
|
|
|
+ println!("seal_input: {}", hex::encode(data));
|
|
|
|
|
|
|
|
- write_buf(mem, dest_ptr, &vm.input);
|
|
|
|
|
- write_buf(mem, len_ptr, &(vm.input.len() as u32).to_le_bytes());
|
|
|
|
|
|
|
+ write_buf(mem, dest_ptr, data);
|
|
|
|
|
+ write_buf(mem, len_ptr, &(data.len() as u32).to_le_bytes());
|
|
|
|
|
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
@@ -466,9 +486,21 @@ impl Runtime {
|
|
|
output_ptr: u32,
|
|
output_ptr: u32,
|
|
|
output_len_ptr: u32,
|
|
output_len_ptr: u32,
|
|
|
) -> Result<u32, Trap> {
|
|
) -> Result<u32, Trap> {
|
|
|
- assert_eq!(flags, 0); // At the time, we never set call flags
|
|
|
|
|
|
|
+ assert!(flags <= 0b1111);
|
|
|
|
|
|
|
|
- let input = read_buf(mem, input_ptr, input_len);
|
|
|
|
|
|
|
+ let input = if CallFlags::ForwardInput.set(flags) {
|
|
|
|
|
+ if vm.input.is_none() {
|
|
|
|
|
+ return Ok(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ vm.input.take().unwrap()
|
|
|
|
|
+ } else if CallFlags::CloneInput.set(flags) {
|
|
|
|
|
+ if vm.input.is_none() {
|
|
|
|
|
+ return Ok(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ vm.input.as_ref().unwrap().clone()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ read_buf(mem, input_ptr, input_len)
|
|
|
|
|
+ };
|
|
|
let value = read_value(mem, value_ptr);
|
|
let value = read_value(mem, value_ptr);
|
|
|
let callee_address = read_account(mem, callee_ptr);
|
|
let callee_address = read_account(mem, callee_ptr);
|
|
|
|
|
|
|
@@ -483,11 +515,15 @@ impl Runtime {
|
|
|
None => return Ok(8), // ReturnCode::NotCallable
|
|
None => return Ok(8), // ReturnCode::NotCallable
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ if vm.called_accounts.contains(&callee) && !CallFlags::AllowReentry.set(flags) {
|
|
|
|
|
+ return Ok(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if value > vm.accounts[vm.account].value {
|
|
if value > vm.accounts[vm.account].value {
|
|
|
return Ok(5); // ReturnCode::TransferFailed
|
|
return Ok(5); // ReturnCode::TransferFailed
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let ((flags, data), state) = match vm.call("call", callee, input, value) {
|
|
|
|
|
|
|
+ let ((ret, data), state) = match vm.call("call", callee, input, value) {
|
|
|
Some(Ok(state)) => ((state.data().output.as_data()), state),
|
|
Some(Ok(state)) => ((state.data().output.as_data()), state),
|
|
|
Some(Err(_)) => return Ok(1), // ReturnCode::CalleeTrapped
|
|
Some(Err(_)) => return Ok(1), // ReturnCode::CalleeTrapped
|
|
|
None => return Ok(8),
|
|
None => return Ok(8),
|
|
@@ -499,11 +535,14 @@ impl Runtime {
|
|
|
write_buf(mem, output_len_ptr, &(data.len() as u32).to_le_bytes());
|
|
write_buf(mem, output_len_ptr, &(data.len() as u32).to_le_bytes());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if flags == 2 {
|
|
|
|
|
|
|
+ if ret == 2 {
|
|
|
return Ok(2); // ReturnCode::CalleeReverted
|
|
return Ok(2); // ReturnCode::CalleeReverted
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
vm.accept_state(state.into_data(), value);
|
|
vm.accept_state(state.into_data(), value);
|
|
|
|
|
+ if CallFlags::TailCall.set(flags) {
|
|
|
|
|
+ return Err(HostReturn::Data(0, data).into());
|
|
|
|
|
+ }
|
|
|
Ok(0)
|
|
Ok(0)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -758,6 +797,7 @@ impl MockSubstrate {
|
|
|
|
|
|
|
|
runtime.debug_buffer.clear();
|
|
runtime.debug_buffer.clear();
|
|
|
runtime.events.clear();
|
|
runtime.events.clear();
|
|
|
|
|
+ runtime.called_accounts.clear();
|
|
|
self.0 = runtime.call(export, callee, input, value).unwrap()?;
|
|
self.0 = runtime.call(export, callee, input, value).unwrap()?;
|
|
|
self.0.data_mut().transferred_value = 0;
|
|
self.0.data_mut().transferred_value = 0;
|
|
|
|
|
|