diff --git a/lib/calculation/deposit.ak b/lib/calculation/deposit.ak index c429191..78c55f0 100644 --- a/lib/calculation/deposit.ak +++ b/lib/calculation/deposit.ak @@ -1,11 +1,11 @@ use aiken/math -use aiken/transaction.{NoDatum, Output} +use aiken/transaction.{NoDatum, Output, InlineDatum} use aiken/transaction/credential.{Address, VerificationKeyCredential} -use aiken/transaction/value.{Value, ada_policy_id, ada_asset_name} +use aiken/transaction/value.{ada_policy_id, ada_asset_name} use calculation/shared.{PoolState} as calc_shared use sundae/multisig use shared.{SingletonValue} -use types/order.{Destination, OrderDatum} +use types/order.{Destination, Fixed, Self, OrderDatum} /// Calculate the result of depositing some amount of tokens into the pool /// @@ -14,13 +14,14 @@ use types/order.{Destination, OrderDatum} /// pub fn do_deposit( pool_state: PoolState, - input_value: Value, + input_utxo: Output, assets: (SingletonValue, SingletonValue), destination: Destination, actual_protocol_fee: Int, output: Output, ) -> PoolState { let (asset_a, asset_b) = assets + let Output { value: input_value, .. } = input_utxo // Policy ID and token name of the assets must match the pool, otherwise someone // could load the pool with junk tokens and freeze the pool. @@ -107,10 +108,24 @@ pub fn do_deposit( ) // Make sure we're paying the result to the correct destination (both the address and the datum), - // with the correct amount - expect output.address == destination.address - expect output.datum == destination.datum + // with the correct amount; In the special case where Datum is "Self" (for example for a repeating strategy) + // use the input datum for validation expect output.value == out_value + expect when destination is { + Fixed { address, datum } -> { + and { + output.address == address, + output.datum == datum + } + } + Self -> { + let Output { address: input_address, datum: input_datum, .. } = input_utxo + and { + output.address == input_address, + output.datum == input_datum + } + } + } // And construct the final pool state PoolState { @@ -158,7 +173,7 @@ test deposit_test() { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, @@ -174,7 +189,13 @@ test deposit_test() { datum: NoDatum, reference_script: None, } - let final_pool_state = do_deposit(pool_state, input_value, assets, order.destination, 2_500_000, output) + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } + let final_pool_state = do_deposit(pool_state, input, assets, order.destination, 2_500_000, output) expect final_pool_state.quantity_a.3rd == 1_010_000_000 expect final_pool_state.quantity_b.3rd == 1_010_000_000 True diff --git a/lib/calculation/donation.ak b/lib/calculation/donation.ak index 1072f81..fc862fa 100644 --- a/lib/calculation/donation.ak +++ b/lib/calculation/donation.ak @@ -1,8 +1,8 @@ use aiken/transaction.{Output} -use aiken/transaction/value.{Value, ada_policy_id, ada_asset_name} +use aiken/transaction/value.{ada_policy_id, ada_asset_name} use calculation/shared.{PoolState} as calc_shared use shared.{SingletonValue} -use types/order.{Destination} +use types/order.{Destination, Fixed, Self} /// A donation describes an amount of assets to deposit into the pool, receiving nothing in return (except for the extra change on the UTXO). /// Because every LP token holder has an entitlement to a percentage of the assets in the pool, the donation is distributed to all LP token holders @@ -15,7 +15,7 @@ pub fn do_donation( /// The pool state as a result of processing all orders before this one pool_state: PoolState, /// The total quantity of tokens on this particular input - input_value: Value, + input_utxo: Output, /// The amounts of each asset being donated assets: (SingletonValue, SingletonValue), /// The destination (address + datum) that any *change* should be sent @@ -27,6 +27,7 @@ pub fn do_donation( /// If there is no change leftover, this output is ignored and used for the next order output: Output, ) -> (PoolState, Bool) { + let Output { value: input_value, .. } = input_utxo let ((asset_a_policy_id, asset_a_asset_name, asset_a_qty), (asset_b_policy_id, asset_b_asset_name, asset_b_qty)) = assets // Make sure we're actually donating the pool assets; this is to prevent setting // poolIdent to None, and then filling the pool UTXO with garbage tokens and eventually locking it @@ -46,11 +47,27 @@ pub fn do_donation( // is so that we can do a few expects without having to return a value right away expect or { !has_remainder, + // Make sure we're paying the result to the correct destination (both the address and the datum), + // with the correct amount; In the special case where Datum is "Self" (for example for a repeating strategy) + // use the input datum for validation and { - output.address == destination.address, - output.datum == destination.datum, output.value == remainder, - }, + when destination is { + Fixed { address, datum } -> { + and { + output.address == address, + output.datum == datum + } + } + Self -> { + let Output { address: input_address, datum: input_datum, .. } = input_utxo + and { + output.address == input_address, + output.datum == input_datum + } + } + }, + } } // Compute the new pool state, which is exactly the old pool state, plus the diff --git a/lib/calculation/process.ak b/lib/calculation/process.ak index 3b412d0..4ce0772 100644 --- a/lib/calculation/process.ak +++ b/lib/calculation/process.ak @@ -18,7 +18,7 @@ use calculation/withdrawal use calculation/strategy use shared.{Ident, datum_of, pool_lp_name, is_script} use sundae/multisig -use types/order.{Order, Destination, OrderDatum, SignedStrategyExecution} +use types/order.{Order, Destination, Fixed, OrderDatum, SignedStrategyExecution} use types/pool.{PoolDatum} /// Construct the initial pool state for processing a set of orders @@ -119,6 +119,8 @@ pub fn process_order( tx_valid_range: ValidityRange, // The transaction withdrawals, so we can check strategy executions withdrawals: Dict, + // The input being processed + input: Output, // The value attached to the input, so we can ensure that any surplus tokens are paid out to the destination along with the results of the order; this lets transactions chain value: Value, // The details of the order to execute, such as whether it's a swap, the limit, etc. @@ -155,6 +157,7 @@ pub fn process_order( output_reference, tx_valid_range, withdrawals, + input, value, details, max_protocol_fee, @@ -177,7 +180,7 @@ pub fn process_order( let next = swap.do_swap( initial, - value, + input, destination, fees_per_10_thousand, fee, @@ -194,7 +197,7 @@ pub fn process_order( let fee = amortized_base_fee + simple_fee expect max_protocol_fee >= fee // Calculate and validate the result of a deposit - let next = deposit.do_deposit(initial, value, assets, destination, fee, output) + let next = deposit.do_deposit(initial, input, assets, destination, fee, output) (next, rest_outputs) } order.Withdrawal(amount) -> { @@ -205,7 +208,7 @@ pub fn process_order( expect max_protocol_fee >= fee // Calculate and validate the result of a withdrawal let next = - withdrawal.do_withdrawal(initial, value, amount, destination, fee, output) + withdrawal.do_withdrawal(initial, input, amount, destination, fee, output) (next, rest_outputs) } // NOTE: we decided not to implement zap, for time constraints, and because a zap can be easily implemented as a chained order, as it is in V1 @@ -219,7 +222,7 @@ pub fn process_order( expect max_protocol_fee >= fee // Calculate and validate the result of a donation let (next, used_output) = - donation.do_donation(initial, value, assets, destination, fee, output) + donation.do_donation(initial, input, assets, destination, fee, output) // If a donation has no change (ex it has exactly the donation + the fee) then we don't have an output dedicated to the order // so we can skip over it // TODO: can we just return used_output here instead of passing around the lists of outputs? @@ -239,7 +242,7 @@ pub fn process_orders( this_pool_ident: Ident, // The transaction valid range, if we end up processing a strategy tx_valid_range: ValidityRange, - // THe withdrawals attached to the transaction, for validating strategies + // The withdrawals attached to the transaction, for validating strategies withdrawals: Dict, // The datums in the witness set, in case we need to lookup a non-inline datum datums: Dict, Data>, @@ -326,6 +329,7 @@ pub fn process_orders( output_reference, tx_valid_range, withdrawals, + order, order.value, details, max_protocol_fee, @@ -383,7 +387,7 @@ test process_orders_test() { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, @@ -464,7 +468,7 @@ test process_30_shuffled_orders_test() { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, diff --git a/lib/calculation/swap.ak b/lib/calculation/swap.ak index ed6fdde..e3f34e6 100644 --- a/lib/calculation/swap.ak +++ b/lib/calculation/swap.ak @@ -1,4 +1,4 @@ -use aiken/transaction.{NoDatum, Output} +use aiken/transaction.{NoDatum, InlineDatum, Output} use aiken/transaction/credential.{Address, VerificationKeyCredential} use aiken/transaction/value.{ AssetName, PolicyId, Value, ada_asset_name, ada_policy_id, @@ -6,7 +6,7 @@ use aiken/transaction/value.{ use calculation/shared.{PoolState} as calc_shared use shared.{SingletonValue} use sundae/multisig -use types/order.{Destination, OrderDatum} +use types/order.{Destination, Fixed, Self, OrderDatum} /// Compute the amount of token a swap yields; returns the amount of token received, and the remainder to be sent to the user /// @@ -59,8 +59,8 @@ pub fn swap_takes( /// Calculate the new pool state after performing a swap, and validate that the output is correct according to the order pub fn do_swap( pool_state: PoolState, - /// The value coming in from the UTXO with the order - input_value: Value, + /// The full UTXO for this swap + input_utxo: Output, /// Where the results of the swap need to be paid destination: Destination, /// The liquidity provider fee to charge @@ -73,6 +73,7 @@ pub fn do_swap( /// The output to compare against output: Output, ) -> PoolState { + let Output { value: input_value, .. } = input_utxo // Destructure the pool state // TODO: it'd make the code nightmarish, but things would be way more efficient to just always pass these values destructured as parameters... let (offer_policy_id, offer_asset_name, offer_amt) = offer @@ -84,6 +85,25 @@ pub fn do_swap( let (a_policy_id, a_asset_name, a_amt) = quantity_a let (b_policy_id, b_asset_name, b_amt) = quantity_b + // Make sure we're paying the result to the correct destination (both the address and the datum), + // with the correct amount; In the special case where Datum is "Self" (for example for a repeating strategy) + // use the input datum for validation + expect when destination is { + Fixed { address, datum } -> { + and { + output.address == address, + output.datum == datum + } + } + Self -> { + let Output { address: input_address, datum: input_datum, .. } = input_utxo + and { + output.address == input_address, + output.datum == input_datum + } + } + } + // There are two symmetric cases, depending on whether you're giving order A or order B // If there's a clever way to write this as one branch it might be clearer to read if offer_policy_id == a_policy_id && offer_asset_name == a_asset_name { @@ -106,10 +126,9 @@ pub fn do_swap( input_value, ) - // Check that the output has the correct destination and value - expect output.address == destination.address - expect output.datum == destination.datum + // Make sure the correct value (including change) carries through to the output expect output.value == out_value + // And check that the min_received (the lowest amount of tokens the user is willing to receive, aka a limit price) // is satisfied expect takes >= min_received.3rd @@ -137,9 +156,9 @@ pub fn do_swap( input_value, ) - expect output.address == destination.address - expect output.datum == destination.datum + // Make sure the correct value (including change) carries through to the output expect output.value == out_value + // Check that mintakes is satisfied expect takes >= min_received.3rd PoolState { @@ -179,7 +198,7 @@ test swap_mintakes_too_high() fail { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, @@ -193,7 +212,13 @@ test swap_mintakes_too_high() fail { datum: NoDatum, reference_script: None, } - let final_pool_state = do_swap(pool_state, input_value, order.destination, 5, 2_500_000, swap_offer, swap_min_received, output) + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } + let final_pool_state = do_swap(pool_state, input, order.destination, 5, 2_500_000, swap_offer, swap_min_received, output) expect final_pool_state.quantity_a.3rd == 1_000_000_000 + 10_000_000 expect final_pool_state.quantity_b.3rd == 1_000_000_000 - 9_896_088 True diff --git a/lib/calculation/withdrawal.ak b/lib/calculation/withdrawal.ak index 8a9cc4d..8e1c082 100644 --- a/lib/calculation/withdrawal.ak +++ b/lib/calculation/withdrawal.ak @@ -1,12 +1,12 @@ use aiken/option use aiken/math -use aiken/transaction.{NoDatum, Output} +use aiken/transaction.{NoDatum, InlineDatum, Output} use aiken/transaction/credential.{Address, VerificationKeyCredential} use aiken/transaction/value.{Value, ada_policy_id, ada_asset_name} use calculation/shared.{PoolState} as calc_shared use sundae/multisig use shared.{SingletonValue} -use types/order.{Destination, OrderDatum} +use types/order.{Destination, Fixed, Self, OrderDatum} /// Execute a withdrawal order /// @@ -14,13 +14,14 @@ use types/order.{Destination, OrderDatum} /// of the circulating supply of LP tokens that was burned. pub fn do_withdrawal( pool_state: PoolState, // The interim pool state against which we should calculate the withdrawal - input_value: Value, // The incoming value; may have additional assets on it, for example if executing as part of a larger chain + input_utxo: Output, // The incoming UTXO, useful for returning surplus and handling Self destinations amount: SingletonValue, // The amount to actually withdraw destination: Destination, // The destination that the LP tokens (and change) should be sent to actual_protocol_fee: Int, // The protocol fee to deduct output: Output, // The output to compare the order execution against, to ensure the right quantity was paid out ) -> PoolState { let (lp_policy, lp_asset_name, amount) = amount + let Output { value: input_value, .. } = input_utxo // Make sure we're withdrawing from the right pool; // if we were to provide LP tokens for a different pool, you could withdraw funds that weren't yours @@ -61,12 +62,26 @@ pub fn do_withdrawal( withdrawn_b, ) - // Check that the result is paid to the destination with the full value - // Like on other order types, the datum here lets us compose with other protocols - // or do chained orders within Sundae - expect output.address == destination.address - expect output.datum == destination.datum + + // Make sure we're paying the result to the correct destination (both the address and the datum), + // with the correct amount; In the special case where Datum is "Self" (for example for a repeating strategy) + // use the input datum for validation expect output.value == remainder + expect when destination is { + Fixed { address, datum } -> { + and { + output.address == address, + output.datum == datum + } + } + Self -> { + let Output { address: input_address, datum: input_datum, .. } = input_utxo + and { + output.address == input_address, + output.datum == input_datum + } + } + } // Return the final pool state PoolState { @@ -153,7 +168,7 @@ fn withdrawal_test(options: WithdrawalTestOptions) { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, @@ -172,7 +187,13 @@ fn withdrawal_test(options: WithdrawalTestOptions) { datum: NoDatum, reference_script: None, } - let final_pool_state = do_withdrawal(pool_state, input_value, amount, order.destination, 2_500_000, output) + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } + let final_pool_state = do_withdrawal(pool_state, input, amount, order.destination, 2_500_000, output) expect final_pool_state.quantity_a.3rd == 1_000_000_000 expect final_pool_state.quantity_b.3rd == 1_000_000_000 True diff --git a/lib/tests/aiken/deposit.ak b/lib/tests/aiken/deposit.ak index 830e797..0cb2449 100644 --- a/lib/tests/aiken/deposit.ak +++ b/lib/tests/aiken/deposit.ak @@ -1,11 +1,11 @@ use sundae/multisig use aiken/math -use aiken/transaction.{NoDatum, Output} +use aiken/transaction.{NoDatum, InlineDatum, Output} use aiken/transaction/value use aiken/transaction/credential.{Address, VerificationKeyCredential} use calculation/shared.{PoolState} use calculation/deposit.{do_deposit} -use types/order.{OrderDatum, Destination} +use types/order.{OrderDatum, Fixed} test deposit_test() { let lp = (#"99999999999999999999999999999999999999999999999999999999", "LP") @@ -48,7 +48,7 @@ fn deposit_test_schema(qa: Int, qb: Int, has_a: Int, has_b: Int, gives_a: Int, g #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { + destination: Fixed { address: addr, datum: NoDatum, }, @@ -63,9 +63,15 @@ fn deposit_test_schema(qa: Int, qb: Int, has_a: Int, has_b: Int, gives_a: Int, g datum: NoDatum, reference_script: None, } + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } let PoolState{..} = do_deposit( pool_state, - input_value, + input, assets, order.destination, 2_500_000, diff --git a/lib/tests/aiken/donation.ak b/lib/tests/aiken/donation.ak index 268e5c8..367ae82 100644 --- a/lib/tests/aiken/donation.ak +++ b/lib/tests/aiken/donation.ak @@ -1,10 +1,10 @@ -use aiken/transaction.{NoDatum, Output} +use aiken/transaction.{NoDatum, InlineDatum, Output} use aiken/transaction/credential.{Address, VerificationKeyCredential} use aiken/transaction/value use calculation/shared.{PoolState} as calc_shared use calculation/donation.{do_donation} use sundae/multisig -use types/order.{Destination, OrderDatum} +use types/order.{Fixed, OrderDatum} test donation() { let addr = @@ -38,12 +38,18 @@ test donation() { #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", ), max_protocol_fee: 2_500_000, - destination: Destination { address: addr, datum: NoDatum }, + destination: Fixed { address: addr, datum: NoDatum }, details: order.Donation { assets: assets, }, extension: Void, } + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } // There's no remainder so do_donation totally ignores this Output record let output = Output { @@ -53,9 +59,129 @@ test donation() { reference_script: None, } let (final_pool_state, has_remainder) = - do_donation(pool_state, input_value, assets, order.destination, 2_500_000, output) + do_donation(pool_state, input, assets, order.destination, 2_500_000, output) expect !has_remainder expect final_pool_state.quantity_a.3rd == 1_001_000_000 expect final_pool_state.quantity_b.3rd == 1_001_000_000 True } + + +test donation_with_remainder() { + let addr = + Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ) + let ada = (#"", #"") + let rberry = + (#"01010101010101010101010101010101010101010101010101010101", "RBERRY") + let lp = (#"99999999999999999999999999999999999999999999999999999999", "LP") + let pool_state = + PoolState { + quantity_a: (#"", #"", 1_000_000_000), + quantity_b: (rberry.1st, rberry.2nd, 1_000_000_000), + quantity_lp: (lp.1st, lp.2nd, 1_000_000_000), + } + let input_value = + value.from_lovelace(3_500_000) + |> value.add(rberry.1st, rberry.2nd, 1_000_000) + let assets = ( + (ada.1st, ada.2nd, 0), + (rberry.1st, rberry.2nd, 1_000_000), + ) + let order = + OrderDatum { + pool_ident: None, + owner: multisig.Signature( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + max_protocol_fee: 2_500_000, + destination: Fixed { address: addr, datum: NoDatum }, + details: order.Donation { + assets: assets, + }, + extension: Void, + } + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } + let output = + Output { + address: addr, + value: value.from_lovelace(1_000_000), + datum: NoDatum, + reference_script: None, + } + let (final_pool_state, has_remainder) = + do_donation(pool_state, input, assets, order.destination, 2_500_000, output) + expect has_remainder + expect final_pool_state.quantity_a.3rd == 1_000_000_000 + expect final_pool_state.quantity_b.3rd == 1_001_000_000 + True +} + + +test donation_with_wrong_remainder() fail { + let addr = + Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ) + let ada = (#"", #"") + let rberry = + (#"01010101010101010101010101010101010101010101010101010101", "RBERRY") + let lp = (#"99999999999999999999999999999999999999999999999999999999", "LP") + let pool_state = + PoolState { + quantity_a: (#"", #"", 1_000_000_000), + quantity_b: (rberry.1st, rberry.2nd, 1_000_000_000), + quantity_lp: (lp.1st, lp.2nd, 1_000_000_000), + } + let input_value = + value.from_lovelace(3_500_000) + |> value.add(rberry.1st, rberry.2nd, 1_000_000) + let assets = ( + (ada.1st, ada.2nd, 0), + (rberry.1st, rberry.2nd, 1_000_000), + ) + let order = + OrderDatum { + pool_ident: None, + owner: multisig.Signature( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + max_protocol_fee: 2_500_000, + destination: Fixed { address: addr, datum: NoDatum }, + details: order.Donation { + assets: assets, + }, + extension: Void, + } + let input = Output { + address: addr, + value: input_value, + datum: InlineDatum(order), + reference_script: None, + } + let output = + Output { + address: addr, + value: value.from_lovelace(123), + datum: NoDatum, + reference_script: None, + } + let (final_pool_state, has_remainder) = + do_donation(pool_state, input, assets, order.destination, 2_500_000, output) + expect has_remainder + expect final_pool_state.quantity_a.3rd == 1_000_000_000 + expect final_pool_state.quantity_b.3rd == 1_001_000_000 + True +} \ No newline at end of file diff --git a/lib/tests/examples/ex_order.ak b/lib/tests/examples/ex_order.ak index 520aa6c..1398a5a 100644 --- a/lib/tests/examples/ex_order.ak +++ b/lib/tests/examples/ex_order.ak @@ -1,5 +1,5 @@ use aiken/cbor -use types/order.{Destination, OrderDatum, Swap, Deposit, Withdrawal, Strategy, StrategyExecution, SignedStrategyExecution, Signature, Scoop, Cancel} +use types/order.{Fixed, OrderDatum, Swap, Deposit, Withdrawal, Strategy, StrategyExecution, SignedStrategyExecution, Signature, Scoop, Cancel} use aiken/transaction.{NoDatum, OutputReference, TransactionId} use aiken/interval use sundae/multisig @@ -7,7 +7,7 @@ use tests/examples/ex_shared.{print_example, wallet_address} fn mk_swap() -> OrderDatum { let addr = wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let dest = Destination { address: addr, datum: NoDatum } + let dest = Fixed { address: addr, datum: NoDatum } let swap = Swap( (#"", #"", 10000000), @@ -35,7 +35,7 @@ test example_swap() { fn mk_deposit() -> OrderDatum { let addr = wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let dest = Destination { address: addr, datum: NoDatum } + let dest = Fixed { address: addr, datum: NoDatum } let deposit = Deposit(( (#"", #"", 10000000), @@ -63,7 +63,7 @@ test example_deposit() { fn mk_withdrawal() { let addr = wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let dest = Destination { address: addr, datum: NoDatum } + let dest = Fixed { address: addr, datum: NoDatum } let withdrawal = Withdrawal( ( @@ -88,10 +88,9 @@ test example_withdrawal() { print_example(mk_withdrawal()) } - fn mk_strategy() { let addr = wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let dest = Destination { address: addr, datum: NoDatum } + let dest = Fixed { address: addr, datum: NoDatum } let strategy = Strategy(Signature(#"d441227553a0f1a965fee7d60a0f724b368dd1bddbc208730fccebcf")) OrderDatum { pool_ident: Some(#"fc2c4a6ae8048b0b5affc169dfd496a7ace7d08288c476d9d7a5804e"), diff --git a/lib/types/order.ak b/lib/types/order.ak index b2b9d3e..c4ed945 100644 --- a/lib/types/order.ak +++ b/lib/types/order.ak @@ -32,11 +32,16 @@ pub type OrderDatum { /// The destination where funds are to be sent after the order pub type Destination { - /// The address to attach to the output (such as the users wallet, or another script) - address: Address, - /// The datum to attach to the output; Could be none in typical cases, but could also be used to compose with other protocols, - /// such as paying the results of a swap back to a treasury contract - datum: Datum, + Fixed { + /// The address to attach to the output (such as the users wallet, or another script) + address: Address, + /// The datum to attach to the output; Could be none in typical cases, but could also be used to compose with other protocols, + /// such as paying the results of a swap back to a treasury contract + datum: Datum, + } + /// The result gets paid back to the exact same address and datum it came from; useful for strategies + /// with unbounded legs + Self } /// There are two ways to delegate authorization of a strategy:: diff --git a/validators/tests/pool.ak b/validators/tests/pool.ak index 099fcbe..5ce87de 100644 --- a/validators/tests/pool.ak +++ b/validators/tests/pool.ak @@ -20,7 +20,7 @@ use tests/examples/ex_settings.{mk_valid_settings_input} use tests/examples/ex_shared.{ mk_output_reference, mk_tx_hash, script_address, wallet_address, } -use types/order.{Deposit, Destination, OrderDatum, Swap} +use types/order.{Deposit, Destination, Fixed, Self, OrderDatum, Swap} use types/pool.{ PoolMintRedeemer, CreatePool, PoolDatum, PoolScoop, } @@ -33,7 +33,8 @@ use pool as pool_validator type ScoopTestOptions { edit_escrow_1_value: Option, edit_escrow_2_value: Option, - edit_escrow_destination: Option
, + edit_escrow_intended_destination: Option, + edit_escrow_actual_destination: Option, edit_fee: Option, edit_swap_fees: Option<(Int,Int)>, edit_pool_input_address: Option
, @@ -46,7 +47,8 @@ fn default_scoop_test_options() -> ScoopTestOptions { ScoopTestOptions { edit_escrow_1_value: None, edit_escrow_2_value: None, - edit_escrow_destination: None, + edit_escrow_intended_destination: None, + edit_escrow_actual_destination: None, edit_fee: None, edit_swap_fees: None, edit_pool_input_address: None, @@ -72,7 +74,7 @@ test scoop_bad_destination() fail { let options = ScoopTestOptions { ..default_scoop_test_options(), - edit_escrow_destination: Some(burn_addr), + edit_escrow_actual_destination: Some(Fixed(burn_addr, NoDatum)), } scoop(options) } @@ -272,7 +274,7 @@ fn scoop(options: ScoopTestOptions) { reference_script: None, }, } - let dest = Destination { address: user_addr, datum: NoDatum } + let dest = option.or_else(options.edit_escrow_intended_destination, Fixed { address: user_addr, datum: NoDatum }) let swap = Swap((#"", #"", 10_000_000), (dummy_policy_id, dummy_asset_name, 0)) let escrow_datum = @@ -315,26 +317,37 @@ fn scoop(options: ScoopTestOptions) { } Input { output_reference, output: updated_output } } + let (escrow1_out_addr, escrow1_out_datum) = when options.edit_escrow_actual_destination is { + Some(Fixed(dest, datum)) -> (dest, datum) + Some(Self) -> (escrow_address, InlineDatum(escrow_datum)) + None -> (user_addr, NoDatum) + } let escrow1_out = Output { - address: option.or_else(options.edit_escrow_destination, user_addr), + address: escrow1_out_addr, value: option.or_else( options.edit_escrow_1_value, value.from_lovelace(2_000_000) |> value.add(dummy_policy_id, dummy_asset_name, 9_896_088), ), - datum: NoDatum, + datum: escrow1_out_datum, reference_script: None, } + // TODO: manage separately? + let (escrow2_out_addr, escrow2_out_datum) = when options.edit_escrow_actual_destination is { + Some(Fixed(dest, datum)) -> (dest, datum) + Some(Self) -> (escrow_address, InlineDatum(escrow_datum)) + None -> (user_addr, NoDatum) + } let escrow2_out = Output { - address: option.or_else(options.edit_escrow_destination, user_addr), + address: escrow2_out_addr, value: option.or_else( options.edit_escrow_2_value, value.from_lovelace(2_000_000) |> value.add(dummy_policy_id, dummy_asset_name, 9_702_095), ), - datum: NoDatum, + datum: escrow2_out_datum, reference_script: None, } let pool_output = @@ -433,7 +446,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) { reference_script: None, }, } - let dest = Destination { address: user_addr, datum: NoDatum } + let dest = options.edit_escrow_intended_destination |> option.or_else(Fixed { address: user_addr, datum: NoDatum }) let swap = Swap( (#"", #"", 10_000_000), @@ -491,20 +504,30 @@ fn scoop_swap_deposit(options: ScoopTestOptions) { output: updated_output, } } + let (escrow1_out_addr, escrow1_out_datum) = when options.edit_escrow_actual_destination is { + Some(Fixed(dest, datum)) -> (dest, datum) + Some(Self) -> (escrow_address, InlineDatum(escrow_datum_1)) + None -> (user_addr, NoDatum) + } let escrow1_out = Output { - address: option.or_else(options.edit_escrow_destination, user_addr), + address: escrow1_out_addr, value: option.or_else(options.edit_escrow_1_value, value.from_lovelace(2_000_000) |> value.add(dummy_policy_id, dummy_asset_name, 9_896_088)), - datum: NoDatum, + datum: escrow1_out_datum, reference_script: None, } + let (escrow2_out_addr, escrow2_out_datum) = when options.edit_escrow_actual_destination is { + Some(Fixed(dest, datum)) -> (dest, datum) + Some(Self) -> (escrow_address, InlineDatum(escrow_datum_2)) + None -> (user_addr, NoDatum) + } let escrow2_out = Output { - address: option.or_else(options.edit_escrow_destination, user_addr), + address: escrow2_out_addr, value: value.from_lovelace(2_000_000) |> value.add(pool_script_hash, pool_lp_name(pool_id), 9_900_990) |> value.add(dummy_policy_id, dummy_asset_name, 196_990), - datum: NoDatum, + datum: escrow2_out_datum, reference_script: None, } let pool_output = Output { @@ -549,6 +572,15 @@ fn scoop_swap_deposit(options: ScoopTestOptions) { result } +test scoop_strategy_self() { + let options = ScoopTestOptions { + ..default_scoop_test_options(), + edit_escrow_intended_destination: Some(Self), + edit_escrow_actual_destination: Some(Self), + } + scoop(options) +} + fn pool_test_tx_input() -> Input { let funds_input = new_tx_input(