Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail when decoding from storage and not all bytes consumed #1897

Merged
merged 17 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make `set_code_hash` generic - [#1906](https://github.com/paritytech/ink/pull/1906)
- Clean E2E configuration parsing - [#1922](https://github.com/paritytech/ink/pull/1922)

### Changed
- Fail when decoding from storage and not all bytes consumed - [#1897](https://github.com/paritytech/ink/pull/1897)

### Added
- Linter: `storage_never_freed` lint - [#1932](https://github.com/paritytech/ink/pull/1932)

Expand Down
28 changes: 10 additions & 18 deletions crates/engine/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ use crate::{
use scale::Encode;
use std::panic::panic_any;

type Result = core::result::Result<(), Error>;

macro_rules! define_error_codes {
(
$(
Expand All @@ -56,7 +54,7 @@ macro_rules! define_error_codes {
Unknown,
}

impl From<ReturnCode> for Result {
impl From<ReturnCode> for Result<(), Error> {
#[inline]
fn from(return_code: ReturnCode) -> Self {
match return_code.0 {
Expand Down Expand Up @@ -177,7 +175,7 @@ impl Default for Engine {

impl Engine {
/// Transfers value from the contract to the destination account.
pub fn transfer(&mut self, account_id: &[u8], mut value: &[u8]) -> Result {
pub fn transfer(&mut self, account_id: &[u8], mut value: &[u8]) -> Result<(), Error> {
// Note that a transfer of `0` is allowed here
let increment = <u128 as scale::Decode>::decode(&mut value)
.map_err(|_| Error::TransferFailed)?;
Expand Down Expand Up @@ -240,33 +238,27 @@ impl Engine {
.map(|v| <u32>::try_from(v.len()).expect("usize to u32 conversion failed"))
}

/// Returns the decoded contract storage at the key if any.
pub fn get_storage(&mut self, key: &[u8], output: &mut &mut [u8]) -> Result {
/// Returns the contract storage bytes at the key if any.
pub fn get_storage(&mut self, key: &[u8]) -> Result<&[u8], Error> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);

self.debug_info.inc_reads(account_id);
match self.database.get_from_contract_storage(&callee, key) {
Some(val) => {
set_output(output, val);
Ok(())
}
Some(val) => Ok(val),
None => Err(Error::KeyNotFound),
}
}

/// Removes the storage entries at the given key,
/// returning previously stored value at the key if any.
pub fn take_storage(&mut self, key: &[u8], output: &mut &mut [u8]) -> Result {
pub fn take_storage(&mut self, key: &[u8]) -> Result<Vec<u8>, Error> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);

self.debug_info.inc_writes(account_id);
match self.database.remove_contract_storage(&callee, key) {
Some(val) => {
set_output(output, &val);
Ok(())
}
Some(val) => Ok(val),
None => Err(Error::KeyNotFound),
}
}
Expand Down Expand Up @@ -425,7 +417,7 @@ impl Engine {
_out_address: &mut &mut [u8],
_out_return_value: &mut &mut [u8],
_salt: &[u8],
) -> Result {
) -> Result<(), Error> {
unimplemented!("off-chain environment does not yet support `instantiate`");
}

Expand All @@ -436,7 +428,7 @@ impl Engine {
_value: &[u8],
_input: &[u8],
_output: &mut &mut [u8],
) -> Result {
) -> Result<(), Error> {
unimplemented!("off-chain environment does not yet support `call`");
}

Expand Down Expand Up @@ -475,7 +467,7 @@ impl Engine {
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result {
) -> Result<(), Error> {
use secp256k1::{
ecdsa::{
RecoverableSignature,
Expand Down
5 changes: 2 additions & 3 deletions crates/engine/src/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,17 +334,16 @@ mod tests {
// given
let mut engine = Engine::new();
let key: &[u8; 32] = &[0x42; 32];
let mut buf = [0_u8; 32];

// when
engine.set_callee(vec![1; 32]);
engine.set_storage(key, &[0x05_u8; 5]);
engine.set_storage(key, &[0x05_u8; 6]);
engine.get_storage(key, &mut &mut buf[..]).unwrap();
engine.get_storage(key).unwrap();

engine.set_callee(vec![2; 32]);
engine.set_storage(key, &[0x07_u8; 7]);
engine.get_storage(key, &mut &mut buf[..]).unwrap();
engine.get_storage(key).unwrap();

// then
assert_eq!(engine.count_writes(), 3);
Expand Down
31 changes: 5 additions & 26 deletions crates/engine/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,16 @@ fn store_load_clear() {
let mut engine = Engine::new();
engine.set_callee(vec![1; 32]);
let key: &[u8; 32] = &[0x42; 32];
let output = &mut &mut get_buffer()[..];
let res = engine.get_storage(key, output);
let res = engine.get_storage(key);
assert_eq!(res, Err(Error::KeyNotFound));

engine.set_storage(key, &[0x05_u8; 5]);
let res = engine.get_storage(key, output);
assert_eq!(res, Ok(()),);
assert_eq!(output[..5], [0x05; 5]);
let res = engine.get_storage(key);
assert!(res.is_ok());
assert_eq!(res.unwrap()[..5], [0x05; 5]);

engine.clear_storage(key);
let res = engine.get_storage(key, output);
let res = engine.get_storage(key);
assert_eq!(res, Err(Error::KeyNotFound));
}

Expand Down Expand Up @@ -180,26 +179,6 @@ fn value_transferred() {
assert_eq!(output, value);
}

#[test]
#[should_panic(
expected = "the output buffer is too small! the decoded storage is of size 16 bytes, but the output buffer has only room for 8."
)]
fn must_panic_when_buffer_too_small() {
// given
let mut engine = Engine::new();
engine.set_callee(vec![1; 32]);
let key: &[u8; 32] = &[0x42; 32];
engine.set_storage(key, &[0x05_u8; 16]);

// when
let mut small_buffer = [0; 8];
let output = &mut &mut small_buffer[..];
let _ = engine.get_storage(key, output);

// then
unreachable!("`get_storage` must already have panicked");
}

#[test]
fn ecdsa_recovery_test_from_contracts_pallet() {
// given
Expand Down
32 changes: 16 additions & 16 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ use ink_engine::{
ext,
ext::Engine,
};
use ink_storage_traits::Storable;
use ink_storage_traits::{
decode_all,
Storable,
};
use schnorrkel::{
PublicKey,
Signature,
Expand Down Expand Up @@ -202,32 +205,29 @@ impl EnvBackend for EnvInstance {
K: scale::Encode,
R: Storable,
{
let mut output: [u8; 9600] = [0; 9600];
match self.engine.get_storage(&key.encode(), &mut &mut output[..]) {
Ok(_) => (),
Err(ext::Error::KeyNotFound) => return Ok(None),
match self.engine.get_storage(&key.encode()) {
Ok(res) => {
let decoded = decode_all(&mut &res[..])?;
Ok(Some(decoded))
}
Err(ext::Error::KeyNotFound) => Ok(None),
Err(_) => panic!("encountered unexpected error"),
}
let decoded = Storable::decode(&mut &output[..])?;
Ok(Some(decoded))
}

fn take_contract_storage<K, R>(&mut self, key: &K) -> Result<Option<R>>
where
K: scale::Encode,
R: Storable,
{
let mut output: [u8; 9600] = [0; 9600];
match self
.engine
.take_storage(&key.encode(), &mut &mut output[..])
{
Ok(_) => (),
Err(ext::Error::KeyNotFound) => return Ok(None),
match self.engine.take_storage(&key.encode()) {
Ok(output) => {
let decoded = decode_all(&mut &output[..])?;
Ok(Some(decoded))
}
Err(ext::Error::KeyNotFound) => Ok(None),
Err(_) => panic!("encountered unexpected error"),
}
let decoded = Storable::decode(&mut &output[..])?;
Ok(Some(decoded))
}

fn contains_contract_storage<K>(&mut self, key: &K) -> Option<u32>
Expand Down
9 changes: 6 additions & 3 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ use crate::{
ReturnFlags,
TypedEnvBackend,
};
use ink_storage_traits::Storable;
use ink_storage_traits::{
decode_all,
Storable,
};

impl CryptoHash for Blake2x128 {
fn hash(input: &[u8], output: &mut <Self as HashOutput>::Type) {
Expand Down Expand Up @@ -236,7 +239,7 @@ impl EnvBackend for EnvInstance {
Err(ExtError::KeyNotFound) => return Ok(None),
Err(_) => panic!("encountered unexpected error"),
}
let decoded = Storable::decode(&mut &output[..])?;
let decoded = decode_all(&mut &output[..])?;
Ok(Some(decoded))
}

Expand All @@ -253,7 +256,7 @@ impl EnvBackend for EnvInstance {
Err(ExtError::KeyNotFound) => return Ok(None),
Err(_) => panic!("encountered unexpected error"),
}
let decoded = Storable::decode(&mut &output[..])?;
let decoded = decode_all(&mut &output[..])?;
Ok(Some(decoded))
}

Expand Down
1 change: 1 addition & 0 deletions crates/storage/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub use self::{
ResolverKey,
},
storage::{
decode_all,
AutoStorableHint,
Packed,
Storable,
Expand Down
13 changes: 13 additions & 0 deletions crates/storage/traits/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ where
}
}

/// Decode and consume all of the given input data.
///
/// If not all data is consumed, an error is returned.
pub fn decode_all<T: Storable>(input: &mut &[u8]) -> Result<T, scale::Error> {
let res = <T as Storable>::decode(input)?;

if input.is_empty() {
Ok(res)
} else {
Err("Input buffer has still data left after decoding!".into())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps would be useful to give the length of the remaining bytes left in a buffer for debugging purposes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it is worth bringing in all the string formatting machinery in for that...we need to be mindful of code size.

It's possible to debug this without this information by fetching the data at the given key and attempting to decode it into the respective type.

}
}

pub(crate) mod private {
/// Seals the implementation of `Packed`.
pub trait Sealed {}
Expand Down
9 changes: 9 additions & 0 deletions integration-tests/contract-storage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
23 changes: 23 additions & 0 deletions integration-tests/contract-storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "contract-storage"
version = "4.2.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
publish = false

[dependencies]
ink = { path = "../../crates/ink", default-features = false }

[dev-dependencies]
ink_e2e = { path = "../../crates/e2e" }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
]
ink-as-dependency = []
e2e-tests = []
Loading