sway_types/
lib.rs

1use fuel_asm::Word;
2use fuel_crypto::Hasher;
3use fuel_tx::{Bytes32, ContractId};
4use serde::{Deserialize, Serialize};
5use std::hash::Hash;
6use std::path::{Path, PathBuf};
7use std::{io, iter, slice};
8
9pub mod constants;
10
11pub mod ident;
12pub mod u256;
13pub use ident::*;
14
15pub mod integer_bits;
16
17pub mod source_engine;
18pub use source_engine::*;
19
20pub mod span;
21pub use span::*;
22
23pub mod style;
24
25pub mod ast;
26
27pub type Id = [u8; Bytes32::LEN];
28pub type Contract = [u8; ContractId::LEN];
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31pub struct Position {
32    pub line: usize,
33    pub col: usize,
34}
35
36/// Based on `<https://llvm.org/docs/CoverageMappingFormat.html#source-code-range>`
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub struct Range {
39    /// Beginning of the code range
40    pub start: Position,
41    /// End of the code range (inclusive)
42    pub end: Position,
43}
44
45impl Range {
46    pub const fn is_valid(&self) -> bool {
47        self.start.line < self.end.line
48            || self.start.line == self.end.line && self.start.col <= self.end.col
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct Instruction {
54    /// Relative to the `$is`
55    pub pc: Word,
56    /// Code range that translates to this point
57    pub range: Range,
58    /// Exit from the current scope?
59    pub exit: bool,
60}
61
62impl Instruction {
63    pub fn to_bytes(&self) -> [u8; 41] {
64        let mut bytes = [0u8; 41];
65
66        // Always convert to `u64` to avoid architectural variants of the bytes representation that
67        // could lead to arch-dependent unique IDs
68        bytes[..8].copy_from_slice(&(self.pc).to_be_bytes());
69        bytes[8..16].copy_from_slice(&(self.range.start.line as u64).to_be_bytes());
70        bytes[16..24].copy_from_slice(&(self.range.start.col as u64).to_be_bytes());
71        bytes[24..32].copy_from_slice(&(self.range.end.line as u64).to_be_bytes());
72        bytes[32..40].copy_from_slice(&(self.range.end.col as u64).to_be_bytes());
73        bytes[40] = self.exit as u8;
74
75        bytes
76    }
77
78    pub fn bytes<'a>(iter: impl Iterator<Item = &'a Self>) -> Vec<u8> {
79        // Need to return owned bytes because flatten is not supported by 1.53 for arrays bigger
80        // than 32 bytes
81        iter.map(Self::to_bytes)
82            .fold::<Vec<u8>, _>(vec![], |mut v, b| {
83                v.extend(b);
84
85                v
86            })
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
91pub struct ProgramId(u16);
92
93impl ProgramId {
94    pub fn new(id: u16) -> Self {
95        Self(id)
96    }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
100pub struct SourceId(u32);
101
102impl SourceId {
103    const SOURCE_ID_BITS: u32 = 20;
104    const SOURCE_ID_MASK: u32 = (1 << Self::SOURCE_ID_BITS) - 1;
105
106    /// Create a combined ID from program and source IDs.
107    pub fn new(program_id: u16, source_id: u32) -> Self {
108        SourceId(((program_id as u32) << Self::SOURCE_ID_BITS) | source_id)
109    }
110
111    /// The [ProgramId] that this [SourceId] was created from.
112    pub fn program_id(&self) -> ProgramId {
113        ProgramId::new((self.0 >> Self::SOURCE_ID_BITS) as u16)
114    }
115
116    /// ID of the source file without the [ProgramId] component.
117    pub fn source_id(&self) -> u32 {
118        self.0 & Self::SOURCE_ID_MASK
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
123pub struct Source {
124    /// Absolute path to the source file
125    path: PathBuf,
126}
127
128impl<T> From<T> for Source
129where
130    T: Into<PathBuf>,
131{
132    fn from(path: T) -> Self {
133        Self { path: path.into() }
134    }
135}
136
137impl AsRef<PathBuf> for Source {
138    fn as_ref(&self) -> &PathBuf {
139        &self.path
140    }
141}
142
143impl AsRef<Path> for Source {
144    fn as_ref(&self) -> &Path {
145        self.path.as_ref()
146    }
147}
148
149impl AsMut<PathBuf> for Source {
150    fn as_mut(&mut self) -> &mut PathBuf {
151        &mut self.path
152    }
153}
154
155impl Source {
156    pub fn bytes(&self) -> io::Result<slice::Iter<'_, u8>> {
157        Ok(self
158            .path
159            .as_path()
160            .to_str()
161            .ok_or_else(|| {
162                io::Error::new(
163                    io::ErrorKind::Other,
164                    "Failed to get the string representation of the path!",
165                )
166            })?
167            .as_bytes()
168            .iter())
169    }
170}
171
172/// Contract call stack frame representation
173#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
174pub struct CallFrame {
175    /// Deterministic representation of the frame
176    id: Id,
177    /// Contract that contains the bytecodes of this frame. Currently only scripts are supported
178    contract: Contract,
179    /// Sway source code that compiles to this frame
180    source: Source,
181    /// Range of code that represents this frame
182    range: Range,
183    /// Set of instructions that describes this frame
184    program: Vec<Instruction>,
185}
186
187impl CallFrame {
188    pub fn new(
189        contract: ContractId,
190        source: Source,
191        range: Range,
192        program: Vec<Instruction>,
193    ) -> io::Result<Self> {
194        Context::validate_source(&source)?;
195        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
196
197        let contract = Contract::from(contract);
198
199        let id = Context::id_from_repr(
200            Instruction::bytes(program.iter())
201                .iter()
202                .chain(contract.iter())
203                .chain(source.bytes()?),
204        );
205
206        Ok(Self {
207            id,
208            contract,
209            source,
210            range,
211            program,
212        })
213    }
214
215    pub const fn id(&self) -> &Id {
216        &self.id
217    }
218
219    pub const fn source(&self) -> &Source {
220        &self.source
221    }
222
223    pub const fn range(&self) -> &Range {
224        &self.range
225    }
226
227    pub fn program(&self) -> &[Instruction] {
228        self.program.as_slice()
229    }
230
231    pub fn contract(&self) -> ContractId {
232        self.contract.into()
233    }
234}
235
236/// Transaction script interpreter representation
237#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
238pub struct TransactionScript {
239    /// Deterministic representation of the script
240    id: Id,
241    /// Sway source code that compiles to this script
242    source: Source,
243    /// Range of code that represents this script
244    range: Range,
245    /// Set of instructions that describes this script
246    program: Vec<Instruction>,
247}
248
249impl TransactionScript {
250    pub fn new(source: Source, range: Range, program: Vec<Instruction>) -> io::Result<Self> {
251        Context::validate_source(&source)?;
252        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
253
254        let id = Context::id_from_repr(
255            Instruction::bytes(program.iter())
256                .iter()
257                .chain(source.bytes()?),
258        );
259
260        Ok(Self {
261            id,
262            source,
263            range,
264            program,
265        })
266    }
267
268    pub const fn id(&self) -> &Id {
269        &self.id
270    }
271
272    pub const fn source(&self) -> &Source {
273        &self.source
274    }
275
276    pub const fn range(&self) -> &Range {
277        &self.range
278    }
279
280    pub fn program(&self) -> &[Instruction] {
281        self.program.as_slice()
282    }
283}
284
285// Representation of a debug context to be mapped from a sway source and consumed by the DAP-sway
286#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
287pub enum Context {
288    CallFrame(CallFrame),
289    TransactionScript(TransactionScript),
290}
291
292impl From<CallFrame> for Context {
293    fn from(frame: CallFrame) -> Self {
294        Self::CallFrame(frame)
295    }
296}
297
298impl From<TransactionScript> for Context {
299    fn from(script: TransactionScript) -> Self {
300        Self::TransactionScript(script)
301    }
302}
303
304impl Context {
305    pub fn validate_source<P>(path: P) -> io::Result<()>
306    where
307        P: AsRef<Path>,
308    {
309        if !path.as_ref().is_absolute() {
310            return Err(io::Error::new(
311                io::ErrorKind::InvalidData,
312                "The source path must be absolute!",
313            ));
314        }
315
316        if !path.as_ref().is_file() {
317            return Err(io::Error::new(
318                io::ErrorKind::InvalidData,
319                "The source path must be a valid Sway source file!",
320            ));
321        }
322
323        if !path.as_ref().exists() {
324            return Err(io::Error::new(
325                io::ErrorKind::NotFound,
326                "The source path must point to an existing file!",
327            ));
328        }
329
330        Ok(())
331    }
332
333    pub fn validate_range<'a>(mut range: impl Iterator<Item = &'a Range>) -> io::Result<()> {
334        if !range.any(|r| !r.is_valid()) {
335            Err(io::Error::new(
336                io::ErrorKind::InvalidData,
337                "The provided source range is inconsistent!",
338            ))
339        } else {
340            Ok(())
341        }
342    }
343
344    pub fn id_from_repr<'a>(bytes: impl Iterator<Item = &'a u8>) -> Id {
345        let bytes: Vec<u8> = bytes.copied().collect();
346
347        *Hasher::hash(bytes.as_slice())
348    }
349
350    pub const fn id(&self) -> &Id {
351        match self {
352            Self::CallFrame(t) => t.id(),
353            Self::TransactionScript(t) => t.id(),
354        }
355    }
356
357    pub const fn source(&self) -> &Source {
358        match self {
359            Self::CallFrame(t) => t.source(),
360            Self::TransactionScript(t) => t.source(),
361        }
362    }
363
364    pub const fn range(&self) -> &Range {
365        match self {
366            Self::CallFrame(t) => t.range(),
367            Self::TransactionScript(t) => t.range(),
368        }
369    }
370
371    pub fn program(&self) -> &[Instruction] {
372        match self {
373            Self::CallFrame(t) => t.program(),
374            Self::TransactionScript(t) => t.program(),
375        }
376    }
377
378    pub fn contract(&self) -> Option<ContractId> {
379        match self {
380            Self::CallFrame(t) => Some(t.contract()),
381            _ => None,
382        }
383    }
384}
385
386pub type FxBuildHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
387pub type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxBuildHasher>;
388pub type FxIndexSet<K> = indexmap::IndexSet<K, FxBuildHasher>;