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:
Alexander Theißen
2022-10-24 19:48:04 +02:00
committed by GitHub
parent d0dcf008ec
commit 3ae4be8662
15 changed files with 926 additions and 169 deletions
+74 -8
View File
@@ -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(_));
});