Skip to content

Commit

Permalink
feat!: represent small values as single bytes (#1163)
Browse files Browse the repository at this point in the history
  • Loading branch information
xunilrj committed Nov 10, 2023
1 parent 4b0aa6b commit d8704e1
Show file tree
Hide file tree
Showing 54 changed files with 593 additions and 629 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ env:
FUEL_CORE_VERSION: 0.20.6
RUST_VERSION: 1.72.1
FORC_VERSION: 0.46.0
FORC_PATCH_BRANCH: ""
FORC_PATCH_BRANCH: "xunilrj/fix-implicit-std-env-vars"
FORC_PATCH_REVISION: ""
FORC_IMPLICIT_STD_GIT_BRANCH: "xunilrj/fix-implicit-std-env-vars"

jobs:
setup-test-projects:
Expand Down
4 changes: 2 additions & 2 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ mod tests {
.await?;
// ANCHOR_END: contract_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 397);
assert_eq!(transaction_cost.gas_used, 470);

Ok(())
}
Expand Down Expand Up @@ -649,7 +649,7 @@ mod tests {
.await?;
// ANCHOR_END: multi_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 618);
assert_eq!(transaction_cost.gas_used, 693);

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-accounts/src/accounts_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use fuels_core::{
constants::BASE_ASSET_ID,
types::{
bech32::Bech32Address,
errors::{error, Error, Result},
errors::{error, Result},
input::Input,
transaction_builders::TransactionBuilder,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-accounts/src/provider/retry_util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fmt::Debug, future::Future, num::NonZeroU32, time::Duration};

use fuels_core::types::errors::{error, Error, Result as SdkResult};
use fuels_core::types::errors::{error, Result as SdkResult};

/// A set of strategies to control retry intervals between attempts.
///
Expand Down
5 changes: 1 addition & 4 deletions packages/fuels-accounts/src/provider/retryable_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ use fuel_core_client::client::{
};
use fuel_tx::{Receipt, Transaction, TxId, UtxoId};
use fuel_types::{Address, AssetId, BlockHeight, ContractId, MessageId, Nonce};
use fuels_core::{
error,
types::errors::{Error, Result},
};
use fuels_core::{error, types::errors::Result};

use crate::provider::{retry_util, RetryConfig};

Expand Down
74 changes: 29 additions & 45 deletions packages/fuels-core/src/codec/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ impl ABIDecoder {
///
/// let decoder = ABIDecoder::default();
///
/// let token = decoder.decode(&ParamType::U8, &[0, 0, 0, 0, 0, 0, 0, 7]).unwrap();
/// let token = decoder.decode(&ParamType::U64, &[0, 0, 0, 0, 0, 0, 0, 7]).unwrap();
///
/// assert_eq!(u8::from_token(token).unwrap(), 7u8);
/// assert_eq!(u64::from_token(token).unwrap(), 7u64);
/// ```
pub fn decode(&self, param_type: &ParamType, bytes: &[u8]) -> Result<Token> {
BoundedDecoder::new(self.config).decode(param_type, bytes)
}

/// Decode data from one of the receipt returns.
pub fn decode_receipt_return(&self, param_type: &ParamType, bytes: &[u8]) -> Result<Token> {
BoundedDecoder::new(self.config).decode(param_type, bytes)
}

/// Same as `decode` but decodes multiple `ParamType`s in one go.
/// # Examples
/// ```
Expand All @@ -68,7 +73,7 @@ impl ABIDecoder {
/// use fuels_core::types::Token;
///
/// let decoder = ABIDecoder::default();
/// let data: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 8];
/// let data: &[u8] = &[7, 8];
///
/// let tokens = decoder.decode_multiple(&[ParamType::U8, ParamType::U8], &data).unwrap();
///
Expand Down Expand Up @@ -114,7 +119,7 @@ mod tests {
];
let data = [
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, // u32
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, // u8
0xff, // u8
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, // u16
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // u64
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
Expand All @@ -141,9 +146,7 @@ mod tests {
#[test]
fn decode_bool() -> Result<()> {
let types = vec![ParamType::Bool, ParamType::Bool];
let data = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00,
];
let data = [0x01, 0x0];

let decoded = ABIDecoder::default().decode_multiple(&types, &data)?;

Expand Down Expand Up @@ -215,9 +218,7 @@ mod tests {
fn decode_array() -> Result<()> {
// Create a parameter type for u8[2].
let types = vec![ParamType::Array(Box::new(ParamType::U8), 2)];
let data = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a,
];
let data = [0xff, 0x2a];

let decoded = ABIDecoder::default().decode_multiple(&types, &data)?;

Expand All @@ -234,7 +235,7 @@ mod tests {
// }

let data = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let param_type = ParamType::Struct {
fields: vec![ParamType::U8, ParamType::Bool],
Expand Down Expand Up @@ -366,8 +367,8 @@ mod tests {
};

let data = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];

let decoded = ABIDecoder::default().decode(&nested_struct, &data)?;
Expand Down Expand Up @@ -416,26 +417,23 @@ mod tests {

let u8_arr = ParamType::Array(Box::new(ParamType::U8), 2);
let b256 = ParamType::B256;
let s = ParamType::StringArray(3);
let ss = ParamType::StringSlice;

let types = [nested_struct, u8_arr, b256, s, ss];
let types = [nested_struct, u8_arr, b256];

let bytes = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // foo.x == 10u16
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.y.a == true
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.b.0 == 1u8
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // foo.b.1 == 2u8
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // u8[2].0 == 1u8
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, // u8[2].0 == 2u8
0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, // b256
0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // b256
0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // b256
0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, 0xb, // b256
0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, // str[3]
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str data
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str data
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // str data
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // u16
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // bool
0x1, 0x2, // array[u8]
0x1, 0x2, // array[u8]
0xd5, 0x57, 0x9c, 0x46, 0xdf, 0xcc, 0x7f, 0x18, // b256 start
0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, //
0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, //
0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93,
0xb, // b256 end
// 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, // "foo"
// 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, //
// 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, //
// 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, //
];

let decoded = ABIDecoder::default().decode_multiple(&types, &bytes)?;
Expand All @@ -457,14 +455,7 @@ mod tests {
0xf3, 0x1e, 0x93, 0xb,
]);

let ss = Token::StringSlice(StaticStringToken::new(
"This is a full sentence".into(),
None,
));

let s = Token::StringArray(StaticStringToken::new("foo".into(), Some(3)));

let expected: Vec<Token> = vec![foo, u8_arr, b256, s, ss];
let expected: Vec<Token> = vec![foo, u8_arr, b256];

assert_eq!(decoded, expected);
Ok(())
Expand Down Expand Up @@ -555,13 +546,6 @@ mod tests {
assert!(matches!(result, Err(Error::InvalidType(_))));
}

#[test]
pub fn multiply_overflow_vector() {
let param_type = Vec::<[(); usize::MAX]>::param_type();
let result = ABIDecoder::default().decode(&param_type, &[]);
assert!(matches!(result, Err(Error::InvalidData(_))));
}

#[test]
pub fn multiply_overflow_arith() {
let mut param_type: ParamType = U16;
Expand Down
94 changes: 64 additions & 30 deletions packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::{convert::TryInto, str};

use fuel_types::bytes::padded_len_usize;

use crate::{
codec::DecoderConfig,
constants::WORD_SIZE,
round_up_to_word_alignment,
traits::Tokenizable,
types::{
enum_variants::EnumVariants,
errors::{error, Error, Result},
errors::{error, Result},
param_types::ParamType,
StaticStringToken, Token, U256,
},
Expand Down Expand Up @@ -37,9 +36,25 @@ impl BoundedDecoder {
}
}

pub(crate) fn decode(&mut self, param_type: &ParamType, bytes: &[u8]) -> Result<Token> {
pub fn decode(&mut self, param_type: &ParamType, bytes: &[u8]) -> Result<Token> {
param_type.validate_is_decodable(self.config.max_depth)?;
Ok(self.decode_param(param_type, bytes)?.token)
match param_type {
// Unit, U8 and Bool are returned as u64 from receipt "Return"
ParamType::Unit => Ok(Token::Unit),
ParamType::U8 => Self::decode_u64(bytes).map(|r| {
Token::U8(match r.token {
Token::U64(v) => v as u8,
_ => unreachable!("decode_u64 returning unexpected token"),
})
}),
ParamType::Bool => Self::decode_u64(bytes).map(|r| {
Token::Bool(match r.token {
Token::U64(v) => v != 0,
_ => unreachable!("decode_u64 returning unexpected token"),
})
}),
_ => self.decode_param(param_type, bytes).map(|x| x.token),
}
}

pub(crate) fn decode_multiple(
Expand Down Expand Up @@ -130,7 +145,17 @@ impl BoundedDecoder {
}

fn decode_tuple(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result<Decoded> {
let (tokens, bytes_read) = self.decode_params(param_types, bytes)?;
let mut tokens = vec![];

let mut bytes_read = 0;

for param_type in param_types.iter() {
// padding has to be taken into account
bytes_read = round_up_to_word_alignment(bytes_read);
let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?;
bytes_read += res.bytes_read;
tokens.push(res.token);
}

Ok(Decoded {
token: Token::Tuple(tokens),
Expand All @@ -139,7 +164,17 @@ impl BoundedDecoder {
}

fn decode_struct(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result<Decoded> {
let (tokens, bytes_read) = self.decode_params(param_types, bytes)?;
let mut tokens = vec![];

let mut bytes_read = 0;

for param_type in param_types.iter() {
// padding has to be taken into account
bytes_read = round_up_to_word_alignment(bytes_read);
let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?;
bytes_read += res.bytes_read;
tokens.push(res.token);
}

Ok(Decoded {
token: Token::Struct(tokens),
Expand Down Expand Up @@ -209,13 +244,12 @@ impl BoundedDecoder {
}

fn decode_string_array(bytes: &[u8], length: usize) -> Result<Decoded> {
let encoded_len = padded_len_usize(length);
let encoded_str = peek(bytes, encoded_len)?;
let encoded_str = peek(bytes, length)?;

let decoded = str::from_utf8(&encoded_str[..length])?;
let decoded = str::from_utf8(encoded_str)?;
let result = Decoded {
token: Token::StringArray(StaticStringToken::new(decoded.into(), Some(length))),
bytes_read: encoded_len,
bytes_read: round_up_to_word_alignment(length),
};
Ok(result)
}
Expand All @@ -233,7 +267,7 @@ impl BoundedDecoder {

let result = Decoded {
token: Token::Bool(b),
bytes_read: WORD_SIZE,
bytes_read: 1,
};

Ok(result)
Expand Down Expand Up @@ -277,17 +311,17 @@ impl BoundedDecoder {
fn decode_u8(bytes: &[u8]) -> Result<Decoded> {
Ok(Decoded {
token: Token::U8(peek_u8(bytes)?),
bytes_read: WORD_SIZE,
bytes_read: 1,
})
}

fn decode_unit(bytes: &[u8]) -> Result<Decoded> {
// We don't need the data, we're doing this purely as a bounds
// check.
peek_fixed::<WORD_SIZE>(bytes)?;
peek_fixed::<1>(bytes)?;
Ok(Decoded {
token: Token::Unit,
bytes_read: WORD_SIZE,
bytes_read: 1,
})
}

Expand All @@ -299,33 +333,33 @@ impl BoundedDecoder {
/// * `data`: slice of encoded data on whose beginning we're expecting an encoded enum
/// * `variants`: all types that this particular enum type could hold
fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result<Decoded> {
let enum_width = variants.compute_encoding_width_of_enum()?;
let enum_width_in_bytes = variants
.compute_enum_width_in_bytes()
.ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?;

let discriminant = peek_u32(bytes)? as u8;
let discriminant = peek_u64(bytes)?;
let selected_variant = variants.param_type_of_variant(discriminant)?;

let skip_extra = variants
let skip_extra_in_bytes = variants
.heap_type_variant()
.and_then(|(heap_discriminant, heap_type)| {
(heap_discriminant == discriminant).then_some(heap_type.compute_encoding_width())
(heap_discriminant == discriminant).then_some(heap_type.compute_encoding_in_bytes())
})
.transpose()?
.unwrap_or_default()
.unwrap_or_default();
let bytes_to_skip = enum_width_in_bytes
- selected_variant
.compute_encoding_in_bytes()
.ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?
+ skip_extra_in_bytes;

let words_to_skip = enum_width - selected_variant.compute_encoding_width()? + skip_extra;
let bytes_to_skip = words_to_skip.checked_mul(WORD_SIZE).ok_or_else(|| {
error!(
InvalidData,
"Overflow error while decoding enum {variants:?}"
)
})?;
let enum_content_bytes = skip(bytes, bytes_to_skip)?;
let result = self.decode_token_in_enum(enum_content_bytes, variants, selected_variant)?;

let selector = Box::new((discriminant, result.token, variants.clone()));
Ok(Decoded {
token: Token::Enum(selector),
bytes_read: enum_width * WORD_SIZE,
bytes_read: enum_width_in_bytes,
})
}

Expand Down Expand Up @@ -426,8 +460,8 @@ fn peek_u16(bytes: &[u8]) -> Result<u16> {
fn peek_u8(bytes: &[u8]) -> Result<u8> {
const BYTES: usize = std::mem::size_of::<u8>();

let slice = peek_fixed::<WORD_SIZE>(bytes)?;
let bytes = slice[WORD_SIZE - BYTES..]
let slice = peek_fixed::<1>(bytes)?;
let bytes = slice[1 - BYTES..]
.try_into()
.expect("peek_u8: You must use a slice containing exactly 1B.");
Ok(u8::from_be_bytes(bytes))
Expand Down
Loading

0 comments on commit d8704e1

Please sign in to comment.