Skip to content

Commit

Permalink
wip: Contract abi
Browse files Browse the repository at this point in the history
  • Loading branch information
Rexagon committed Sep 5, 2023
1 parent 17a9301 commit 2b19f8e
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 15 deletions.
173 changes: 173 additions & 0 deletions src/abi/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use ahash::HashMap;
use anyhow::Result;
use sha2::Digest;

use crate::abi::AbiValue;
use crate::prelude::CellSlice;

use super::{AbiHeaderType, AbiVersion, NamedAbiType, NamedAbiValue};
use crate::abi::error::AbiError;

pub struct Contract {

Check warning on line 11 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Check

struct `Contract` is never constructed

Check failure on line 11 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

struct `Contract` is never constructed

Check warning on line 11 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Test Suite

struct `Contract` is never constructed
pub abi_version: AbiVersion,
pub header: Vec<AbiHeaderType>,
pub functions: HashMap<String, Function>,
}

pub struct Function {

Check warning on line 17 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Check

struct `Function` is never constructed

Check failure on line 17 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

struct `Function` is never constructed

Check warning on line 17 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Test Suite

struct `Function` is never constructed
pub abi_version: AbiVersion,
pub header: Vec<AbiHeaderType>,
pub name: String,
pub inputs: Vec<NamedAbiType>,
pub outputs: Vec<NamedAbiType>,
pub input_id: u32,
pub output_id: u32,
}

impl Function {
pub fn compute_function_id(

Check warning on line 28 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Check

associated items `compute_function_id`, `decode_internal_input`, `decode_internal_input_ext`, `decode_output`, `decode_output_ext`, and `display_signature` are never used

Check failure on line 28 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

associated items `compute_function_id`, `decode_internal_input`, `decode_internal_input_ext`, `decode_output`, `decode_output_ext`, and `display_signature` are never used

Check warning on line 28 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Test Suite

associated items `compute_function_id`, `decode_internal_input`, `decode_internal_input_ext`, `decode_output`, `decode_output_ext`, and `display_signature` are never used
abi_version: AbiVersion,
name: &str,
header: &[AbiHeaderType],
inputs: &[NamedAbiType],
outputs: &[NamedAbiType],
) -> u32 {
let mut hasher = sha2::Sha256::new();
FunctionSignatureRaw {
abi_version,
name,
header,
inputs,
outputs,
}
.update_hasher(&mut hasher);

let hash: [u8; 32] = hasher.finalize().into();
u32::from_be_bytes(hash[0..4].try_into().unwrap())
}

pub fn decode_internal_input(&self, mut slice: CellSlice<'_>) -> Result<Vec<NamedAbiValue>> {
self.decode_internal_input_ext(&mut slice, false)
}

pub fn decode_internal_input_ext(
&self,
slice: &mut CellSlice<'_>,
allow_partial: bool,
) -> Result<Vec<NamedAbiValue>> {
let id = slice.load_u32()?;
anyhow::ensure!(
id == self.input_id,
AbiError::InputIdMismatch {
expected: self.input_id,
id
}
);
let res = ok!(NamedAbiValue::load_tuple_ext(
&self.inputs,
self.abi_version,
true,
allow_partial,
slice
));
ok!(AbiValue::check_remaining(slice, allow_partial));
Ok(res)
}

pub fn decode_output(&self, mut slice: CellSlice<'_>) -> Result<Vec<NamedAbiValue>> {
self.decode_output_ext(&mut slice, false)
}

pub fn decode_output_ext(
&self,
slice: &mut CellSlice<'_>,
allow_partial: bool,
) -> Result<Vec<NamedAbiValue>> {
let id = slice.load_u32()?;
anyhow::ensure!(
id == self.output_id,
AbiError::OutputIdMismatch {
expected: self.output_id,
id
}
);
let res = ok!(NamedAbiValue::load_tuple_ext(
&self.outputs,
self.abi_version,
true,
allow_partial,
slice
));
ok!(AbiValue::check_remaining(slice, allow_partial));
Ok(res)
}

pub fn display_signature(&self) -> impl std::fmt::Display + '_ {
FunctionSignatureRaw {
abi_version: self.abi_version,
name: &self.name,
header: &self.header,
inputs: &self.inputs,
outputs: &self.outputs,
}
}
}

struct FunctionSignatureRaw<'a> {
abi_version: AbiVersion,
name: &'a str,
header: &'a [AbiHeaderType],
inputs: &'a [NamedAbiType],
outputs: &'a [NamedAbiType],
}

impl FunctionSignatureRaw<'_> {
fn update_hasher<H: Digest>(&self, engine: &mut H) {

Check warning on line 125 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Check

method `update_hasher` is never used

Check failure on line 125 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

method `update_hasher` is never used

Check warning on line 125 in src/abi/contract.rs

View workflow job for this annotation

GitHub Actions / Test Suite

method `update_hasher` is never used
#[repr(transparent)]
struct Hasher<'a, H>(&'a mut H);

impl<H: Digest> std::fmt::Write for Hasher<'_, H> {
#[inline]
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.update(s.as_bytes());
Ok(())
}
}

std::fmt::write(&mut Hasher(engine), format_args!("{self}")).unwrap();
}
}

impl std::fmt::Display for FunctionSignatureRaw<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ok!(write!(f, "{}(", self.name));

let mut first = true;
if self.abi_version.major == 1 {
for header in self.header {
if !std::mem::take(&mut first) {
ok!(f.write_str(","));
}
ok!(std::fmt::Display::fmt(header, f));
}
}
for item in self.inputs {
if !std::mem::take(&mut first) {
ok!(f.write_str(","));
}
ok!(std::fmt::Display::fmt(&item.ty, f));
}

