pub trait BasicSnippet {
// Required methods
fn parameters(&self) -> Vec<(DataType, String)>;
fn return_values(&self) -> Vec<(DataType, String)>;
fn entrypoint(&self) -> String;
fn code(&self, library: &mut Library) -> Vec<LabelledInstruction>;
// Provided methods
fn annotated_code(&self, library: &mut Library) -> Vec<LabelledInstruction> { ... }
fn stack_diff(&self) -> isize { ... }
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> { ... }
}Expand description
A (basic) snippet represents a piece of code that can be run in Triton VM.
Generally speaking, it’s not possible to run a snippet stand-alone. Rather, snippets are (generally) intended to be used in combination with other snippets. Together, they can make up a full Triton VM program.
§Example
/// Checks whether some u32 is odd.
struct IsOdd;
impl BasicSnippet for IsOdd {
fn parameters(&self) -> Vec<(DataType, String)> {
vec![(DataType::U32, "x".to_string())]
}
fn return_values(&self) -> Vec<(DataType, String)> {
vec![(DataType::Bool, "x % 2".to_string())]
}
fn entrypoint(&self) -> String {
"is_odd".to_string()
}
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
triton_asm!(
// BEFORE: _ x
// AFTER: _ (x%2)
{self.entrypoint()}:
push 2 // _ x 2
pick 1 // _ 2 x
div_mod // _ (x//2) (x%2)
pick 1 // _ (x%2) (x//2)
pop 1 // _ (x%2)
return
)
}
}§A Caveat Regarding Type Safety
The methods parameters and
return_values define some expected and returned
DataTypes, respectively. Note that there is no mechanism that enforces
any guarantees about these types. Notably:
- tasm-lib never checks any kind of type consistency or correctness, neither at run- nor compile-time
- Triton VM does not know anything about types
While the types defined in these respective methods streamline a snippet’s testability and simplify the required reasoning when composing them, they are only used as a development aid.
§Dyn-Compatibility
This trait is dyn-compatible (previously known as “object safe”).
Required Methods§
Sourcefn parameters(&self) -> Vec<(DataType, String)>
fn parameters(&self) -> Vec<(DataType, String)>
The parameters expected by this snippet.
The parameters are expected to be on top of Triton VM’s stack when the snippet is called. If this is not the case, behavior of the snippet is generally undefined.
See also the caveat regarding type safety.
Sourcefn return_values(&self) -> Vec<(DataType, String)>
fn return_values(&self) -> Vec<(DataType, String)>
The (types of the) values this snippet computes.
The return values are at the top of the stack when the snippet returns.
See also the caveat regarding type safety.
Sourcefn entrypoint(&self) -> String
fn entrypoint(&self) -> String
The name of the snippet as a possible target for Triton VM’s
instruction call.
Sourcefn code(&self, library: &mut Library) -> Vec<LabelledInstruction>
fn code(&self, library: &mut Library) -> Vec<LabelledInstruction>
The Triton Assembly that defines this snippet.
Snippet authors are responsible for the following:
- Only use the pre-conditions
- listed explicitly in the snippet’s documentation, and
- implied by
parameters.
- Uphold all post-conditions
- listed explicitly in the snippet’s documentation, and
- implied by
return_values.
Notably, no type checking of any kind is performed. See also the caveat regarding type safety.
Provided Methods§
Sourcefn annotated_code(&self, library: &mut Library) -> Vec<LabelledInstruction>
fn annotated_code(&self, library: &mut Library) -> Vec<LabelledInstruction>
Adds “type hints” to the code.
This method should not be overwritten by implementors of the trait.
Note that type hints do not change the behavior of Triton VM in any way. They are only useful in debuggers, like the Triton TUI.
Sourcefn stack_diff(&self) -> isize
fn stack_diff(&self) -> isize
The size difference of the stack as a result of executing this snippet.
Sourcefn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint>
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint>
Contains an entry for every sign off.
Many of the snippets defined in this TASM library are critical for the consensus logic of the blockchain Neptune Cash. Therefore, it is paramount that the snippets are free of errors. In order to catch as many errors as possible, the snippets are reviewed by as many developers as possible. The requirements of such a review are listed here.
A reviewer can (and should) sign off on any snippet they have reviewed and for which they found no defects. This is done by adding that snippet’s fingerprint (at the time) to the overriding implementation of this method on that snippet.
Together with the tools git blame and cryptographic
signing of commits, this makes sign-offs traceable. It also guarantees
that changes to snippets that have already been signed-off are easy to
detect.
§For Reviewers
§Modifying snippets
While the primary intention of the review process is to review a snippet, there are circumstances under which modifying it is acceptable.
Modifying a snippet to simplify reviewing that snippet is fair game. A
common example of this case is replacing a swap-juggle chain with a few
picks & places.
Modifying a snippet in order to improve performance should only happen if the performance impact is meaningful. The currently agreed-upon threshold is 0.5% of at least one consensus program.
It is acceptable, and can be desired, to modify a snippet by including assumption checks. For example, if the snippet’s pre-conditions require some input to fall within a certain range, it is fine to add a corresponding range check to the snippet. Removing existing checks of such nature is considered bad practice.
In either case, modifying a snippet that has already been reviewed and signed off by someone else in a way that alters its fingerprint requires their consent.
§Checklist
Use the following checklist to guide your review. Signing off on a snippet means that in your eyes, all points on this checklist are true.
- the snippet’s documentation lists pre- and post-conditions
- the snippet makes no assumptions outside the stated pre-conditions
- given all pre-conditions, all post-conditions are met
- whenever this snippet calls another snippet, all of that other snippet’s pre-conditions are met
- all dynamic memory offsets are range-checked before they are used
- each field accessor is used at most once per struct instance, or range-checked before each use
- reading from non-deterministically initialized memory only happens from the region specified in the memory convention
- memory-writes only happen outside of page 0 (see memory convention)
§Documentation Template
If a snippet you are reviewing is not (properly) documented yet, you can use
the following template to document the type implementing BasicSnippet.
/// ### Behavior
///
/// ```text
/// BEFORE: _
/// AFTER: _
/// ```
///
/// ### Preconditions
///
/// - condition
///
/// ### Postconditions
///
/// - condition§Non-Unit Structs
Most, but not all types implementing BasicSnippet are unit structs.
Fingerprinting gets more difficult for non-unit structs.
In such cases, a default instantiation should be selected and signed off.
§Overriding this Method
This default implementation is intended to be overridden for any snippet that has been signed off, but should not call the fingerprint method.