Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,9 @@ Access properties of transaction inputs:
int i = 0;
int inputValue = tx.inputs[i].value;
byte[] inputScript = tx.inputs[i].scriptPubKey;
byte[32] outpointTxId = tx.inputs[i].outpointTxId;
int outpointIndex = tx.inputs[i].outpointIndex;
byte[8] sequence = tx.inputs[i].sequence;
```

**Example:**
Expand Down
20 changes: 8 additions & 12 deletions silverscript-lang/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,12 +830,9 @@ pub enum IndexedIntrospectionKind {
InputValue,
InputScriptPubKey,
InputSigScript,
/// TODO: not supported yet
InputOutpointTransactionHash,
/// TODO: not supported yet
InputOutpointTxId,
InputOutpointIndex,
/// TODO: not supported yet
InputSequenceNumber,
InputSequence,
OutputValue,
OutputScriptPubKey,
}
Expand Down Expand Up @@ -1313,9 +1310,9 @@ fn indexed_introspection_root(kind: IndexedIntrospectionKind) -> &'static str {
IndexedIntrospectionKind::InputValue
| IndexedIntrospectionKind::InputScriptPubKey
| IndexedIntrospectionKind::InputSigScript
| IndexedIntrospectionKind::InputOutpointTransactionHash
| IndexedIntrospectionKind::InputOutpointTxId
| IndexedIntrospectionKind::InputOutpointIndex
| IndexedIntrospectionKind::InputSequenceNumber => "tx.inputs",
| IndexedIntrospectionKind::InputSequence => "tx.inputs",
IndexedIntrospectionKind::OutputValue | IndexedIntrospectionKind::OutputScriptPubKey => "tx.outputs",
}
}
Expand All @@ -1325,9 +1322,9 @@ fn indexed_introspection_field(kind: IndexedIntrospectionKind) -> &'static str {
IndexedIntrospectionKind::InputValue | IndexedIntrospectionKind::OutputValue => ".value",
IndexedIntrospectionKind::InputScriptPubKey | IndexedIntrospectionKind::OutputScriptPubKey => ".scriptPubKey",
IndexedIntrospectionKind::InputSigScript => ".sigScript",
IndexedIntrospectionKind::InputOutpointTransactionHash => ".outpointTransactionHash",
IndexedIntrospectionKind::InputOutpointTxId => ".outpointTxId",
IndexedIntrospectionKind::InputOutpointIndex => ".outpointIndex",
IndexedIntrospectionKind::InputSequenceNumber => ".sequenceNumber",
IndexedIntrospectionKind::InputSequence => ".sequence",
}
}

Expand Down Expand Up @@ -2642,10 +2639,9 @@ fn parse_indexed_introspection<'i>(pair: Pair<'i, Rule>) -> Result<Expr<'i>, Com
".value" => IndexedIntrospectionKind::InputValue,
".scriptPubKey" => IndexedIntrospectionKind::InputScriptPubKey,
".sigScript" => IndexedIntrospectionKind::InputSigScript,
// TODO: support this
".outpointTransactionHash" => IndexedIntrospectionKind::InputOutpointTransactionHash,
".outpointTxId" => IndexedIntrospectionKind::InputOutpointTxId,
".outpointIndex" => IndexedIntrospectionKind::InputOutpointIndex,
".sequenceNumber" => IndexedIntrospectionKind::InputSequenceNumber,
".sequence" => IndexedIntrospectionKind::InputSequence,
_ => return Err(CompilerError::Unsupported(format!("input field '{field_raw}' not supported"))),
}
} else if text.starts_with("tx.outputs") {
Expand Down
4 changes: 2 additions & 2 deletions silverscript-lang/src/compiler/builtin_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ pub(super) fn indexed_introspection_type(kind: IndexedIntrospectionKind) -> Type
match kind {
IndexedIntrospectionKind::InputScriptPubKey
| IndexedIntrospectionKind::InputSigScript
| IndexedIntrospectionKind::InputOutpointTransactionHash
| IndexedIntrospectionKind::OutputScriptPubKey => byte_array(ArrayDim::Dynamic),
IndexedIntrospectionKind::InputOutpointTxId => byte_array(ArrayDim::Fixed(32)),
IndexedIntrospectionKind::InputSequence => byte_array(ArrayDim::Fixed(8)),
IndexedIntrospectionKind::InputValue
| IndexedIntrospectionKind::InputOutpointIndex
| IndexedIntrospectionKind::InputSequenceNumber
| IndexedIntrospectionKind::OutputValue => scalar(TypeBase::Int),
}
}
Expand Down
4 changes: 2 additions & 2 deletions silverscript-lang/src/compiler/compile/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,13 +585,13 @@ fn compile_indexed_introspection_expr<'i>(
ctx.emit_op(OpSwap, 0)?;
ctx.emit_op(OpTxInputScriptSigSubstr, -2)?;
}
IndexedIntrospectionKind::InputOutpointTransactionHash => {
IndexedIntrospectionKind::InputOutpointTxId => {
ctx.emit_op(OpOutpointTxId, 0)?;
}
IndexedIntrospectionKind::InputOutpointIndex => {
ctx.emit_op(OpOutpointIndex, 0)?;
}
IndexedIntrospectionKind::InputSequenceNumber => {
IndexedIntrospectionKind::InputSequence => {
ctx.emit_op(OpTxInputSeq, 0)?;
}
IndexedIntrospectionKind::OutputValue => {
Expand Down
2 changes: 1 addition & 1 deletion silverscript-lang/src/silverscript.pest
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ indexed_introspection = {
}

output_field = { "." ~ ("value" | "scriptPubKey") }
input_field = { "." ~ ("value" | "scriptPubKey" | "outpointTransactionHash" | "outpointIndex" | "sigScript") }
input_field = { "." ~ ("value" | "scriptPubKey" | "outpointTxId" | "outpointIndex" | "sequence" | "sigScript") }

typed_array = { type_name ~ "{" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ "}" }

Expand Down
59 changes: 59 additions & 0 deletions silverscript-lang/tests/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1440,12 +1440,71 @@ fn fixed_size_builtin_results_assign_to_exact_byte_array_types() {
compile_contract(source, &[], CompileOptions::default()).expect("fixed-size builtin results should assign to their exact types");
}

#[test]
fn indexed_introspection_fields_emit_and_execute_all_supported_opcodes() {
let source = r#"
contract Introspection() {
entry main() {
require(tx.inputs[0].value == 5000);
require(tx.inputs[0].scriptPubKey.length >= 0);
require(tx.inputs[0].sigScript.length == 0);
require(tx.inputs[0].outpointTxId == byte[32]("0123456789abcdef0123456789abcdef"));
require(tx.inputs[0].outpointIndex == 7);
require(tx.inputs[0].sequence == byte[8]("sequence"));
require(tx.outputs[0].value == 1000);
require(tx.outputs[0].scriptPubKey.length >= 0);
}
}
"#;

let compiled = compile_contract(source, &[], CompileOptions::default()).expect("all indexed introspection fields should compile");
let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify");
for opcode in [
"OpTxInputAmount",
"OpTxInputSpk",
"OpTxInputScriptSigLen",
"OpTxInputScriptSigSubstr",
"OpOutpointTxId",
"OpOutpointIndex",
"OpTxInputSeq",
"OpTxOutputAmount",
"OpTxOutputSpk",
] {
assert!(opcodes.contains(opcode), "missing {opcode} in {opcodes}");
}

let (tx, mut entries) = build_basic_opcode_tx(vec![]);
entries[0].script_public_key = ScriptPublicKey::new(0, compiled.bytecode.clone().into());
let reused_values = SigHashReusedValuesUnsync::new();
let sig_cache = Cache::new(10_000);
let populated = PopulatedTransaction::new(&tx, entries);
let covenants = CovenantsContext::from_tx(&populated).expect("covenants context should build");
let context = EngineCtx::new(&sig_cache).with_reused(&reused_values).with_covenants_ctx(&covenants);
let mut trace = Vec::new();
let result = {
let mut vm = TxScriptEngine::from_transaction_input(
&populated,
&tx.inputs[0],
0,
populated.utxo(0).expect("utxo entry for input 0"),
context,
EngineFlags { covenants_enabled: true, ..Default::default() },
)
.with_opcode_execution_log_buffer(&mut trace);
vm.execute()
};
let trace = String::from_utf8_lossy(&trace);
assert!(result.is_ok(), "indexed introspection execution failed:\n{trace}");
}

#[test]
fn rejects_assigning_fixed_size_builtin_results_to_wrong_byte_array_sizes() {
let cases = [
("OpTxSubnetId", "byte[19] value = OpTxSubnetId();"),
("OpOutpointTxId", "byte[31] value = OpOutpointTxId(0);"),
("OpTxInputSeq", "byte[7] value = OpTxInputSeq(0);"),
("outpointTxId introspection", "byte[31] value = tx.inputs[0].outpointTxId;"),
("sequence introspection", "byte[7] value = tx.inputs[0].sequence;"),
(
"OpChainblockSeqCommit",
"byte[31] value = OpChainblockSeqCommit(byte[32](0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f));",
Expand Down
4 changes: 3 additions & 1 deletion silverscript-lang/tests/parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ fn parses_arrays_and_introspection() {
int b = (a * 2).split(1).length;
int c = tx.outputs[idx].value;
int d = tx.inputs[idx].outpointIndex;
byte[32] txId = tx.inputs[idx].outpointTxId;
byte[8] sequence = tx.inputs[idx].sequence;
int inputCount = tx.inputs.length;
require(c >= d);
}
Expand All @@ -166,7 +168,7 @@ fn parses_arrays_and_introspection() {
};
assert!(matches!(&indexed.kind, ExprKind::IndexedIntrospection { .. }));

let Statement::VariableDefinition { expr: Some(unindexed), .. } = &contract.functions[0].body[4] else {
let Statement::VariableDefinition { expr: Some(unindexed), .. } = &contract.functions[0].body[6] else {
panic!("expected introspection variable");
};
assert!(matches!(&unindexed.kind, ExprKind::Introspection(_)));
Expand Down
2 changes: 2 additions & 0 deletions tree-sitter/bindings/node/binding_test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tree-sitter/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,9 @@ export default grammar({
choice(
"value",
"scriptPubKey",
"outpointTransactionHash",
"outpointTxId",
"outpointIndex",
"sequence",
"sigScript",
),

Expand Down
6 changes: 5 additions & 1 deletion tree-sitter/src/grammar.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion tree-sitter/src/node-types.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading