diff --git a/amm/core/src/lib.rs b/amm/core/src/lib.rs index eeac604..f8b6871 100644 --- a/amm/core/src/lib.rs +++ b/amm/core/src/lib.rs @@ -35,6 +35,8 @@ pub enum Instruction { token_b_amount: u128, fees: u128, amm_program_id: ProgramId, + /// Unix timestamp (milliseconds) after which this transaction is invalid. + deadline: u64, }, /// Adds liquidity to the Pool @@ -51,6 +53,8 @@ pub enum Instruction { min_amount_liquidity: u128, max_amount_to_add_token_a: u128, max_amount_to_add_token_b: u128, + /// Unix timestamp (milliseconds) after which this transaction is invalid. + deadline: u64, }, /// Removes liquidity from the Pool @@ -67,6 +71,8 @@ pub enum Instruction { remove_liquidity_amount: u128, min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, + /// Unix timestamp (milliseconds) after which this transaction is invalid. + deadline: u64, }, /// Swap some quantity of Tokens (either Token A or Token B) @@ -77,12 +83,13 @@ pub enum Instruction { /// - Vault Holding Account for Token A (initialized) /// - Vault Holding Account for Token B (initialized) /// - User Holding Account for Token A - /// - User Holding Account for Token B Either User Holding Account for Token A or Token B is - /// authorized. + /// - User Holding Account for Token B; either is authorized. SwapExactInput { swap_amount_in: u128, min_amount_out: u128, token_definition_id_in: AccountId, + /// Unix timestamp (milliseconds) after which this transaction is invalid. + deadline: u64, }, /// Swap tokens specifying the exact desired output amount, @@ -93,12 +100,13 @@ pub enum Instruction { /// - Vault Holding Account for Token A (initialized) /// - Vault Holding Account for Token B (initialized) /// - User Holding Account for Token A - /// - User Holding Account for Token B Either User Holding Account for Token A or Token B is - /// authorized. + /// - User Holding Account for Token B; either is authorized. SwapExactOutput { exact_amount_out: u128, max_amount_in: u128, token_definition_id_in: AccountId, + /// Unix timestamp (milliseconds) after which this transaction is invalid. + deadline: u64, }, /// Sync pool reserves with current vault balances. diff --git a/amm/methods/guest/Cargo.lock b/amm/methods/guest/Cargo.lock index cc8d9a8..9f3ac81 100644 --- a/amm/methods/guest/Cargo.lock +++ b/amm/methods/guest/Cargo.lock @@ -2898,10 +2898,11 @@ dependencies = [ [[package]] name = "spel-framework" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", + "serde_json", "spel-framework-core", "spel-framework-macros", ] @@ -2909,24 +2910,27 @@ dependencies = [ [[package]] name = "spel-framework-core" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", "serde", "serde_json", "sha2", + "syn 2.0.117", "thiserror 1.0.69", ] [[package]] name = "spel-framework-macros" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "proc-macro2", "quote", + "serde_json", "sha2", + "spel-framework-core", "syn 2.0.117", ] diff --git a/amm/methods/guest/Cargo.toml b/amm/methods/guest/Cargo.toml index 385c4ac..fd7351d 100644 --- a/amm/methods/guest/Cargo.toml +++ b/amm/methods/guest/Cargo.toml @@ -10,7 +10,7 @@ name = "amm" path = "src/bin/amm.rs" [dependencies] -spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" } +spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3", package = "spel-framework" } nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" } risc0-zkvm = { version = "=3.0.5", default-features = false } amm_core = { path = "../../core" } diff --git a/amm/methods/guest/src/bin/amm.rs b/amm/methods/guest/src/bin/amm.rs index 17db95d..9e71954 100644 --- a/amm/methods/guest/src/bin/amm.rs +++ b/amm/methods/guest/src/bin/amm.rs @@ -31,6 +31,7 @@ mod amm { token_b_amount: u128, fees: u128, amm_program_id: ProgramId, + deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::new_definition::new_definition( pool, @@ -46,7 +47,8 @@ mod amm { fees, amm_program_id, ); - Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) + Ok(SpelOutput::with_chained_calls(post_states, chained_calls) + .with_timestamp_validity_window(..deadline)) } /// Adds liquidity to the Pool. @@ -62,6 +64,7 @@ mod amm { min_amount_liquidity: u128, max_amount_to_add_token_a: u128, max_amount_to_add_token_b: u128, + deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::add::add_liquidity( pool, @@ -75,7 +78,8 @@ mod amm { max_amount_to_add_token_a, max_amount_to_add_token_b, ); - Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) + Ok(SpelOutput::with_chained_calls(post_states, chained_calls) + .with_timestamp_validity_window(..deadline)) } /// Removes liquidity from the Pool. @@ -91,6 +95,7 @@ mod amm { remove_liquidity_amount: u128, min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, + deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::remove::remove_liquidity( pool, @@ -105,7 +110,8 @@ mod amm { min_amount_to_remove_token_a, min_amount_to_remove_token_b, ); - Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) + Ok(SpelOutput::with_chained_calls(post_states, chained_calls) + .with_timestamp_validity_window(..deadline)) } /// Swap some quantity of tokens while maintaining the pool constant product. @@ -119,6 +125,7 @@ mod amm { swap_amount_in: u128, min_amount_out: u128, token_definition_id_in: AccountId, + deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::swap::swap_exact_input( pool, @@ -130,7 +137,8 @@ mod amm { min_amount_out, token_definition_id_in, ); - Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) + Ok(SpelOutput::with_chained_calls(post_states, chained_calls) + .with_timestamp_validity_window(..deadline)) } /// Swap tokens specifying the exact desired output amount. @@ -144,6 +152,7 @@ mod amm { exact_amount_out: u128, max_amount_in: u128, token_definition_id_in: AccountId, + deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::swap::swap_exact_output( pool, @@ -155,7 +164,8 @@ mod amm { max_amount_in, token_definition_id_in, ); - Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) + Ok(SpelOutput::with_chained_calls(post_states, chained_calls) + .with_timestamp_validity_window(..deadline)) } /// Sync pool reserves with current vault balances. diff --git a/artifacts/amm-idl.json b/artifacts/amm-idl.json index 3003f2f..96dfe17 100644 --- a/artifacts/amm-idl.json +++ b/artifacts/amm-idl.json @@ -70,6 +70,10 @@ { "name": "amm_program_id", "type": "program_id" + }, + { + "name": "deadline", + "type": "u64" } ] }, @@ -131,6 +135,10 @@ { "name": "max_amount_to_add_token_b", "type": "u128" + }, + { + "name": "deadline", + "type": "u64" } ] }, @@ -192,6 +200,10 @@ { "name": "min_amount_to_remove_token_b", "type": "u128" + }, + { + "name": "deadline", + "type": "u64" } ] }, @@ -241,6 +253,10 @@ { "name": "token_definition_id_in", "type": "account_id" + }, + { + "name": "deadline", + "type": "u64" } ] }, @@ -290,6 +306,10 @@ { "name": "token_definition_id_in", "type": "account_id" + }, + { + "name": "deadline", + "type": "u64" } ] }, diff --git a/ata/methods/guest/Cargo.lock b/ata/methods/guest/Cargo.lock index 8c55a8a..1f89b1b 100644 --- a/ata/methods/guest/Cargo.lock +++ b/ata/methods/guest/Cargo.lock @@ -2897,10 +2897,11 @@ dependencies = [ [[package]] name = "spel-framework" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", + "serde_json", "spel-framework-core", "spel-framework-macros", ] @@ -2908,24 +2909,27 @@ dependencies = [ [[package]] name = "spel-framework-core" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", "serde", "serde_json", "sha2", + "syn 2.0.117", "thiserror 1.0.69", ] [[package]] name = "spel-framework-macros" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "proc-macro2", "quote", + "serde_json", "sha2", + "spel-framework-core", "syn 2.0.117", ] diff --git a/ata/methods/guest/Cargo.toml b/ata/methods/guest/Cargo.toml index 22e8f42..f203c7c 100644 --- a/ata/methods/guest/Cargo.toml +++ b/ata/methods/guest/Cargo.toml @@ -10,7 +10,7 @@ name = "ata" path = "src/bin/ata.rs" [dependencies] -spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" } +spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3", package = "spel-framework" } nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" } risc0-zkvm = { version = "=3.0.5", default-features = false } ata_core = { path = "../../core" } diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs index d121029..ff3791a 100644 --- a/integration_tests/tests/amm.rs +++ b/integration_tests/tests/amm.rs @@ -966,6 +966,7 @@ fn try_execute_new_definition( token_b_amount: Balances::vault_b_init(), fees, amm_program_id: Ids::amm_program(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1018,6 +1019,7 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou swap_amount_in, min_amount_out, token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1045,6 +1047,7 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou swap_amount_in, min_amount_out, token_definition_id_in: Ids::token_b_definition(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1077,6 +1080,7 @@ fn execute_add_liquidity( min_amount_liquidity, max_amount_to_add_token_a, max_amount_to_add_token_b, + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1115,6 +1119,7 @@ fn execute_remove_liquidity( remove_liquidity_amount, min_amount_to_remove_token_a, min_amount_to_remove_token_b, + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1178,6 +1183,7 @@ fn amm_remove_liquidity() { remove_liquidity_amount: Balances::remove_lp(), min_amount_to_remove_token_a: Balances::remove_min_a(), min_amount_to_remove_token_b: Balances::remove_min_b(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1240,6 +1246,7 @@ fn amm_remove_liquidity_insufficient_user_lp_fails() { remove_liquidity_amount: Balances::remove_lp(), min_amount_to_remove_token_a: Balances::remove_min_a(), min_amount_to_remove_token_b: Balances::remove_min_b(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1464,6 +1471,7 @@ fn amm_add_liquidity() { min_amount_liquidity: Balances::add_min_lp(), max_amount_to_add_token_a: Balances::add_max_a(), max_amount_to_add_token_b: Balances::add_max_b(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1526,6 +1534,7 @@ fn amm_swap_b_to_a() { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), token_definition_id_in: Ids::token_b_definition(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1577,6 +1586,7 @@ fn amm_swap_a_to_b() { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, }; let message = public_transaction::Message::try_new( @@ -1671,6 +1681,205 @@ fn amm_fee_accumulates_across_multiple_swaps_and_pays_out_on_remove() { ); } +#[test] +fn amm_swap_rejects_expired_deadline() { + let mut state = state_for_amm_tests(); + + let deadline_ms = 1_000u64; + let block_timestamp_ms = 2_000u64; + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + token_definition_id_in: Ids::token_a_definition(), + deadline: deadline_ms, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + ], + vec![Nonce(0)], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), + Err(NssaError::OutOfValidityWindow) + )); +} + +#[test] +fn amm_swap_exact_output_rejects_expired_deadline() { + let mut state = state_for_amm_tests(); + + let deadline_ms = 1_000u64; + let block_timestamp_ms = 2_000u64; + + let instruction = amm_core::Instruction::SwapExactOutput { + exact_amount_out: Balances::swap_min_out(), + max_amount_in: Balances::swap_amount_in(), + token_definition_id_in: Ids::token_a_definition(), + deadline: deadline_ms, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + ], + vec![current_nonce(&state, Ids::user_a())], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), + Err(NssaError::OutOfValidityWindow) + )); +} + +#[test] +fn amm_add_liquidity_rejects_expired_deadline() { + let mut state = state_for_amm_tests(); + + let deadline_ms = 1_000u64; + let block_timestamp_ms = 2_000u64; + + let instruction = amm_core::Instruction::AddLiquidity { + min_amount_liquidity: Balances::add_min_lp(), + max_amount_to_add_token_a: Balances::add_max_a(), + max_amount_to_add_token_b: Balances::add_max_b(), + deadline: deadline_ms, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::user_a(), + Ids::user_b(), + Ids::user_lp(), + ], + vec![ + current_nonce(&state, Ids::user_a()), + current_nonce(&state, Ids::user_b()), + ], + instruction, + ) + .unwrap(); + + let witness_set = + public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), + Err(NssaError::OutOfValidityWindow) + )); +} + +#[test] +fn amm_remove_liquidity_rejects_expired_deadline() { + let mut state = state_for_amm_tests(); + + let deadline_ms = 1_000u64; + let block_timestamp_ms = 2_000u64; + + let instruction = amm_core::Instruction::RemoveLiquidity { + remove_liquidity_amount: Balances::remove_lp(), + min_amount_to_remove_token_a: Balances::remove_min_a(), + min_amount_to_remove_token_b: Balances::remove_min_b(), + deadline: deadline_ms, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::user_a(), + Ids::user_b(), + Ids::user_lp(), + ], + vec![current_nonce(&state, Ids::user_lp())], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), + Err(NssaError::OutOfValidityWindow) + )); +} + +#[test] +fn amm_new_definition_rejects_expired_deadline() { + let mut state = state_for_amm_tests_with_precreated_user_lp_for_new_def(); + + let deadline_ms = 1_000u64; + let block_timestamp_ms = 2_000u64; + + let instruction = amm_core::Instruction::NewDefinition { + token_a_amount: Balances::vault_a_init(), + token_b_amount: Balances::vault_b_init(), + fees: amm_core::FEE_TIER_BPS_30, + amm_program_id: Ids::amm_program(), + deadline: deadline_ms, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::lp_lock_holding(), + Ids::user_a(), + Ids::user_b(), + Ids::user_lp(), + ], + vec![ + current_nonce(&state, Ids::user_a()), + current_nonce(&state, Ids::user_b()), + current_nonce(&state, Ids::user_lp()), + ], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&Keys::user_a(), &Keys::user_b(), &Keys::user_lp()], + ); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), + Err(NssaError::OutOfValidityWindow) + )); +} + #[test] fn amm_add_liquidity_after_fee_accrual() { let mut state = state_for_amm_tests(); diff --git a/token/methods/guest/Cargo.lock b/token/methods/guest/Cargo.lock index 198a356..d08f635 100644 --- a/token/methods/guest/Cargo.lock +++ b/token/methods/guest/Cargo.lock @@ -2864,10 +2864,11 @@ dependencies = [ [[package]] name = "spel-framework" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", + "serde_json", "spel-framework-core", "spel-framework-macros", ] @@ -2875,24 +2876,27 @@ dependencies = [ [[package]] name = "spel-framework-core" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "borsh", "nssa_core", "serde", "serde_json", "sha2", + "syn 2.0.117", "thiserror 1.0.69", ] [[package]] name = "spel-framework-macros" version = "0.2.0" -source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d" +source = "git+https://github.com/logos-co/spel.git?rev=9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3#9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3" dependencies = [ "proc-macro2", "quote", + "serde_json", "sha2", + "spel-framework-core", "syn 2.0.117", ] diff --git a/token/methods/guest/Cargo.toml b/token/methods/guest/Cargo.toml index fe9a7bf..c1c412e 100644 --- a/token/methods/guest/Cargo.toml +++ b/token/methods/guest/Cargo.toml @@ -10,7 +10,7 @@ name = "token" path = "src/bin/token.rs" [dependencies] -spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" } +spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "9e7f2754e1d4cdb3ea36e63b1ff86c3af55488d3", package = "spel-framework" } nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" } risc0-zkvm = { version = "=3.0.5", default-features = false } token_core = { path = "../../core" }