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::other("Failed to get the string representation of the path!")
163            })?
164            .as_bytes()
165            .iter())
166    }
167}
168
169/// Contract call stack frame representation
170#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
171pub struct CallFrame {
172    /// Deterministic representation of the frame
173    id: Id,
174    /// Contract that contains the bytecodes of this frame. Currently only scripts are supported
175    contract: Contract,
176    /// Sway source code that compiles to this frame
177    source: Source,
178    /// Range of code that represents this frame
179    range: Range,
180    /// Set of instructions that describes this frame
181    program: Vec<Instruction>,
182}
183
184impl CallFrame {
185    pub fn new(
186        contract: ContractId,
187        source: Source,
188        range: Range,
189        program: Vec<Instruction>,
190    ) -> io::Result<Self> {
191        Context::validate_source(&source)?;
192        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
193
194        let contract = Contract::from(contract);
195
196        let id = Context::id_from_repr(
197            Instruction::bytes(program.iter())
198                .iter()
199                .chain(contract.iter())
200                .chain(source.bytes()?),
201        );
202
203        Ok(Self {
204            id,
205            contract,
206            source,
207            range,
208            program,
209        })
210    }
211
212    pub const fn id(&self) -> &Id {
213        &self.id
214    }
215
216    pub const fn source(&self) -> &Source {
217        &self.source
218    }
219
220    pub const fn range(&self) -> &Range {
221        &self.range
222    }
223
224    pub fn program(&self) -> &[Instruction] {
225        self.program.as_slice()
226    }
227
228    pub fn contract(&self) -> ContractId {
229        self.contract.into()
230    }
231}
232
233/// Transaction script interpreter representation
234#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
235pub struct TransactionScript {
236    /// Deterministic representation of the script
237    id: Id,
238    /// Sway source code that compiles to this script
239    source: Source,
240    /// Range of code that represents this script
241    range: Range,
242    /// Set of instructions that describes this script
243    program: Vec<Instruction>,
244}
245
246impl TransactionScript {
247    pub fn new(source: Source, range: Range, program: Vec<Instruction>) -> io::Result<Self> {
248        Context::validate_source(&source)?;
249        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
250
251        let id = Context::id_from_repr(
252            Instruction::bytes(program.iter())
253                .iter()
254                .chain(source.bytes()?),
255        );
256
257        Ok(Self {
258            id,
259            source,
260            range,
261            program,
262        })
263    }
264
265    pub const fn id(&self) -> &Id {
266        &self.id
267    }
268
269    pub const fn source(&self) -> &Source {
270        &self.source
271    }
272
273    pub const fn range(&self) -> &Range {
274        &self.range
275    }
276
277    pub fn program(&self) -> &[Instruction] {
278        self.program.as_slice()
279    }
280}
281
282// Representation of a debug context to be mapped from a sway source and consumed by the DAP-sway
283#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
284pub enum Context {
285    CallFrame(CallFrame),
286    TransactionScript(TransactionScript),
287}
288
289impl From<CallFrame> for Context {
290    fn from(frame: CallFrame) -> Self {
291        Self::CallFrame(frame)
292    }
293}
294
295impl From<TransactionScript> for Context {
296    fn from(script: TransactionScript) -> Self {
297        Self::TransactionScript(script)
298    }
299}
300
301impl Context {
302    pub fn validate_source<P>(path: P) -> io::Result<()>
303    where
304        P: AsRef<Path>,
305    {
306        if !path.as_ref().is_absolute() {
307            return Err(io::Error::new(
308                io::ErrorKind::InvalidData,
309                "The source path must be absolute!",
310            ));
311        }
312
313        if !path.as_ref().is_file() {
314            return Err(io::Error::new(
315                io::ErrorKind::InvalidData,
316                "The source path must be a valid Sway source file!",
317            ));
318        }
319
320        if !path.as_ref().exists() {
321            return Err(io::Error::new(
322                io::ErrorKind::NotFound,
323                "The source path must point to an existing file!",
324            ));
325        }
326
327        Ok(())
328    }
329
330    pub fn validate_range<'a>(mut range: impl Iterator<Item = &'a Range>) -> io::Result<()> {
331        if !range.any(|r| !r.is_valid()) {
332            Err(io::Error::new(
333                io::ErrorKind::InvalidData,
334                "The provided source range is inconsistent!",
335            ))
336        } else {
337            Ok(())
338        }
339    }
340
341    pub fn id_from_repr<'a>(bytes: impl Iterator<Item = &'a u8>) -> Id {
342        let bytes: Vec<u8> = bytes.copied().collect();
343
344        *Hasher::hash(bytes.as_slice())
345    }
346
347    pub const fn id(&self) -> &Id {
348        match self {
349            Self::CallFrame(t) => t.id(),
350            Self::TransactionScript(t) => t.id(),
351        }
352    }
353
354    pub const fn source(&self) -> &Source {
355        match self {
356            Self::CallFrame(t) => t.source(),
357            Self::TransactionScript(t) => t.source(),
358        }
359    }
360
361    pub const fn range(&self) -> &Range {
362        match self {
363            Self::CallFrame(t) => t.range(),
364            Self::TransactionScript(t) => t.range(),
365        }
366    }
367
368    pub fn program(&self) -> &[Instruction] {
369        match self {
370            Self::CallFrame(t) => t.program(),
371            Self::TransactionScript(t) => t.program(),
372        }
373    }
374
375    pub fn contract(&self) -> Option<ContractId> {
376        match self {
377            Self::CallFrame(t) => Some(t.contract()),
378            _ => None,
379        }
380    }
381}
382
383pub type FxBuildHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
384pub type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxBuildHasher>;
385pub type FxIndexSet<K> = indexmap::IndexSet<K, FxBuildHasher>;