diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index 322b1848..7954002a 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,28 @@ impl tx3_lang::backend::Compiler for Compiler { }])) } ir::CompilerOp::ComputeTipSlot => Ok(ir::Expression::Number(self.cursor.slot as i128)), + ir::CompilerOp::ComputeSlotToTime(x) => { + let slot = coercion::expr_into_number(&x)?; + if slot < 0 { + return Err(tx3_lang::backend::Error::CoerceError( + format!("{}", slot), + "positive slot number".to_string(), + )); + } + + Ok(ir::Expression::Number(slot_to_time(slot, &self.cursor))) + } + ir::CompilerOp::ComputeTimeToSlot(x) => { + let time = coercion::expr_into_number(&x)?; + if time < 0 { + return Err(tx3_lang::backend::Error::CoerceError( + format!("{}", time), + "positive timestamp".to_string(), + )); + } + + Ok(ir::Expression::Number(time_to_slot(time, &self.cursor))) + } } } } @@ -118,3 +141,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 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 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 = time - current_time; + current_slot + (time_diff / 1000) +} 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, }, ) } diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 5eedade0..5dd55d73 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::SlotToTime(x) => x.analyze(parent), + DataExpr::TimeToSlot(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::SlotToTime(x) => x.is_resolved(), + DataExpr::TimeToSlot(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_time_and_slot_conversion() { + let mut ast = crate::parsing::parse_string( + r#" + party Sender; + + type TimestampDatum { + slot_time: Int, + 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: time_to_slot(1666716638000), + time: slot_to_time(60638), + }, + } + } + "#, + ) + .unwrap(); + + let result = analyze(&mut ast); + assert!(result.errors.is_empty()); + } } diff --git a/crates/tx3-lang/src/applying.rs b/crates/tx3-lang/src/applying.rs index cd67e8b8..bd75c7f3 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::ComputeSlotToTime(x) => vec![x], + ir::CompilerOp::ComputeTimeToSlot(x) => vec![x], } } @@ -1162,6 +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::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 9843446d..0c256d9c 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, + SlotToTime(Box), + TimeToSlot(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::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 86103376..7a293a7d 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, + ComputeSlotToTime(Expression), + ComputeTimeToSlot(Expression), } #[derive(Encode, Decode, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -467,6 +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::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 528a4372..e33bbc66 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::SlotToTime(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeSlotToTime(x.into_lower(ctx)?), + )), + ast::DataExpr::TimeToSlot(x) => ir::Expression::EvalCompiler(Box::new( + ir::CompilerOp::ComputeTimeToSlot(x.into_lower(ctx)?), + )), }; Ok(out) diff --git a/crates/tx3-lang/src/parsing.rs b/crates/tx3-lang/src/parsing.rs index 867f52ac..2a92319f 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 slot_to_time_parse(pair: Pair) -> Result { + let inner = pair.into_inner().next().unwrap(); + Ok(DataExpr::SlotToTime(Box::new(DataExpr::parse(inner)?))) + } + + fn time_to_slot_parse(pair: Pair) -> Result { + let inner = pair.into_inner().next().unwrap(); + Ok(DataExpr::TimeToSlot(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::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), }) @@ -1225,6 +1237,8 @@ impl AstNode for DataExpr { DataExpr::PropertyOp(x) => &x.span, DataExpr::UtxoRef(x) => x.span(), DataExpr::MinUtxo(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 b401d68a..c81e61d6 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" ~ "(" ~ ")" } +slot_to_time = { "slot_to_time" ~ "(" ~ 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* )* } 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 | + slot_to_time | + time_to_slot | struct_constructor | list_constructor | concat_constructor | 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 new file mode 100644 index 00000000..12fba1b5 --- /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: 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 + } ] } }