Skip to content

Commit

Permalink
Add Context to encapsulate debug ctx variants
Browse files Browse the repository at this point in the history
A call frame holds a contract while a transaction script doesn't. This
structural difference is specific for the transaction script context,
and doesn't map the specifics of predicate calls - that should bring
more variants.

This commit introduces the `Context` type to encapsulate all of these
variants.
  • Loading branch information
vlopes11 committed Jun 17, 2021
1 parent 0b683de commit 05339d4
Showing 1 changed file with 186 additions and 48 deletions.
234 changes: 186 additions & 48 deletions core-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use fuel_asm::Word;
use fuel_tx::{crypto, ContractAddress, Hash};
use serde::{Deserialize, Serialize};
use std::io;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::{io, iter, slice};

pub type Id = [u8; Hash::size_of()];
pub type Contract = [u8; ContractAddress::size_of()];
Expand Down Expand Up @@ -54,6 +54,17 @@ impl Instruction {

bytes
}

pub fn bytes<'a>(iter: impl Iterator<Item = &'a Self>) -> Vec<u8> {
// Need to return owned bytes because flatten is not supported by 1.53 for arrays bigger
// than 32 bytes
iter.map(Self::to_bytes)
.fold::<Vec<u8>, _>(vec![], |mut v, b| {
v.extend(&b);

v
})
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
Expand All @@ -77,12 +88,35 @@ impl AsRef<PathBuf> for Source {
}
}

impl AsRef<Path> for Source {
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}

impl AsMut<PathBuf> for Source {
fn as_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
}

impl Source {
pub fn bytes(&self) -> io::Result<slice::Iter<'_, u8>> {
Ok(self
.path
.as_path()
.to_str()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"Failed to get the string representation of the path!",
)
})?
.as_bytes()
.iter())
}
}

/// Contract call stack frame representation
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CallFrame {
Expand All @@ -105,90 +139,194 @@ impl CallFrame {
range: Range,
program: Vec<Instruction>,
) -> io::Result<Self> {
Context::validate_source(&source)?;
Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;

let contract = Contract::from(contract);

if !source.path.as_path().is_absolute() {
let id = Context::id_from_repr(
Instruction::bytes(program.iter())
.iter()
.chain(contract.iter())
.chain(source.bytes()?),
);

Ok(Self {
id,
contract,
source,
range,
program,
})
}

pub const fn id(&self) -> &Id {
&self.id
}

pub const fn source(&self) -> &Source {
&self.source
}

pub const fn range(&self) -> &Range {
&self.range
}

pub fn program(&self) -> &[Instruction] {
self.program.as_slice()
}

pub fn contract(&self) -> ContractAddress {
self.contract.into()
}
}

/// Transaction script interpreter representation
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TransactionScript {
/// Deterministic representation of the script
id: Id,
/// Sway source code that compiles to this script
source: Source,
/// Range of code that represents this script
range: Range,
/// Set of instructions that describes this script
program: Vec<Instruction>,
}

impl TransactionScript {
pub fn new(source: Source, range: Range, program: Vec<Instruction>) -> io::Result<Self> {
Context::validate_source(&source)?;
Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;

let id = Context::id_from_repr(
Instruction::bytes(program.iter())
.iter()
.chain(source.bytes()?),
);

Ok(Self {
id,
source,
range,
program,
})
}

pub const fn id(&self) -> &Id {
&self.id
}

pub const fn source(&self) -> &Source {
&self.source
}

pub const fn range(&self) -> &Range {
&self.range
}

pub fn program(&self) -> &[Instruction] {
self.program.as_slice()
}
}

// Representation of a debug context to be mapped from a sway source and consumed by the DAP-sway
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Context {
CallFrame(CallFrame),
TransactionScript(TransactionScript),
}

impl From<CallFrame> for Context {
fn from(frame: CallFrame) -> Self {
Self::CallFrame(frame)
}
}

impl From<TransactionScript> for Context {
fn from(script: TransactionScript) -> Self {
Self::TransactionScript(script)
}
}

impl Context {
pub fn validate_source<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
if !path.as_ref().is_absolute() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"The source path must be absolute!",
));
}

if !source.path.as_path().is_file() {
if !path.as_ref().is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"The source path must be a valid Sway source file!",
));
}

if !source.path.as_path().exists() {
if !path.as_ref().exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"The source path must point to an existing file!",
));
}

if !range.is_valid() || program.iter().any(|i| !i.range.is_valid()) {
return Err(io::Error::new(
Ok(())
}

pub fn validate_range<'a>(mut range: impl Iterator<Item = &'a Range>) -> io::Result<()> {
if !range.any(|r| !r.is_valid()) {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"The provided source range is inconsistent!",
));
))
} else {
Ok(())
}
}

let mut repr: Vec<u8> = source
.path
.as_path()
.to_str()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"Failed to get the string representation of the path!",
)
})?
.as_bytes()
.iter()
.chain(contract.iter())
.copied()
.collect();

// IntoIter for 1.52.1 compat
program
.iter()
.map(Instruction::to_bytes)
.for_each(|b| repr.extend(&b));

let id = *crypto::hash(repr.as_slice());
pub fn id_from_repr<'a>(bytes: impl Iterator<Item = &'a u8>) -> Id {
let bytes: Vec<u8> = bytes.copied().collect();

Ok(Self {
id,
contract,
source,
range,
program,
})
*crypto::hash(bytes.as_slice())
}

pub const fn id(&self) -> &Id {
&self.id
match self {
Self::CallFrame(t) => t.id(),
Self::TransactionScript(t) => t.id(),
}
}

pub const fn source(&self) -> &Source {
&self.source
match self {
Self::CallFrame(t) => t.source(),
Self::TransactionScript(t) => t.source(),
}
}

pub const fn range(&self) -> &Range {
&self.range
}

pub const fn contract_raw(&self) -> &Contract {
&self.contract
match self {
Self::CallFrame(t) => t.range(),
Self::TransactionScript(t) => t.range(),
}
}

pub fn program(&self) -> &[Instruction] {
self.program.as_slice()
match self {
Self::CallFrame(t) => t.program(),
Self::TransactionScript(t) => t.program(),
}
}

pub fn contract(&self) -> ContractAddress {
self.contract.into()
pub fn contract(&self) -> Option<ContractAddress> {
match self {
Self::CallFrame(t) => Some(t.contract()),
_ => None,
}
}
}

0 comments on commit 05339d4

Please sign in to comment.