From f5ae9ad0bc8498447a0f429b41b805fdfb2e6e02 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Thu, 4 Sep 2025 14:41:04 -0300 Subject: [PATCH 01/10] feat(ir): add ComputeTipSlot operation --- crates/tx3-cardano/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index 322b1848..f5dd47be 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -34,6 +34,24 @@ pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits { const DEFAULT_EXTRA_FEES: u64 = 200_000; const MIN_UTXO_BYTES: i128 = 197; +struct SlotConfig { + zero_time: i64, + zero_slot: i64, + slot_length: i64, +} + +const MAINNET_SLOT_CONFIG: SlotConfig = SlotConfig { + zero_time: 1596059091000, + zero_slot: 4492800, + slot_length: 1000, +}; + +const TESTNET_SLOT_CONFIG: SlotConfig = SlotConfig { + zero_time: 1666656000000, + zero_slot: 0, + slot_length: 1000, +}; + #[derive(Debug, Clone, Default)] pub struct Config { pub extra_fees: Option, From 98bd1625398d0ac0af1509ea2d891028e5caebf2 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Fri, 5 Sep 2025 15:59:03 -0300 Subject: [PATCH 02/10] feat(lang): add ComputeTipSlot operation and related parsing support --- crates/tx3-cardano/src/lib.rs | 19 +------------------ crates/tx3-cardano/src/tests.rs | 7 ++++++- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index f5dd47be..58f79bff 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -34,27 +34,10 @@ pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits { const DEFAULT_EXTRA_FEES: u64 = 200_000; const MIN_UTXO_BYTES: i128 = 197; -struct SlotConfig { - zero_time: i64, - zero_slot: i64, - slot_length: i64, -} - -const MAINNET_SLOT_CONFIG: SlotConfig = SlotConfig { - zero_time: 1596059091000, - zero_slot: 4492800, - slot_length: 1000, -}; - -const TESTNET_SLOT_CONFIG: SlotConfig = SlotConfig { - zero_time: 1666656000000, - zero_slot: 0, - slot_length: 1000, -}; - #[derive(Debug, Clone, Default)] pub struct Config { pub extra_fees: Option, + pub tip_slot: Option, } pub type TxBody = diff --git a/crates/tx3-cardano/src/tests.rs b/crates/tx3-cardano/src/tests.rs index 8aaf2f44..bd415603 100644 --- a/crates/tx3-cardano/src/tests.rs +++ b/crates/tx3-cardano/src/tests.rs @@ -46,7 +46,10 @@ fn test_compiler(config: Option) -> Compiler { ]), }; - let config = config.unwrap_or(Config { extra_fees: None }); + let config = config.unwrap_or(Config { + extra_fees: None, + tip_slot: None, + }); Compiler::new( pparams, @@ -438,6 +441,7 @@ async fn extra_fees_test() { let mut compiler = test_compiler(Some(Config { extra_fees: Some(extra_fees), + tip_slot: None, })); let utxos = wildcard_utxos(None); @@ -461,6 +465,7 @@ async fn extra_fees_test() { async fn extra_fees_zero_test() { let mut compiler = test_compiler(Some(Config { extra_fees: Some(0), + tip_slot: None, })); let utxos = wildcard_utxos(None); From 91cbd3d0e22e7e70e7ec8acf42702848717bfe99 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 9 Sep 2025 16:07:08 -0300 Subject: [PATCH 03/10] feat(compiler): add ChainTip struct and update Compiler --- crates/tx3-cardano/src/lib.rs | 1 - crates/tx3-cardano/src/tests.rs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index 58f79bff..322b1848 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -37,7 +37,6 @@ const MIN_UTXO_BYTES: i128 = 197; #[derive(Debug, Clone, Default)] pub struct Config { pub extra_fees: Option, - pub tip_slot: Option, } pub type TxBody = diff --git a/crates/tx3-cardano/src/tests.rs b/crates/tx3-cardano/src/tests.rs index bd415603..8aaf2f44 100644 --- a/crates/tx3-cardano/src/tests.rs +++ b/crates/tx3-cardano/src/tests.rs @@ -46,10 +46,7 @@ fn test_compiler(config: Option) -> Compiler { ]), }; - let config = config.unwrap_or(Config { - extra_fees: None, - tip_slot: None, - }); + let config = config.unwrap_or(Config { extra_fees: None }); Compiler::new( pparams, @@ -441,7 +438,6 @@ async fn extra_fees_test() { let mut compiler = test_compiler(Some(Config { extra_fees: Some(extra_fees), - tip_slot: None, })); let utxos = wildcard_utxos(None); @@ -465,7 +461,6 @@ async fn extra_fees_test() { async fn extra_fees_zero_test() { let mut compiler = test_compiler(Some(Config { extra_fees: Some(0), - tip_slot: None, })); let utxos = wildcard_utxos(None); From 876e3b9f65277104eac0e6e2d885a623eb667fdd Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Wed, 10 Sep 2025 16:06:01 -0300 Subject: [PATCH 04/10] feat(ir): add ComputeSlotsToUnixTime and ComputeUnixTimeToSlots operations --- crates/tx3-lang/src/applying.rs | 8 ++++++++ crates/tx3-lang/src/ir.rs | 8 ++++++++ crates/tx3-lang/src/tx3.pest | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/crates/tx3-lang/src/applying.rs b/crates/tx3-lang/src/applying.rs index cd67e8b8..606f30e6 100644 --- a/crates/tx3-lang/src/applying.rs +++ b/crates/tx3-lang/src/applying.rs @@ -1151,6 +1151,8 @@ impl Composite for ir::CompilerOp { ir::CompilerOp::BuildScriptAddress(x) => vec![x], ir::CompilerOp::ComputeMinUtxo(x) => vec![x], ir::CompilerOp::ComputeTipSlot => vec![], + ir::CompilerOp::ComputeSlotsToUnixTime(x) => vec![x], + ir::CompilerOp::ComputeUnixTimeToSlots(x) => vec![x], } } @@ -1162,6 +1164,12 @@ impl Composite for ir::CompilerOp { ir::CompilerOp::BuildScriptAddress(x) => Ok(ir::CompilerOp::BuildScriptAddress(f(x)?)), ir::CompilerOp::ComputeMinUtxo(x) => Ok(ir::CompilerOp::ComputeMinUtxo(f(x)?)), ir::CompilerOp::ComputeTipSlot => Ok(ir::CompilerOp::ComputeTipSlot), + ir::CompilerOp::ComputeSlotsToUnixTime(x) => { + Ok(ir::CompilerOp::ComputeSlotsToUnixTime(f(x)?)) + } + ir::CompilerOp::ComputeUnixTimeToSlots(x) => { + Ok(ir::CompilerOp::ComputeUnixTimeToSlots(f(x)?)) + } } } } diff --git a/crates/tx3-lang/src/ir.rs b/crates/tx3-lang/src/ir.rs index 86103376..ccce06d1 100644 --- a/crates/tx3-lang/src/ir.rs +++ b/crates/tx3-lang/src/ir.rs @@ -71,6 +71,8 @@ pub enum CompilerOp { BuildScriptAddress(Expression), ComputeMinUtxo(Expression), ComputeTipSlot, + ComputeSlotsToUnixTime(Expression), + ComputeUnixTimeToSlots(Expression), } #[derive(Encode, Decode, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -467,6 +469,12 @@ impl Node for CompilerOp { CompilerOp::BuildScriptAddress(x) => CompilerOp::BuildScriptAddress(x.apply(visitor)?), CompilerOp::ComputeMinUtxo(x) => CompilerOp::ComputeMinUtxo(x.apply(visitor)?), CompilerOp::ComputeTipSlot => CompilerOp::ComputeTipSlot, + CompilerOp::ComputeSlotsToUnixTime(x) => { + CompilerOp::ComputeSlotsToUnixTime(x.apply(visitor)?) + } + CompilerOp::ComputeUnixTimeToSlots(x) => { + CompilerOp::ComputeUnixTimeToSlots(x.apply(visitor)?) + } }; Ok(visited) diff --git a/crates/tx3-lang/src/tx3.pest b/crates/tx3-lang/src/tx3.pest index b401d68a..306827f1 100644 --- a/crates/tx3-lang/src/tx3.pest +++ b/crates/tx3-lang/src/tx3.pest @@ -128,6 +128,10 @@ min_utxo = { "min_utxo" ~ "(" ~ identifier ~ ")" } tip_slot = { "tip_slot" ~ "(" ~ ")" } +slots_to_unix_time = { "slots_to_unix_time" ~ "(" ~ data_expr ~ ")" } + +unix_time_to_slots = { "unix_time_to_slots" ~ "(" ~ data_expr ~ ")" } + data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_prefix* ~ data_primary ~ data_postfix* )* } data_infix = _{ data_add | data_sub } @@ -150,6 +154,8 @@ data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_p string | min_utxo | tip_slot | + slots_to_unix_time | + unix_time_to_slots | struct_constructor | list_constructor | concat_constructor | From f8b821b8b5e638da33bb8f9245e64e9d15a9c0eb Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Wed, 10 Sep 2025 16:44:39 -0300 Subject: [PATCH 05/10] feat(lang): add SlotsToUnixTime and UnixTimeToSlots data expressions --- crates/tx3-lang/src/ast.rs | 4 ++++ crates/tx3-lang/src/lowering.rs | 6 ++++++ crates/tx3-lang/src/parsing.rs | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index 9843446d..239c4650 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -700,6 +700,8 @@ pub enum DataExpr { Identifier(Identifier), MinUtxo(Identifier), ComputeTipSlot, + SlotsToUnixTime(Box), + UnixTimeToSlots(Box), AddOp(AddOp), SubOp(SubOp), ConcatOp(ConcatOp), @@ -740,6 +742,8 @@ impl DataExpr { DataExpr::UtxoRef(_) => Some(Type::UtxoRef), DataExpr::MinUtxo(_) => Some(Type::AnyAsset), DataExpr::ComputeTipSlot => Some(Type::Int), + DataExpr::SlotsToUnixTime(_) => Some(Type::Int), + DataExpr::UnixTimeToSlots(_) => Some(Type::Int), } } } diff --git a/crates/tx3-lang/src/lowering.rs b/crates/tx3-lang/src/lowering.rs index 528a4372..d88e8502 100644 --- a/crates/tx3-lang/src/lowering.rs +++ b/crates/tx3-lang/src/lowering.rs @@ -452,6 +452,12 @@ impl IntoLower for ast::DataExpr { ast::DataExpr::ComputeTipSlot => { ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeTipSlot)) } + ast::DataExpr::SlotsToUnixTime(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeSlotsToUnixTime(x.into_lower(ctx)?), + )), + ast::DataExpr::UnixTimeToSlots(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeUnixTimeToSlots(x.into_lower(ctx)?), + )), }; Ok(out) diff --git a/crates/tx3-lang/src/parsing.rs b/crates/tx3-lang/src/parsing.rs index 867f52ac..828303b1 100644 --- a/crates/tx3-lang/src/parsing.rs +++ b/crates/tx3-lang/src/parsing.rs @@ -1098,6 +1098,16 @@ impl DataExpr { Ok(DataExpr::ComputeTipSlot) } + fn slots_to_unix_time_parse(pair: Pair) -> Result { + let inner = pair.into_inner().next().unwrap(); + Ok(DataExpr::SlotsToUnixTime(Box::new(DataExpr::parse(inner)?))) + } + + fn unix_time_to_slots_parse(pair: Pair) -> Result { + let inner = pair.into_inner().next().unwrap(); + Ok(DataExpr::UnixTimeToSlots(Box::new(DataExpr::parse(inner)?))) + } + fn concat_constructor_parse(pair: Pair) -> Result { Ok(DataExpr::ConcatOp(ConcatOp::parse(pair)?)) } @@ -1185,6 +1195,8 @@ impl AstNode for DataExpr { Rule::concat_constructor => DataExpr::concat_constructor_parse(x), Rule::min_utxo => DataExpr::min_utxo_parse(x), Rule::tip_slot => DataExpr::tip_slot_parse(x), + Rule::slots_to_unix_time => DataExpr::slots_to_unix_time_parse(x), + Rule::unix_time_to_slots => DataExpr::unix_time_to_slots_parse(x), Rule::data_expr => DataExpr::parse(x), x => unreachable!("unexpected rule as data primary: {:?}", x), }) @@ -1225,6 +1237,8 @@ impl AstNode for DataExpr { DataExpr::PropertyOp(x) => &x.span, DataExpr::UtxoRef(x) => x.span(), DataExpr::MinUtxo(x) => x.span(), + DataExpr::SlotsToUnixTime(x) => x.span(), + DataExpr::UnixTimeToSlots(x) => x.span(), DataExpr::ComputeTipSlot => &Span::DUMMY, // TODO } } From ab2e0bf4a3ae7a6c7023939dc4b3c58090c14064 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Fri, 12 Sep 2025 12:47:52 -0300 Subject: [PATCH 06/10] feat(compiler): add timestamp field to ChainPoint --- crates/tx3-cardano/src/lib.rs | 42 +++++++++++++++++++++++++++++++++ crates/tx3-cardano/src/tests.rs | 1 + 2 files changed, 43 insertions(+) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index 322b1848..b9080842 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -46,6 +46,7 @@ pub type TxBody = pub struct ChainPoint { pub slot: u64, pub hash: Vec, + pub timestamp: u128, } pub struct Compiler { @@ -109,6 +110,34 @@ impl tx3_lang::backend::Compiler for Compiler { }])) } ir::CompilerOp::ComputeTipSlot => Ok(ir::Expression::Number(self.cursor.slot as i128)), + ir::CompilerOp::ComputeSlotsToUnixTime(x) => { + let slot_i128 = coercion::expr_into_number(&x)?; + if slot_i128 < 0 { + return Err(tx3_lang::backend::Error::CoerceError( + format!("{}", slot_i128), + "positive slot number".to_string(), + )); + } + + let current_time = self.cursor.timestamp as i128; + let time_diff = slot_i128 - self.cursor.slot as i128; + let unix_time_millis = current_time + (time_diff * 1000); + + Ok(ir::Expression::Number(unix_time_millis)) + } + ir::CompilerOp::ComputeUnixTimeToSlots(x) => { + let unix_time_i128 = coercion::expr_into_number(&x)?; + if unix_time_i128 < 0 { + return Err(tx3_lang::backend::Error::CoerceError( + format!("{}", unix_time_i128), + "positive unix timestamp".to_string(), + )); + } + let unix_time = unix_time_i128 as u64; + let slot = + compute_unix_time_to_slot(unix_time, self.cursor.slot, self.cursor.timestamp)?; + Ok(ir::Expression::Number(slot as i128)) + } } } } @@ -118,3 +147,16 @@ fn eval_size_fees(tx: &[u8], pparams: &PParams, extra_fees: Option) -> u64 + pparams.min_fee_constant + extra_fees.unwrap_or(DEFAULT_EXTRA_FEES) } + +fn compute_unix_time_to_slot( + unix_time: u64, + current_tip_slot: u64, + tip_timestamp: u128, +) -> Result { + let current_time = tip_timestamp as i128; + + let time_diff = (unix_time as i128 - current_time) / 1000; + let slot = (current_tip_slot as i128 + time_diff) as u64; + + Ok(slot) +} diff --git a/crates/tx3-cardano/src/tests.rs b/crates/tx3-cardano/src/tests.rs index 8aaf2f44..17199e2e 100644 --- a/crates/tx3-cardano/src/tests.rs +++ b/crates/tx3-cardano/src/tests.rs @@ -54,6 +54,7 @@ fn test_compiler(config: Option) -> Compiler { ChainPoint { slot: 101674141, hash: vec![], + timestamp: 1757611408, }, ) } From a9c96f52949b09bc4542b42fdaff8b2b173c555d Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Mon, 29 Sep 2025 13:34:39 -0300 Subject: [PATCH 07/10] feat(ir): refactor ComputeSlotsToUnixTime and ComputeUnixTimeToSlots operations --- crates/tx3-cardano/src/lib.rs | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index b9080842..e50db329 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -111,32 +111,32 @@ impl tx3_lang::backend::Compiler for Compiler { } ir::CompilerOp::ComputeTipSlot => Ok(ir::Expression::Number(self.cursor.slot as i128)), ir::CompilerOp::ComputeSlotsToUnixTime(x) => { - let slot_i128 = coercion::expr_into_number(&x)?; - if slot_i128 < 0 { + let slot = coercion::expr_into_number(&x)?; + if slot < 0 { return Err(tx3_lang::backend::Error::CoerceError( - format!("{}", slot_i128), + format!("{}", slot), "positive slot number".to_string(), )); } - let current_time = self.cursor.timestamp as i128; - let time_diff = slot_i128 - self.cursor.slot as i128; - let unix_time_millis = current_time + (time_diff * 1000); - - Ok(ir::Expression::Number(unix_time_millis)) + Ok(ir::Expression::Number(slot_to_unix_time( + slot, + &self.cursor, + ))) } ir::CompilerOp::ComputeUnixTimeToSlots(x) => { - let unix_time_i128 = coercion::expr_into_number(&x)?; - if unix_time_i128 < 0 { + let unix_time = coercion::expr_into_number(&x)?; + if unix_time < 0 { return Err(tx3_lang::backend::Error::CoerceError( - format!("{}", unix_time_i128), + format!("{}", unix_time), "positive unix timestamp".to_string(), )); } - let unix_time = unix_time_i128 as u64; - let slot = - compute_unix_time_to_slot(unix_time, self.cursor.slot, self.cursor.timestamp)?; - Ok(ir::Expression::Number(slot as i128)) + + Ok(ir::Expression::Number(unix_time_to_slot( + unix_time, + &self.cursor, + ))) } } } @@ -148,15 +148,15 @@ fn eval_size_fees(tx: &[u8], pparams: &PParams, extra_fees: Option) -> u64 + extra_fees.unwrap_or(DEFAULT_EXTRA_FEES) } -fn compute_unix_time_to_slot( - unix_time: u64, - current_tip_slot: u64, - tip_timestamp: u128, -) -> Result { - let current_time = tip_timestamp as i128; - - let time_diff = (unix_time as i128 - current_time) / 1000; - let slot = (current_tip_slot as i128 + time_diff) as u64; +fn slot_to_unix_time(slot: i128, cursor: &ChainPoint) -> i128 { + let current_time = cursor.timestamp as i128; + let time_diff = slot - cursor.slot as i128; + current_time + (time_diff * 1000) +} - Ok(slot) +fn unix_time_to_slot(unix_time: i128, cursor: &ChainPoint) -> i128 { + let current_slot = cursor.slot as i128; + let current_time = cursor.timestamp as i128; + let time_diff = unix_time - current_time; + current_slot + (time_diff / 1000) } From ffaab70ce411a345383aaf7a899bb56627cbd411 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Mon, 29 Sep 2025 15:45:30 -0300 Subject: [PATCH 08/10] feat(lang): add SlotsToUnixTime and UnixTimeToSlots expressions to semantic analysis --- crates/tx3-lang/src/analyzing.rs | 38 ++++++++++++++++++++++++++++++++ examples/posix_time.tx3 | 22 ++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 examples/posix_time.tx3 diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 5eedade0..0a5ec1ae 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -566,6 +566,8 @@ impl Analyzable for DataExpr { DataExpr::StaticAssetConstructor(x) => x.analyze(parent), DataExpr::AnyAssetConstructor(x) => x.analyze(parent), DataExpr::MinUtxo(x) => x.analyze(parent), + DataExpr::SlotsToUnixTime(x) => x.analyze(parent), + DataExpr::UnixTimeToSlots(x) => x.analyze(parent), DataExpr::ConcatOp(x) => x.analyze(parent), _ => AnalyzeReport::default(), } @@ -583,6 +585,8 @@ impl Analyzable for DataExpr { DataExpr::StaticAssetConstructor(x) => x.is_resolved(), DataExpr::AnyAssetConstructor(x) => x.is_resolved(), DataExpr::MinUtxo(x) => x.is_resolved(), + DataExpr::SlotsToUnixTime(x) => x.is_resolved(), + DataExpr::UnixTimeToSlots(x) => x.is_resolved(), DataExpr::ConcatOp(x) => x.is_resolved(), _ => true, } @@ -1233,4 +1237,38 @@ mod tests { let result = analyze(&mut ast); assert!(!result.errors.is_empty()); } + + #[test] + fn test_unix_time_and_slot_conversion() { + let mut ast = crate::parsing::parse_string( + r#" + party Sender; + + type TimestampDatum { + slot_time: Int, + unix_time: Int, + } + + tx create_timestamp_tx() { + input source { + from: Sender, + min_amount: Ada(2000000), + } + + output timestamp_output { + to: Sender, + amount: source - fees, + datum: TimestampDatum { + slot_time: unix_time_to_slots(1666716638000), + unix_time: slots_to_unix_time(60638), + }, + } + } + "#, + ) + .unwrap(); + + let result = analyze(&mut ast); + assert!(result.errors.is_empty()); + } } diff --git a/examples/posix_time.tx3 b/examples/posix_time.tx3 new file mode 100644 index 00000000..a9d05ce1 --- /dev/null +++ b/examples/posix_time.tx3 @@ -0,0 +1,22 @@ +party Sender; + +type TimestampDatum { + current_slot: Int, + expiry_slot: Int, +} + +tx create_timestamp_tx(deadline: Int) { + input source { + from: Sender, + min_amount: Ada(2000000), + } + + output timestamp_output { + to: Sender, + amount: source - fees, + datum: TimestampDatum { + current_slot: slots_to_unix_time(tip_slot()), + expiry_slot: unix_time_to_slots(deadline), + }, + } +} From cdc1b3da94a4cf02df4f29aefb9c57d0ce6e219f Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 14 Oct 2025 15:21:05 -0300 Subject: [PATCH 09/10] feat(lang): rename SlotsToUnixTime and UnixTimeToSlots to SlotToTime and TimeToSlot --- crates/tx3-cardano/src/lib.rs | 28 +++++++++++----------------- crates/tx3-lang/src/analyzing.rs | 16 ++++++++-------- crates/tx3-lang/src/applying.rs | 12 ++++-------- crates/tx3-lang/src/ast.rs | 8 ++++---- crates/tx3-lang/src/ir.rs | 12 ++++-------- crates/tx3-lang/src/lowering.rs | 8 ++++---- crates/tx3-lang/src/parsing.rs | 16 ++++++++-------- crates/tx3-lang/src/tx3.pest | 8 ++++---- 8 files changed, 47 insertions(+), 61 deletions(-) diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index e50db329..7954002a 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -110,7 +110,7 @@ impl tx3_lang::backend::Compiler for Compiler { }])) } ir::CompilerOp::ComputeTipSlot => Ok(ir::Expression::Number(self.cursor.slot as i128)), - ir::CompilerOp::ComputeSlotsToUnixTime(x) => { + ir::CompilerOp::ComputeSlotToTime(x) => { let slot = coercion::expr_into_number(&x)?; if slot < 0 { return Err(tx3_lang::backend::Error::CoerceError( @@ -119,24 +119,18 @@ impl tx3_lang::backend::Compiler for Compiler { )); } - Ok(ir::Expression::Number(slot_to_unix_time( - slot, - &self.cursor, - ))) + Ok(ir::Expression::Number(slot_to_time(slot, &self.cursor))) } - ir::CompilerOp::ComputeUnixTimeToSlots(x) => { - let unix_time = coercion::expr_into_number(&x)?; - if unix_time < 0 { + ir::CompilerOp::ComputeTimeToSlot(x) => { + let time = coercion::expr_into_number(&x)?; + if time < 0 { return Err(tx3_lang::backend::Error::CoerceError( - format!("{}", unix_time), - "positive unix timestamp".to_string(), + format!("{}", time), + "positive timestamp".to_string(), )); } - Ok(ir::Expression::Number(unix_time_to_slot( - unix_time, - &self.cursor, - ))) + Ok(ir::Expression::Number(time_to_slot(time, &self.cursor))) } } } @@ -148,15 +142,15 @@ fn eval_size_fees(tx: &[u8], pparams: &PParams, extra_fees: Option) -> u64 + extra_fees.unwrap_or(DEFAULT_EXTRA_FEES) } -fn slot_to_unix_time(slot: i128, cursor: &ChainPoint) -> i128 { +fn slot_to_time(slot: i128, cursor: &ChainPoint) -> i128 { let current_time = cursor.timestamp as i128; let time_diff = slot - cursor.slot as i128; current_time + (time_diff * 1000) } -fn unix_time_to_slot(unix_time: i128, cursor: &ChainPoint) -> i128 { +fn time_to_slot(time: i128, cursor: &ChainPoint) -> i128 { let current_slot = cursor.slot as i128; let current_time = cursor.timestamp as i128; - let time_diff = unix_time - current_time; + let time_diff = time - current_time; current_slot + (time_diff / 1000) } diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 0a5ec1ae..5dd55d73 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -566,8 +566,8 @@ impl Analyzable for DataExpr { DataExpr::StaticAssetConstructor(x) => x.analyze(parent), DataExpr::AnyAssetConstructor(x) => x.analyze(parent), DataExpr::MinUtxo(x) => x.analyze(parent), - DataExpr::SlotsToUnixTime(x) => x.analyze(parent), - DataExpr::UnixTimeToSlots(x) => x.analyze(parent), + DataExpr::SlotToTime(x) => x.analyze(parent), + DataExpr::TimeToSlot(x) => x.analyze(parent), DataExpr::ConcatOp(x) => x.analyze(parent), _ => AnalyzeReport::default(), } @@ -585,8 +585,8 @@ impl Analyzable for DataExpr { DataExpr::StaticAssetConstructor(x) => x.is_resolved(), DataExpr::AnyAssetConstructor(x) => x.is_resolved(), DataExpr::MinUtxo(x) => x.is_resolved(), - DataExpr::SlotsToUnixTime(x) => x.is_resolved(), - DataExpr::UnixTimeToSlots(x) => x.is_resolved(), + DataExpr::SlotToTime(x) => x.is_resolved(), + DataExpr::TimeToSlot(x) => x.is_resolved(), DataExpr::ConcatOp(x) => x.is_resolved(), _ => true, } @@ -1239,14 +1239,14 @@ mod tests { } #[test] - fn test_unix_time_and_slot_conversion() { + fn test_time_and_slot_conversion() { let mut ast = crate::parsing::parse_string( r#" party Sender; type TimestampDatum { slot_time: Int, - unix_time: Int, + time: Int, } tx create_timestamp_tx() { @@ -1259,8 +1259,8 @@ mod tests { to: Sender, amount: source - fees, datum: TimestampDatum { - slot_time: unix_time_to_slots(1666716638000), - unix_time: slots_to_unix_time(60638), + slot_time: time_to_slot(1666716638000), + time: slot_to_time(60638), }, } } diff --git a/crates/tx3-lang/src/applying.rs b/crates/tx3-lang/src/applying.rs index 606f30e6..bd75c7f3 100644 --- a/crates/tx3-lang/src/applying.rs +++ b/crates/tx3-lang/src/applying.rs @@ -1151,8 +1151,8 @@ impl Composite for ir::CompilerOp { ir::CompilerOp::BuildScriptAddress(x) => vec![x], ir::CompilerOp::ComputeMinUtxo(x) => vec![x], ir::CompilerOp::ComputeTipSlot => vec![], - ir::CompilerOp::ComputeSlotsToUnixTime(x) => vec![x], - ir::CompilerOp::ComputeUnixTimeToSlots(x) => vec![x], + ir::CompilerOp::ComputeSlotToTime(x) => vec![x], + ir::CompilerOp::ComputeTimeToSlot(x) => vec![x], } } @@ -1164,12 +1164,8 @@ impl Composite for ir::CompilerOp { ir::CompilerOp::BuildScriptAddress(x) => Ok(ir::CompilerOp::BuildScriptAddress(f(x)?)), ir::CompilerOp::ComputeMinUtxo(x) => Ok(ir::CompilerOp::ComputeMinUtxo(f(x)?)), ir::CompilerOp::ComputeTipSlot => Ok(ir::CompilerOp::ComputeTipSlot), - ir::CompilerOp::ComputeSlotsToUnixTime(x) => { - Ok(ir::CompilerOp::ComputeSlotsToUnixTime(f(x)?)) - } - ir::CompilerOp::ComputeUnixTimeToSlots(x) => { - Ok(ir::CompilerOp::ComputeUnixTimeToSlots(f(x)?)) - } + ir::CompilerOp::ComputeSlotToTime(x) => Ok(ir::CompilerOp::ComputeSlotToTime(f(x)?)), + ir::CompilerOp::ComputeTimeToSlot(x) => Ok(ir::CompilerOp::ComputeTimeToSlot(f(x)?)), } } } diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index 239c4650..0c256d9c 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -700,8 +700,8 @@ pub enum DataExpr { Identifier(Identifier), MinUtxo(Identifier), ComputeTipSlot, - SlotsToUnixTime(Box), - UnixTimeToSlots(Box), + SlotToTime(Box), + TimeToSlot(Box), AddOp(AddOp), SubOp(SubOp), ConcatOp(ConcatOp), @@ -742,8 +742,8 @@ impl DataExpr { DataExpr::UtxoRef(_) => Some(Type::UtxoRef), DataExpr::MinUtxo(_) => Some(Type::AnyAsset), DataExpr::ComputeTipSlot => Some(Type::Int), - DataExpr::SlotsToUnixTime(_) => Some(Type::Int), - DataExpr::UnixTimeToSlots(_) => Some(Type::Int), + DataExpr::SlotToTime(_) => Some(Type::Int), + DataExpr::TimeToSlot(_) => Some(Type::Int), } } } diff --git a/crates/tx3-lang/src/ir.rs b/crates/tx3-lang/src/ir.rs index ccce06d1..7a293a7d 100644 --- a/crates/tx3-lang/src/ir.rs +++ b/crates/tx3-lang/src/ir.rs @@ -71,8 +71,8 @@ pub enum CompilerOp { BuildScriptAddress(Expression), ComputeMinUtxo(Expression), ComputeTipSlot, - ComputeSlotsToUnixTime(Expression), - ComputeUnixTimeToSlots(Expression), + ComputeSlotToTime(Expression), + ComputeTimeToSlot(Expression), } #[derive(Encode, Decode, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -469,12 +469,8 @@ impl Node for CompilerOp { CompilerOp::BuildScriptAddress(x) => CompilerOp::BuildScriptAddress(x.apply(visitor)?), CompilerOp::ComputeMinUtxo(x) => CompilerOp::ComputeMinUtxo(x.apply(visitor)?), CompilerOp::ComputeTipSlot => CompilerOp::ComputeTipSlot, - CompilerOp::ComputeSlotsToUnixTime(x) => { - CompilerOp::ComputeSlotsToUnixTime(x.apply(visitor)?) - } - CompilerOp::ComputeUnixTimeToSlots(x) => { - CompilerOp::ComputeUnixTimeToSlots(x.apply(visitor)?) - } + CompilerOp::ComputeSlotToTime(x) => CompilerOp::ComputeSlotToTime(x.apply(visitor)?), + CompilerOp::ComputeTimeToSlot(x) => CompilerOp::ComputeTimeToSlot(x.apply(visitor)?), }; Ok(visited) diff --git a/crates/tx3-lang/src/lowering.rs b/crates/tx3-lang/src/lowering.rs index d88e8502..e33bbc66 100644 --- a/crates/tx3-lang/src/lowering.rs +++ b/crates/tx3-lang/src/lowering.rs @@ -452,11 +452,11 @@ impl IntoLower for ast::DataExpr { ast::DataExpr::ComputeTipSlot => { ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeTipSlot)) } - ast::DataExpr::SlotsToUnixTime(x) => ir::Expression::EvalCompiler(Box::new( - ir::CompilerOp::ComputeSlotsToUnixTime(x.into_lower(ctx)?), + ast::DataExpr::SlotToTime(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeSlotToTime(x.into_lower(ctx)?), )), - ast::DataExpr::UnixTimeToSlots(x) => ir::Expression::EvalCompiler(Box::new( - ir::CompilerOp::ComputeUnixTimeToSlots(x.into_lower(ctx)?), + ast::DataExpr::TimeToSlot(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeTimeToSlot(x.into_lower(ctx)?), )), }; diff --git a/crates/tx3-lang/src/parsing.rs b/crates/tx3-lang/src/parsing.rs index 828303b1..2a92319f 100644 --- a/crates/tx3-lang/src/parsing.rs +++ b/crates/tx3-lang/src/parsing.rs @@ -1098,14 +1098,14 @@ impl DataExpr { Ok(DataExpr::ComputeTipSlot) } - fn slots_to_unix_time_parse(pair: Pair) -> Result { + fn slot_to_time_parse(pair: Pair) -> Result { let inner = pair.into_inner().next().unwrap(); - Ok(DataExpr::SlotsToUnixTime(Box::new(DataExpr::parse(inner)?))) + Ok(DataExpr::SlotToTime(Box::new(DataExpr::parse(inner)?))) } - fn unix_time_to_slots_parse(pair: Pair) -> Result { + fn time_to_slot_parse(pair: Pair) -> Result { let inner = pair.into_inner().next().unwrap(); - Ok(DataExpr::UnixTimeToSlots(Box::new(DataExpr::parse(inner)?))) + Ok(DataExpr::TimeToSlot(Box::new(DataExpr::parse(inner)?))) } fn concat_constructor_parse(pair: Pair) -> Result { @@ -1195,8 +1195,8 @@ impl AstNode for DataExpr { Rule::concat_constructor => DataExpr::concat_constructor_parse(x), Rule::min_utxo => DataExpr::min_utxo_parse(x), Rule::tip_slot => DataExpr::tip_slot_parse(x), - Rule::slots_to_unix_time => DataExpr::slots_to_unix_time_parse(x), - Rule::unix_time_to_slots => DataExpr::unix_time_to_slots_parse(x), + Rule::slot_to_time => DataExpr::slot_to_time_parse(x), + Rule::time_to_slot => DataExpr::time_to_slot_parse(x), Rule::data_expr => DataExpr::parse(x), x => unreachable!("unexpected rule as data primary: {:?}", x), }) @@ -1237,8 +1237,8 @@ impl AstNode for DataExpr { DataExpr::PropertyOp(x) => &x.span, DataExpr::UtxoRef(x) => x.span(), DataExpr::MinUtxo(x) => x.span(), - DataExpr::SlotsToUnixTime(x) => x.span(), - DataExpr::UnixTimeToSlots(x) => x.span(), + DataExpr::SlotToTime(x) => x.span(), + DataExpr::TimeToSlot(x) => x.span(), DataExpr::ComputeTipSlot => &Span::DUMMY, // TODO } } diff --git a/crates/tx3-lang/src/tx3.pest b/crates/tx3-lang/src/tx3.pest index 306827f1..c81e61d6 100644 --- a/crates/tx3-lang/src/tx3.pest +++ b/crates/tx3-lang/src/tx3.pest @@ -128,9 +128,9 @@ min_utxo = { "min_utxo" ~ "(" ~ identifier ~ ")" } tip_slot = { "tip_slot" ~ "(" ~ ")" } -slots_to_unix_time = { "slots_to_unix_time" ~ "(" ~ data_expr ~ ")" } +slot_to_time = { "slot_to_time" ~ "(" ~ data_expr ~ ")" } -unix_time_to_slots = { "unix_time_to_slots" ~ "(" ~ data_expr ~ ")" } +time_to_slot = { "time_to_slot" ~ "(" ~ data_expr ~ ")" } data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_prefix* ~ data_primary ~ data_postfix* )* } @@ -154,8 +154,8 @@ data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_p string | min_utxo | tip_slot | - slots_to_unix_time | - unix_time_to_slots | + slot_to_time | + time_to_slot | struct_constructor | list_constructor | concat_constructor | From a87bf0c2c3d1053deddd490533d4f5afb9d491a2 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 14 Oct 2025 15:21:57 -0300 Subject: [PATCH 10/10] fix: update tests --- examples/asteria.ast | 24 +++++---- examples/asteria.move_ship.tir | 20 ++++++-- examples/cardano_witness.mint_from_plutus.tir | 6 +-- examples/input_datum.ast | 24 +++++---- examples/input_datum.increase_counter.tir | 8 ++- examples/lang_tour.ast | 50 +++++++++++-------- examples/lang_tour.my_tx.tir | 20 ++++++-- examples/list_concat.ast | 12 +++-- examples/list_concat.concat_list.tir | 4 +- examples/posix_time.tx3 | 4 +- examples/swap.ast | 48 ++++++++++-------- examples/swap.swap.tir | 16 ++++-- 12 files changed, 148 insertions(+), 88 deletions(-) diff --git a/examples/asteria.ast b/examples/asteria.ast index af1c2419..c695a4ed 100644 --- a/examples/asteria.ast +++ b/examples/asteria.ast @@ -450,11 +450,13 @@ } }, "property": { - "value": "pos_x", - "span": { - "dummy": false, - "start": 1281, - "end": 1286 + "Identifier": { + "value": "pos_x", + "span": { + "dummy": false, + "start": 1281, + "end": 1286 + } } }, "span": { @@ -511,11 +513,13 @@ } }, "property": { - "value": "pos_y", - "span": { - "dummy": false, - "start": 1324, - "end": 1329 + "Identifier": { + "value": "pos_y", + "span": { + "dummy": false, + "start": 1324, + "end": 1329 + } } }, "span": { diff --git a/examples/asteria.move_ship.tir b/examples/asteria.move_ship.tir index 74562cde..7bdfb4d6 100644 --- a/examples/asteria.move_ship.tir +++ b/examples/asteria.move_ship.tir @@ -403,7 +403,9 @@ } } }, - 0 + { + "Number": 0 + } ] } }, @@ -552,7 +554,9 @@ } } }, - 1 + { + "Number": 1 + } ] } }, @@ -698,7 +702,9 @@ } } }, - 2 + { + "Number": 2 + } ] } }, @@ -833,7 +839,9 @@ } } }, - 3 + { + "Number": 3 + } ] } }, @@ -968,7 +976,9 @@ } } }, - 4 + { + "Number": 4 + } ] } } diff --git a/examples/cardano_witness.mint_from_plutus.tir b/examples/cardano_witness.mint_from_plutus.tir index 85165ace..7f6c09cc 100644 --- a/examples/cardano_witness.mint_from_plutus.tir +++ b/examples/cardano_witness.mint_from_plutus.tir @@ -201,6 +201,9 @@ { "name": "plutus_witness", "data": { + "version": { + "Number": 3 + }, "script": { "Bytes": [ 81, @@ -222,9 +225,6 @@ 174, 105 ] - }, - "version": { - "Number": 3 } } } diff --git a/examples/input_datum.ast b/examples/input_datum.ast index bee582a0..485c5476 100644 --- a/examples/input_datum.ast +++ b/examples/input_datum.ast @@ -161,11 +161,13 @@ } }, "property": { - "value": "counter", - "span": { - "dummy": false, - "start": 323, - "end": 330 + "Identifier": { + "value": "counter", + "span": { + "dummy": false, + "start": 323, + "end": 330 + } } }, "span": { @@ -213,11 +215,13 @@ } }, "property": { - "value": "other_field", - "span": { - "dummy": false, - "start": 368, - "end": 379 + "Identifier": { + "value": "other_field", + "span": { + "dummy": false, + "start": 368, + "end": 379 + } } }, "span": { diff --git a/examples/input_datum.increase_counter.tir b/examples/input_datum.increase_counter.tir index e622cd16..16ec73a3 100644 --- a/examples/input_datum.increase_counter.tir +++ b/examples/input_datum.increase_counter.tir @@ -79,7 +79,9 @@ } } }, - 0 + { + "Number": 0 + } ] } }, @@ -119,7 +121,9 @@ } } }, - 1 + { + "Number": 1 + } ] } } diff --git a/examples/lang_tour.ast b/examples/lang_tour.ast index a4bccf83..9ad96014 100644 --- a/examples/lang_tour.ast +++ b/examples/lang_tour.ast @@ -434,11 +434,13 @@ } }, "property": { - "value": "field1", - "span": { - "dummy": false, - "start": 1166, - "end": 1172 + "Identifier": { + "value": "field1", + "span": { + "dummy": false, + "start": 1166, + "end": 1172 + } } }, "span": { @@ -507,11 +509,13 @@ } }, "property": { - "value": "field3", - "span": { - "dummy": false, - "start": 1240, - "end": 1246 + "Identifier": { + "value": "field3", + "span": { + "dummy": false, + "start": 1240, + "end": 1246 + } } }, "span": { @@ -534,11 +538,13 @@ } }, "property": { - "value": "field2", - "span": { - "dummy": false, - "start": 1255, - "end": 1261 + "Identifier": { + "value": "field2", + "span": { + "dummy": false, + "start": 1255, + "end": 1261 + } } }, "span": { @@ -561,11 +567,13 @@ } }, "property": { - "value": "field1", - "span": { - "dummy": false, - "start": 1270, - "end": 1276 + "Identifier": { + "value": "field1", + "span": { + "dummy": false, + "start": 1270, + "end": 1276 + } } }, "span": { @@ -1159,4 +1167,4 @@ "start": 0, "end": 1560 } -} +} \ No newline at end of file diff --git a/examples/lang_tour.my_tx.tir b/examples/lang_tour.my_tx.tir index baefa921..c1ed8b7e 100644 --- a/examples/lang_tour.my_tx.tir +++ b/examples/lang_tour.my_tx.tir @@ -195,7 +195,9 @@ } } }, - 2 + { + "Number": 2 + } ] } }, @@ -266,7 +268,9 @@ } } }, - 0 + { + "Number": 0 + } ] } } @@ -340,7 +344,9 @@ } } }, - 2 + { + "Number": 2 + } ] } }, @@ -400,7 +406,9 @@ } } }, - 1 + { + "Number": 1 + } ] } }, @@ -460,7 +468,9 @@ } } }, - 0 + { + "Number": 0 + } ] } } diff --git a/examples/list_concat.ast b/examples/list_concat.ast index 96935361..97e1cf85 100644 --- a/examples/list_concat.ast +++ b/examples/list_concat.ast @@ -149,11 +149,13 @@ } }, "property": { - "value": "list", - "span": { - "dummy": false, - "start": 275, - "end": 279 + "Identifier": { + "value": "list", + "span": { + "dummy": false, + "start": 275, + "end": 279 + } } }, "span": { diff --git a/examples/list_concat.concat_list.tir b/examples/list_concat.concat_list.tir index ec341389..8c4e3548 100644 --- a/examples/list_concat.concat_list.tir +++ b/examples/list_concat.concat_list.tir @@ -75,7 +75,9 @@ } } }, - 0 + { + "Number": 0 + } ] } }, diff --git a/examples/posix_time.tx3 b/examples/posix_time.tx3 index a9d05ce1..12fba1b5 100644 --- a/examples/posix_time.tx3 +++ b/examples/posix_time.tx3 @@ -15,8 +15,8 @@ tx create_timestamp_tx(deadline: Int) { to: Sender, amount: source - fees, datum: TimestampDatum { - current_slot: slots_to_unix_time(tip_slot()), - expiry_slot: unix_time_to_slots(deadline), + current_slot: slot_to_time(tip_slot()), + expiry_slot: time_to_slot(deadline), }, } } diff --git a/examples/swap.ast b/examples/swap.ast index a6627afc..188ea2ed 100644 --- a/examples/swap.ast +++ b/examples/swap.ast @@ -282,11 +282,13 @@ } }, "property": { - "value": "pair_a", - "span": { - "dummy": false, - "start": 557, - "end": 563 + "Identifier": { + "value": "pair_a", + "span": { + "dummy": false, + "start": 557, + "end": 563 + } } }, "span": { @@ -309,11 +311,13 @@ } }, "property": { - "value": "amount", - "span": { - "dummy": false, - "start": 570, - "end": 576 + "Identifier": { + "value": "amount", + "span": { + "dummy": false, + "start": 570, + "end": 576 + } } }, "span": { @@ -360,11 +364,13 @@ } }, "property": { - "value": "pair_b", - "span": { - "dummy": false, - "start": 603, - "end": 609 + "Identifier": { + "value": "pair_b", + "span": { + "dummy": false, + "start": 603, + "end": 609 + } } }, "span": { @@ -387,11 +393,13 @@ } }, "property": { - "value": "amount", - "span": { - "dummy": false, - "start": 616, - "end": 622 + "Identifier": { + "value": "amount", + "span": { + "dummy": false, + "start": 616, + "end": 622 + } } }, "span": { diff --git a/examples/swap.swap.tir b/examples/swap.swap.tir index ec7e271f..e89e2c6a 100644 --- a/examples/swap.swap.tir +++ b/examples/swap.swap.tir @@ -138,7 +138,9 @@ } } }, - 0 + { + "Number": 0 + } ] } }, @@ -153,7 +155,9 @@ ] } }, - 0 + { + "Number": 0 + } ] } } @@ -191,7 +195,9 @@ } } }, - 1 + { + "Number": 1 + } ] } }, @@ -206,7 +212,9 @@ ] } }, - 0 + { + "Number": 0 + } ] } }