ok!(f.write_str(")("));

first = true;
for item in self.outputs {
if !std::mem::take(&mut first) {
ok!(f.write_str(","));
}
ok!(std::fmt::Display::fmt(&item.ty, f));
}

write!(f, ")v{}", self.abi_version.major)
}
}
16 changes: 16 additions & 0 deletions src/abi/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,20 @@ pub enum AbiError {
/// Length of the parsed bytes.
len: usize,
},
/// Expected a different function id while decoding function input.
#[error("expected function input id 0x{expected:08x}, got 0x{id:08x}")]
InputIdMismatch {
/// Function input id.
expected: u32,
/// Id from parsed data.
id: u32,
},
/// Expected a different function id while decoding function output.
#[error("expected function output id 0x{expected:08x}, got 0x{id:08x}")]
OutputIdMismatch {
/// Function output id.
expected: u32,
/// Id from parsed data.
id: u32,
},
}
1 change: 1 addition & 0 deletions src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub use self::value::{AbiHeader, AbiValue, NamedAbiValue, PlainAbiValue};

pub mod error;

mod contract;
mod traits;
mod ty;
mod value;
Expand Down
31 changes: 16 additions & 15 deletions src/abi/value/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl NamedAbiValue {
slice: &mut CellSlice,
) -> Result<Vec<Self>> {
let result = ok!(Self::load_tuple_ext(items, version, true, false, slice));
ok!(ensure_slice_empty(slice, false));
ok!(AbiValue::check_remaining(slice, false));
Ok(result)
}

Expand Down Expand Up @@ -68,7 +68,7 @@ impl NamedAbiValue {
/// Use [`NamedAbiValue::load_partial`] if you allow an ABI type to be a prefix.
pub fn load(ty: &NamedAbiType, version: AbiVersion, slice: &mut CellSlice) -> Result<Self> {
let res = ok!(Self::load_ext(ty, version, true, false, slice));
ok!(ensure_slice_empty(slice, false));
ok!(AbiValue::check_remaining(slice, false));
Ok(res)
}

Expand Down Expand Up @@ -105,6 +105,15 @@ impl NamedAbiValue {
}

impl AbiValue {
/// Checks whether the slice is empty and raises an error if we didn't expect it to be empty.
pub fn check_remaining(slice: &mut CellSlice, allow_partial: bool) -> Result<()> {
anyhow::ensure!(
allow_partial || slice.is_data_empty() && slice.is_refs_empty(),
AbiError::IncompleteDeserialization
);
Ok(())
}

/// Loads exactly one unnamed tuple from the specified slice requiring it to be fully consumed.
///
/// Use [`AbiValue::load_tuple_partial`] if you allow an ABI type to be a prefix.
Expand All @@ -114,7 +123,7 @@ impl AbiValue {
slice: &mut CellSlice,
) -> Result<Vec<Self>> {
let res = ok!(Self::load_tuple_ext(types, version, true, false, slice));
ok!(ensure_slice_empty(slice, false));
ok!(Self::check_remaining(slice, false));
Ok(res)
}

Expand Down Expand Up @@ -151,7 +160,7 @@ impl AbiValue {
/// Use [`AbiValue::load_partial`] if you allow an ABI type to be a prefix.
pub fn load(ty: &AbiType, version: AbiVersion, slice: &mut CellSlice) -> Result<Self> {
let res = ok!(Self::load_ext(ty, version, true, false, slice));
ok!(ensure_slice_empty(slice, false));
ok!(Self::check_remaining(slice, false));
Ok(res)
}

Expand Down Expand Up @@ -260,14 +269,6 @@ fn preload_bits(bits: u16, slice: &mut CellSlice) -> Result<()> {
Ok(())
}

fn ensure_slice_empty(slice: &mut CellSlice, allow_partial: bool) -> Result<()> {
anyhow::ensure!(
allow_partial || slice.is_data_empty() && slice.is_refs_empty(),
AbiError::IncompleteDeserialization
);
Ok(())
}

fn load_uint(bits: u16, slice: &mut CellSlice) -> Result<BigUint> {
ok!(preload_bits(bits, slice));
load_uint_plain(bits, slice).map_err(From::from)
Expand Down Expand Up @@ -421,7 +422,7 @@ fn load_array_raw(
for value in dict.values().take(len) {
let slice = &mut value?;
let value = ok!(AbiValue::load_ext(ty, version, true, allow_partial, slice));
ok!(ensure_slice_empty(slice, allow_partial));
ok!(AbiValue::check_remaining(slice, allow_partial));
result.push(value);
}

Expand Down Expand Up @@ -507,7 +508,7 @@ fn load_optional(
let cell = ok!(load_cell(version, last, slice));
let slice = &mut cell.as_slice()?;
let value = ok!(AbiValue::load_ext(ty, version, true, allow_partial, slice));
ok!(ensure_slice_empty(slice, allow_partial));
ok!(AbiValue::check_remaining(slice, allow_partial));
Ok(Some(Box::new(value)))
}
}
Expand All @@ -522,7 +523,7 @@ fn load_ref(
let cell = ok!(load_cell(version, last, slice));
let slice = &mut cell.as_slice()?;
let value = ok!(AbiValue::load_ext(ty, version, true, allow_partial, slice));
ok!(ensure_slice_empty(slice, allow_partial));
ok!(AbiValue::check_remaining(slice, allow_partial));
Ok(Box::new(value))
}

Expand Down

0 comments on commit 2b19f8e

Please sign in to comment.