diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a51db02..030110d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: - uses: aiken-lang/setup-aiken@v0.1.0 with: - version: v1.0.20-alpha + version: v1.0.24-alpha - run: | # Run the tests diff --git a/aiken.lock b/aiken.lock index c86c914..1482efe 100644 --- a/aiken.lock +++ b/aiken.lock @@ -11,6 +11,11 @@ name = "SundaeSwap-finance/aicone" version = "ae0852d40cc6332437492102451cf331a3c10b0d" source = "github" +[[requirements]] +name = "aiken-extra/tx_util" +version = "1.170.202312" +source = "github" + [[packages]] name = "aiken-lang/stdlib" version = "97cd61345bcc8925c521b30d0f354859eb0148cd" @@ -23,4 +28,10 @@ version = "ae0852d40cc6332437492102451cf331a3c10b0d" requirements = [] source = "github" +[[packages]] +name = "aiken-extra/tx_util" +version = "1.170.202312" +requirements = [] +source = "github" + [etags] diff --git a/aiken.toml b/aiken.toml index 8a78860..bb019cb 100644 --- a/aiken.toml +++ b/aiken.toml @@ -17,3 +17,8 @@ source = "github" name = "SundaeSwap-finance/aicone" version = "ae0852d40cc6332437492102451cf331a3c10b0d" source = "github" + +[[dependencies]] +name = "aiken-extra/tx_util" +version = "1.170.202312" +source = "github" diff --git a/validators/pool.ak b/validators/pool.ak index 0a1eaff..9379915 100644 --- a/validators/pool.ak +++ b/validators/pool.ak @@ -1,28 +1,27 @@ -use aiken/builtin use aiken/bytearray use aiken/dict use aiken/hash use aiken/interval use aiken/list use aiken/math -use aiken/option use aiken/transaction.{ - Datum, InlineDatum, Input, Mint, NoDatum, Output, OutputReference, - ScriptContext, Spend, Transaction, TransactionId, + InlineDatum, Input, Mint, Output, OutputReference, + ScriptContext, Transaction, TransactionId, } use aiken/transaction/credential.{ - Address, ScriptCredential, VerificationKeyCredential, Inline + Address, Inline, ScriptCredential, } use aiken/transaction/value.{MintedValue, PolicyId, Value, ada_policy_id} use calculation/process.{pool_input_to_state, process_orders} use calculation/shared.{PoolState} as calc_shared -use shared.{AssetClass, Ident, spent_output, pool_nft_name, pool_lp_name, has_exact_token_count, count_orders} +use shared.{ + AssetClass, Ident, count_orders, has_exact_token_count, pool_lp_name, + pool_nft_name, spent_output, +} use sundae/multisig -use tests/examples/ex_settings.{mk_valid_settings_input} -use tests/examples/ex_shared.{wallet_address, script_address, mk_output_reference, mk_tx_hash} -use types/order.{Destination, OrderDatum, Swap, Deposit} use types/pool.{ - CreatePool, MintLP, PoolDatum, PoolMintRedeemer, PoolRedeemer, PoolScoop, WithdrawFees + CreatePool, MintLP, PoolDatum, PoolMintRedeemer, PoolRedeemer, PoolScoop, + WithdrawFees, } use types/settings.{SettingsDatum, find_settings_datum} @@ -53,7 +52,7 @@ use types/settings.{SettingsDatum, find_settings_datum} /// /// A + B*n + C < (A + B) * n validator(settings_policy_id: PolicyId) { - fn spend(datum: PoolDatum, redeemer: PoolRedeemer, ctx: ScriptContext) { + pub fn spend(datum: PoolDatum, redeemer: PoolRedeemer, ctx: ScriptContext) { // First, we destructure the transaction right upfront, because field access is O(n), // and we want access to these fields with just a single pass over the transaction // This will be a common pattern throughout the scripts @@ -215,7 +214,6 @@ validator(settings_policy_id: PolicyId) { outcome, actual_protocol_fees, ) - // Now, we check various things about the output datum to ensure they're each correct. // Check that the datum correctly records the final circulating LP, accounting for any deposits and withdrawals // In particular, this is important because that circulating supply is exaclty what determines the users ownership of assets in the pool @@ -230,16 +228,12 @@ validator(settings_policy_id: PolicyId) { datum.assets == output_datum.assets, datum.fees_per_10_thousand == output_datum.fees_per_10_thousand, datum.market_open == output_datum.market_open, - // Finally, make sure we don't change the stake credential; this can only be done when withdrawing fees, by the treasury administrator pool_input.address.stake_credential == pool_output.address.stake_credential, } } WithdrawFees { amount, treasury_output } -> { - let PoolDatum { - protocol_fees: initial_protocol_fees, - .. - } = datum + let PoolDatum { protocol_fees: initial_protocol_fees, .. } = datum // Make sure we withdraw *only* up to what we've earned // We allow less than, so that you can leave some behind for the minUTXO cost, or continuing to earn staking rewards, etc. expect amount <= initial_protocol_fees @@ -300,7 +294,7 @@ validator(settings_policy_id: PolicyId) { } } - fn mint(r: PoolMintRedeemer, ctx: ScriptContext) { + pub fn mint(r: PoolMintRedeemer, ctx: ScriptContext) { // When minting, we can be doing one of two things: minting the pool itself, or minting the LP token when r is { // For creating a new pool, one of our design objectives was to avoid requiring interaction with any global @@ -414,15 +408,19 @@ validator(settings_policy_id: PolicyId) { new_pool_nft_token, ) == 1 } - + // Make sure we send the pool metadata token to the metadata admin // We use an index from the redeemer to skip to the right output, in case there are multiple outputs to the metadata admin // This is safe to do for the usual reasons: if they point at a UTXO without the ref token, the transaction will fail. expect Some(metadata_output) = list.at(ctx.transaction.outputs, metadata_output_ix) expect metadata_output.address == settings_datum.metadata_admin - expect value.quantity_of(metadata_output.value, own_policy_id, new_pool_ref_token) == 1 - + expect + value.quantity_of( + metadata_output.value, + own_policy_id, + new_pool_ref_token, + ) == 1 // And check that the datum is initialized correctly; This is part of why we have a minting policy handling this, // as it allows us to authenticate the providence of the datum. @@ -522,7 +520,6 @@ fn has_expected_pool_value( has_exact_token_count(output_value, 3), value.lovelace_of(output_value) == final_protocol_fees + quantity_a_amt, value.quantity_of(output_value, quantity_b_policy_id, quantity_b_name) == quantity_b_amt, - value.quantity_of( output_value, pool_script_hash, @@ -558,591 +555,7 @@ fn compare_asset_class(a: AssetClass, b: AssetClass) { } // Convert a specific integer (like a UTXO index) into a byte array, so we can construct a hashable string when minting the pool -fn int_to_ident(n: Int) -> Ident { +pub fn int_to_ident(n: Int) -> Ident { expect n < 256 bytearray.push(#"", n) -} - -// Tests - -type ScoopTestOptions { - edit_escrow_1_value: Option, - edit_escrow_2_value: Option, - edit_escrow_destination: Option
, - edit_fee: Option, - edit_swap_fees: Option<(Int,Int)>, - edit_pool_output_value: Option, - edit_settings_datum: Option, -} - -fn default_scoop_test_options() -> ScoopTestOptions { - ScoopTestOptions { - edit_escrow_1_value: None, - edit_escrow_2_value: None, - edit_escrow_destination: None, - edit_fee: None, - edit_swap_fees: None, - edit_pool_output_value: None, - edit_settings_datum: None, - } -} - -test ok_scoop_swap_swap() { - let options = default_scoop_test_options() - scoop(options) -} - -test ok_scoop_swap_deposit() { - let options = default_scoop_test_options() - scoop_swap_deposit(options) -} - -test scoop_bad_destination() fail { - let burn_addr = - wallet_address(#"12000000000000000000000000000000000000000000000000000000") - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_escrow_destination: Some(burn_addr), - } - scoop(options) -} - -test scoop_payouts_swapped() fail { - let dummy_policy_id = - #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let dummy_asset_name = #"53554e444145" - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_escrow_1_value: Some( - value.from_lovelace(2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 9_702_095), - ), - edit_escrow_2_value: Some( - value.from_lovelace(2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 9_896_088), - ), - } - scoop(options) -} - -test pool_validator_ignores_fee() { - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_fee: Some(value.from_lovelace(1_000_000_000)), - } - scoop(options) -} - -test scoop_high_swap_fees() { - let hash_of_pool_script = - #"00000000000000000000000000000000000000000000000000000000" - let pool_id = #"00000000000000000000000000000000000000000000000000000000" - let pool_nft_name = shared.pool_nft_name(pool_id) - let dummy_policy_id = - #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let dummy_asset_name = #"53554e444145" - let swap_fee = 100 - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_swap_fees: Some((swap_fee, swap_fee)), - edit_escrow_1_value: Some( - value.from_lovelace(2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 9_802_950), - ), - edit_escrow_2_value: Some( - value.from_lovelace(2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 9_611_678), - ), - edit_pool_output_value: Some( - value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) - |> value.add( - dummy_policy_id, - dummy_asset_name, - 1_000_000_000 - ( 9_802_950 + 9_611_678 ), - ) - |> value.add(hash_of_pool_script, pool_nft_name, 1), - ), - } - scoop(options) -} - -test output_missing_nft() fail { - let dummy_policy_id = - #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let dummy_asset_name = #"53554e444145" - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_pool_output_value: Some( - value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) - |> value.add( - dummy_policy_id, - dummy_asset_name, - 1_000_000_000 - ( 9_896_088 + 9_702_095 ), - ), - ), - } - scoop(options) -} - -test scooper_not_in_settings() fail { - let somebody = #"11111111111111111111111111111111111111111111111111111111" - let options = - ScoopTestOptions { - ..default_scoop_test_options(), - edit_settings_datum: Some( - InlineDatum( - SettingsDatum { - settings_admin: multisig.AnyOf([]), - metadata_admin: Address( - VerificationKeyCredential( - #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", - ), - None, - ), - treasury_admin: multisig.AnyOf([]), - treasury_address: Address( - VerificationKeyCredential( - #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", - ), - None, - ), - treasury_allowance: (1, 10), - authorized_scoopers: Some([somebody]), - authorized_staking_keys: [], - base_fee: 0, - simple_fee: 2_500_000, - strategy_fee: 5_000_000, - pool_creation_fee: 0, - extensions: Void, - }, - ), - ), - } - scoop(options) -} - -fn scoop(options: ScoopTestOptions) { - let settings_policy_id = - #"00000000000000000000000000000000000000000000000000000000" - let dummy_policy_id = - #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let dummy_asset_name = #"53554e444145" - let scooper = #"00000000000000000000000000000000000000000000000000000000" - let hash_of_pool_script = - #"00000000000000000000000000000000000000000000000000000000" - let hash_of_escrow_script = - #"00000000000000000000000000000000000000000000000000000000" - let user_addr = - wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let owner = - multisig.Signature( - #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", - ) - let pool_id = #"00000000000000000000000000000000000000000000000000000000" - let pool_datum = - PoolDatum { - identifier: pool_id, - assets: ((#"", #""), (dummy_policy_id, dummy_asset_name)), - circulating_lp: 1_000_000_000, - fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), - market_open: 0, - fee_finalized: 100, - protocol_fees: 2_000_000, - } - let pool_out_datum = - PoolDatum { - identifier: pool_id, - assets: ((#"", #""), (dummy_policy_id, dummy_asset_name)), - circulating_lp: 1_000_000_000, - fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), - market_open: 0, - fee_finalized: 0, - protocol_fees: 7_000_000, - } - let pool_nft_name = shared.pool_nft_name(pool_id) - let pool_address = script_address(hash_of_pool_script) - let pool_input = - Input { - output_reference: mk_output_reference(0), - output: Output { - address: pool_address, - value: value.from_lovelace(1_000_000_000 + 2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000) - |> value.add(hash_of_pool_script, pool_nft_name, 1), - datum: InlineDatum(pool_datum), - reference_script: None, - }, - } - let dest = Destination { address: user_addr, datum: NoDatum } - let swap = - Swap((#"", #"", 10_000_000), (dummy_policy_id, dummy_asset_name, 0)) - let escrow_datum = - OrderDatum { - pool_ident: None, - owner, - max_protocol_fee: 2_500_000, - destination: dest, - details: swap, - extension: builtin.i_data(0), - } - let escrow_address = script_address(hash_of_escrow_script) - let escrow1_in = - Input { - output_reference: mk_output_reference(2), - output: Output { - address: escrow_address, - value: value.from_lovelace(4_500_000 + 10_000_000), - datum: InlineDatum(escrow_datum), - reference_script: None, - }, - } - let escrow2_in = - Input { - output_reference: mk_output_reference(3), - output: Output { - address: escrow_address, - value: value.from_lovelace(4_500_000 + 10_000_000), - datum: InlineDatum(escrow_datum), - reference_script: None, - }, - } - let settings_input = { - let Input { output_reference, output } = - mk_valid_settings_input([scooper], 1) - let updated_output = - Output { - ..output, - datum: option.or_else(options.edit_settings_datum, output.datum), - } - Input { output_reference, output: updated_output } - } - let escrow1_out = - Output { - address: option.or_else(options.edit_escrow_destination, user_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, - reference_script: None, - } - let escrow2_out = - Output { - address: option.or_else(options.edit_escrow_destination, user_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, - reference_script: None, - } - let pool_output = - Output { - address: pool_address, - value: option.or_else( - options.edit_pool_output_value, - value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) - |> value.add( - dummy_policy_id, - dummy_asset_name, - 1_000_000_000 - ( 9_896_088 + 9_702_095 ), - ) - |> value.add(hash_of_pool_script, pool_nft_name, 1), - ), - datum: InlineDatum(pool_out_datum), - reference_script: None, - } - let ctx = - ScriptContext { - transaction: Transaction { - inputs: [pool_input, escrow1_in, escrow2_in], - reference_inputs: [settings_input], - outputs: [pool_output, escrow1_out, escrow2_out], - fee: option.or_else(options.edit_fee, value.from_lovelace(1_000_000)), - mint: value.to_minted_value(value.from_lovelace(0)), - certificates: [], - withdrawals: dict.new(), - validity_range: interval.between(1, 2), - extra_signatories: [scooper], - redeemers: dict.new(), - datums: dict.new(), - id: mk_tx_hash(1), - }, - purpose: Spend(pool_input.output_reference), - } - let pool_redeemer = PoolScoop( - 0, - 0, - [ - (1, None, 0), - (2, None, 0), - ] - ) - let result = spend(settings_policy_id, pool_datum, pool_redeemer, ctx) - result -} - -fn scoop_swap_deposit(options: ScoopTestOptions) { - let settings_policy_id = #"00000000000000000000000000000000000000000000000000000000" - let dummy_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let dummy_asset_name = #"53554e444145" - let scooper = #"00000000000000000000000000000000000000000000000000000000" - let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000" - let hash_of_escrow_script = #"00000000000000000000000000000000000000000000000000000000" - let user_addr = wallet_address( - #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513" - ) - let owner = multisig.Signature( - #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", - ) - let pool_id = #"000000000000000000000000000000000000000000000000000000000000" - let pool_datum = PoolDatum { - identifier: pool_id, - assets: ( - (#"", #""), - (dummy_policy_id, dummy_asset_name), - ), - circulating_lp: 1_000_000_000, - fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), - market_open: 0, - fee_finalized: 0, - protocol_fees: 2_000_000, - } - let pool_out_datum = PoolDatum { - identifier: pool_id, - assets: ( - (#"", #""), - (dummy_policy_id, dummy_asset_name), - ), - circulating_lp: 1_009_900_990, - fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), - market_open: 0, - fee_finalized: 0, - protocol_fees: 7_000_000, - } - let pool_nft_name = shared.pool_nft_name(pool_id) - let pool_address = script_address(hash_of_pool_script) - let pool_input = Input { - output_reference: mk_output_reference(0), - output: Output { - address: pool_address, - value: value.from_lovelace(1_000_000_000 + 2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000) - |> value.add(hash_of_pool_script, pool_nft_name, 1), - datum: InlineDatum(pool_datum), - reference_script: None, - }, - } - let dest = Destination { address: user_addr, datum: NoDatum } - let swap = - Swap( - (#"", #"", 10_000_000), - (dummy_policy_id, dummy_asset_name, 0), - ) - let deposit = - Deposit(( - (#"", #"", 10_000_000), - (dummy_policy_id, dummy_asset_name, 10_000_000), - )) - let escrow_datum_1 = OrderDatum { - pool_ident: None, - owner: owner, - max_protocol_fee: 2_500_000, - destination: dest, - details: swap, - extension: builtin.i_data(0), - } - let escrow_datum_2 = OrderDatum { - pool_ident: None, - owner: owner, - max_protocol_fee: 2_500_000, - destination: dest, - details: deposit, - extension: builtin.i_data(0), - } - let escrow_address = script_address(hash_of_escrow_script) - let escrow1_in = Input { - output_reference: mk_output_reference(2), - output: Output { - address: escrow_address, - value: value.from_lovelace(4_500_000 + 10_000_000), - datum: InlineDatum(escrow_datum_1), - reference_script: None, - }, - } - let escrow2_in = Input { - output_reference: mk_output_reference(3), - output: Output { - address: escrow_address, - value: value.from_lovelace(4_500_000 + 10_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 10_000_000), - datum: InlineDatum(escrow_datum_2), - reference_script: None, - }, - } - let settings_input = { - let Input {output_reference, output} = mk_valid_settings_input([scooper], 1) - let updated_output = Output { - ..output, - datum: option.or_else(options.edit_settings_datum, output.datum) - } - Input { - output_reference: output_reference, - output: updated_output, - } - } - let escrow1_out = Output { - address: option.or_else(options.edit_escrow_destination, user_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, - reference_script: None, - } - let escrow2_out = Output { - address: option.or_else(options.edit_escrow_destination, user_addr), - value: value.from_lovelace(2_000_000) - |> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990) - |> value.add(dummy_policy_id, dummy_asset_name, 196_990), - datum: NoDatum, - reference_script: None, - } - let pool_output = Output { - address: pool_address, - value: option.or_else(options.edit_pool_output_value, - value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) - |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000 - 9_896_088 + 10_000_000 - 196_990) - |> value.add(hash_of_pool_script, pool_nft_name, 1)), - datum: InlineDatum(pool_out_datum), - reference_script: None, - } - - let ctx = ScriptContext { - transaction: Transaction { - inputs: [pool_input, escrow1_in, escrow2_in], - reference_inputs: [settings_input], - outputs: [pool_output, escrow1_out, escrow2_out], - fee: option.or_else(options.edit_fee, value.from_lovelace(1_000_000)), - mint: value.to_minted_value( - value.from_lovelace(0) - |> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990) - ), - certificates: [], - withdrawals: dict.new(), - validity_range: interval.between(1, 2), - extra_signatories: [scooper], - redeemers: dict.new(), - datums: dict.new(), - id: mk_tx_hash(1), - }, - purpose: Spend(pool_input.output_reference), - } - let pool_redeemer = PoolScoop( - 0, - 0, - [ - (1, None, 0), - (2, None, 0), - ] - ) - let result = spend(settings_policy_id, pool_datum, pool_redeemer, ctx) - result -} - -test mint_test() { - let settings_policy_id = #"00000000000000000000000000000000000000000000000000000000" - let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000" - let pool_address = script_address(hash_of_pool_script) - let rberry_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - let rberry_token_name = #"524245525259" - let user_address = - wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") - let settings_input = mk_valid_settings_input([], 1) - let funds_input = Input { - output_reference: OutputReference { - transaction_id: mk_tx_hash(0), - output_index: 0 - }, - output: Output { - address: user_address, - value: value.from_lovelace(10_000_000_000) - |> value.add(rberry_policy_id, rberry_token_name, 1_000_000_000), - datum: NoDatum, - reference_script: None, - }, - } - let pool_id = - funds_input.output_reference.transaction_id.hash - |> bytearray.concat(#"23") // '#' character - |> - bytearray.concat(int_to_ident(funds_input.output_reference.output_index)) - |> hash.blake2b_256 - |> bytearray.drop(4) - let (new_pool_ref_token, new_pool_nft_token, new_pool_lp_token) = shared.pool_token_names(pool_id) - let pool_output = Output { - address: pool_address, - value: value.from_lovelace(1_002_000_000) - |> value.add(rberry_policy_id, rberry_token_name, 1_000_000_000) - |> value.add(hash_of_pool_script, new_pool_nft_token, 1), - datum: InlineDatum(PoolDatum { - identifier: pool_id, - assets: ((#"", #""), (rberry_policy_id, rberry_token_name)), - circulating_lp: 1_000_000_000, - fees_per_10_thousand: (5, 5), - market_open: 0, - fee_finalized: 0, - protocol_fees: 2_000_000, - }), - reference_script: None, - } - let lp_output = Output { - address: user_address, - value: value.from_lovelace(2_000_000) - |> value.add(hash_of_pool_script, pool_lp_name(pool_id), 1_000_000_000), - datum: NoDatum, - reference_script: None, - } - let ref_output = Output { - address: user_address, - value: value.from_lovelace(2_000_000) - |> value.add(hash_of_pool_script, new_pool_ref_token, 1), - datum: NoDatum, - reference_script: None, - } - let poolMintRedeemer = CreatePool { - assets: ((#"", #""), (rberry_policy_id, rberry_token_name)), - pool_output: 0, - metadata_output: 2, - } - let ctx = ScriptContext { - transaction: Transaction { - inputs: [funds_input], - reference_inputs: [settings_input], - outputs: [pool_output, lp_output, ref_output], - fee: value.from_lovelace(1_000_000), - mint: value.to_minted_value( - value.from_lovelace(0) - |> value.add(hash_of_pool_script, new_pool_lp_token, 1_000_000_000) - |> value.add(hash_of_pool_script, new_pool_nft_token, 1) - |> value.add(hash_of_pool_script, new_pool_ref_token, 1) - ), - certificates: [], - withdrawals: dict.new(), - validity_range: interval.between(1, 2), - extra_signatories: [], - redeemers: dict.new(), - datums: dict.new(), - id: mk_tx_hash(1), - }, - purpose: Mint(hash_of_pool_script), - } - let result = mint(settings_policy_id, poolMintRedeemer, ctx) - result -} +} \ No newline at end of file diff --git a/validators/settings.ak b/validators/settings.ak index 80a5f2e..cb68090 100644 --- a/validators/settings.ak +++ b/validators/settings.ak @@ -9,7 +9,7 @@ use shared.{spent_output} /// /// It is parameterized by the protocol_boot_utxo, a constant to make it an NFT by the usual trick. validator(protocol_boot_utxo: OutputReference) { - fn spend(d: SettingsDatum, redeemer: SettingsRedeemer, ctx: ScriptContext) { + pub fn spend(d: SettingsDatum, redeemer: SettingsRedeemer, ctx: ScriptContext) { // Find our own input so we know the datum / our own address let own_input = spent_output(ctx) let own_address = own_input.address @@ -107,7 +107,7 @@ validator(protocol_boot_utxo: OutputReference) { } // Let us mint the settings NFT exactly once, by checking that one of the inputs is the protocol_boot_utxo - fn mint(_r: Data, ctx: ScriptContext) { + pub fn mint(_r: Data, ctx: ScriptContext) { expect Mint(own_policy_id) = ctx.purpose // Check that we mint *only* one token, and it's exactly our own policy id, and the settings NFT name @@ -128,3 +128,4 @@ validator(protocol_boot_utxo: OutputReference) { } } } + diff --git a/validators/tests/pool.ak b/validators/tests/pool.ak new file mode 100644 index 0000000..59705af --- /dev/null +++ b/validators/tests/pool.ak @@ -0,0 +1,637 @@ +use aiken/builtin +use aiken/bytearray +use aiken/dict +use aiken/hash +use aiken/interval +use aiken/option +use aiken/transaction.{ + Datum, InlineDatum, Input, NoDatum, Output, OutputReference, + ScriptContext, Spend, Transaction, TransactionId, +} +use aiken/transaction/credential.{ + Address, VerificationKeyCredential, +} +use aiken/transaction/value.{Value} +use shared.{ + pool_lp_name, +} +use sundae/multisig +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/pool.{ + CreatePool, PoolDatum, PoolScoop, +} +use types/settings.{SettingsDatum} +use tx_util/builder.{add_asset_to_tx_output, add_tx_input, add_tx_output, add_tx_ref_input, build_txn_context, mint_assets, new_tx_input, new_tx_output} +use pool as pool_validator + + +type ScoopTestOptions { + edit_escrow_1_value: Option, + edit_escrow_2_value: Option, + edit_escrow_destination: Option
, + edit_fee: Option, + edit_swap_fees: Option<(Int,Int)>, + edit_pool_output_value: Option, + edit_settings_datum: Option, +} + +fn default_scoop_test_options() -> ScoopTestOptions { + ScoopTestOptions { + edit_escrow_1_value: None, + edit_escrow_2_value: None, + edit_escrow_destination: None, + edit_fee: None, + edit_swap_fees: None, + edit_pool_output_value: None, + edit_settings_datum: None, + } +} + +test ok_scoop_swap_swap() { + let options = default_scoop_test_options() + scoop(options) +} + +test ok_scoop_swap_deposit() { + let options = default_scoop_test_options() + scoop_swap_deposit(options) +} + +test scoop_bad_destination() fail { + let burn_addr = + wallet_address(#"12000000000000000000000000000000000000000000000000000000") + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_escrow_destination: Some(burn_addr), + } + scoop(options) +} + +test scoop_payouts_swapped() fail { + let dummy_policy_id = + #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let dummy_asset_name = #"53554e444145" + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_escrow_1_value: Some( + value.from_lovelace(2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 9_702_095), + ), + edit_escrow_2_value: Some( + value.from_lovelace(2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 9_896_088), + ), + } + scoop(options) +} + +test pool_validator_ignores_fee() { + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_fee: Some(value.from_lovelace(1_000_000_000)), + } + scoop(options) +} + +test scoop_high_swap_fees() { + let hash_of_pool_script = + #"00000000000000000000000000000000000000000000000000000000" + let pool_id = #"00000000000000000000000000000000000000000000000000000000" + let pool_nft_name = shared.pool_nft_name(pool_id) + let dummy_policy_id = + #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let dummy_asset_name = #"53554e444145" + let swap_fee = 100 + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_swap_fees: Some((swap_fee, swap_fee)), + edit_escrow_1_value: Some( + value.from_lovelace(2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 9_802_950), + ), + edit_escrow_2_value: Some( + value.from_lovelace(2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 9_611_678), + ), + edit_pool_output_value: Some( + value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) + |> value.add( + dummy_policy_id, + dummy_asset_name, + 1_000_000_000 - ( 9_802_950 + 9_611_678 ), + ) + |> value.add(hash_of_pool_script, pool_nft_name, 1), + ), + } + scoop(options) +} + +test output_missing_nft() fail { + let dummy_policy_id = + #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let dummy_asset_name = #"53554e444145" + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_pool_output_value: Some( + value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) + |> value.add( + dummy_policy_id, + dummy_asset_name, + 1_000_000_000 - ( 9_896_088 + 9_702_095 ), + ), + ), + } + scoop(options) +} + +test scooper_not_in_settings() fail { + let somebody = #"11111111111111111111111111111111111111111111111111111111" + let options = + ScoopTestOptions { + ..default_scoop_test_options(), + edit_settings_datum: Some( + InlineDatum( + SettingsDatum { + settings_admin: multisig.AnyOf([]), + metadata_admin: Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ), + treasury_admin: multisig.AnyOf([]), + treasury_address: Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ), + treasury_allowance: (1, 10), + authorized_scoopers: Some([somebody]), + authorized_staking_keys: [], + base_fee: 0, + simple_fee: 2_500_000, + strategy_fee: 5_000_000, + pool_creation_fee: 0, + extensions: Void, + }, + ), + ), + } + scoop(options) +} + +fn scoop(options: ScoopTestOptions) { + let settings_policy_id = + #"00000000000000000000000000000000000000000000000000000000" + let dummy_policy_id = + #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let dummy_asset_name = #"53554e444145" + let scooper = #"00000000000000000000000000000000000000000000000000000000" + let hash_of_pool_script = + #"00000000000000000000000000000000000000000000000000000000" + let hash_of_escrow_script = + #"00000000000000000000000000000000000000000000000000000000" + let user_addr = + wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") + let owner = + multisig.Signature( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ) + let pool_id = #"00000000000000000000000000000000000000000000000000000000" + let pool_datum = + PoolDatum { + identifier: pool_id, + assets: ((#"", #""), (dummy_policy_id, dummy_asset_name)), + circulating_lp: 1_000_000_000, + fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), + market_open: 0, + fee_finalized: 100, + protocol_fees: 2_000_000, + } + let pool_out_datum = + PoolDatum { + identifier: pool_id, + assets: ((#"", #""), (dummy_policy_id, dummy_asset_name)), + circulating_lp: 1_000_000_000, + fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), + market_open: 0, + fee_finalized: 0, + protocol_fees: 7_000_000, + } + let pool_nft_name = shared.pool_nft_name(pool_id) + let pool_address = script_address(hash_of_pool_script) + let pool_input = + Input { + output_reference: mk_output_reference(0), + output: Output { + address: pool_address, + value: value.from_lovelace(1_000_000_000 + 2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000) + |> value.add(hash_of_pool_script, pool_nft_name, 1), + datum: InlineDatum(pool_datum), + reference_script: None, + }, + } + let dest = Destination { address: user_addr, datum: NoDatum } + let swap = + Swap((#"", #"", 10_000_000), (dummy_policy_id, dummy_asset_name, 0)) + let escrow_datum = + OrderDatum { + pool_ident: None, + owner, + max_protocol_fee: 2_500_000, + destination: dest, + details: swap, + extension: builtin.i_data(0), + } + let escrow_address = script_address(hash_of_escrow_script) + let escrow1_in = + Input { + output_reference: mk_output_reference(2), + output: Output { + address: escrow_address, + value: value.from_lovelace(4_500_000 + 10_000_000), + datum: InlineDatum(escrow_datum), + reference_script: None, + }, + } + let escrow2_in = + Input { + output_reference: mk_output_reference(3), + output: Output { + address: escrow_address, + value: value.from_lovelace(4_500_000 + 10_000_000), + datum: InlineDatum(escrow_datum), + reference_script: None, + }, + } + let settings_input = { + let Input { output_reference, output } = + mk_valid_settings_input([scooper], 1) + let updated_output = + Output { + ..output, + datum: option.or_else(options.edit_settings_datum, output.datum), + } + Input { output_reference, output: updated_output } + } + let escrow1_out = + Output { + address: option.or_else(options.edit_escrow_destination, user_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, + reference_script: None, + } + let escrow2_out = + Output { + address: option.or_else(options.edit_escrow_destination, user_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, + reference_script: None, + } + let pool_output = + Output { + address: pool_address, + value: option.or_else( + options.edit_pool_output_value, + value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) + |> value.add( + dummy_policy_id, + dummy_asset_name, + 1_000_000_000 - ( 9_896_088 + 9_702_095 ), + ) + |> value.add(hash_of_pool_script, pool_nft_name, 1), + ), + datum: InlineDatum(pool_out_datum), + reference_script: None, + } + let ctx = + ScriptContext { + transaction: Transaction { + inputs: [pool_input, escrow1_in, escrow2_in], + reference_inputs: [settings_input], + outputs: [pool_output, escrow1_out, escrow2_out], + fee: option.or_else(options.edit_fee, value.from_lovelace(1_000_000)), + mint: value.to_minted_value(value.from_lovelace(0)), + certificates: [], + withdrawals: dict.new(), + validity_range: interval.between(1, 2), + extra_signatories: [scooper], + redeemers: dict.new(), + datums: dict.new(), + id: mk_tx_hash(1), + }, + purpose: Spend(pool_input.output_reference), + } + let pool_redeemer = PoolScoop( + 0, + 0, + [ + (1, None, 0), + (2, None, 0), + ] + ) + let result = pool_validator.spend(settings_policy_id, pool_datum, pool_redeemer, ctx) + result +} + +fn scoop_swap_deposit(options: ScoopTestOptions) { + let settings_policy_id = #"00000000000000000000000000000000000000000000000000000000" + let dummy_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let dummy_asset_name = #"53554e444145" + let scooper = #"00000000000000000000000000000000000000000000000000000000" + let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000" + let hash_of_escrow_script = #"00000000000000000000000000000000000000000000000000000000" + let user_addr = wallet_address( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513" + ) + let owner = multisig.Signature( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ) + let pool_id = #"000000000000000000000000000000000000000000000000000000000000" + let pool_datum = PoolDatum { + identifier: pool_id, + assets: ( + (#"", #""), + (dummy_policy_id, dummy_asset_name), + ), + circulating_lp: 1_000_000_000, + fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), + market_open: 0, + fee_finalized: 0, + protocol_fees: 2_000_000, + } + let pool_out_datum = PoolDatum { + identifier: pool_id, + assets: ( + (#"", #""), + (dummy_policy_id, dummy_asset_name), + ), + circulating_lp: 1_009_900_990, + fees_per_10_thousand: option.or_else(options.edit_swap_fees, (5,5)), + market_open: 0, + fee_finalized: 0, + protocol_fees: 7_000_000, + } + let pool_nft_name = shared.pool_nft_name(pool_id) + let pool_address = script_address(hash_of_pool_script) + let pool_input = Input { + output_reference: mk_output_reference(0), + output: Output { + address: pool_address, + value: value.from_lovelace(1_000_000_000 + 2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000) + |> value.add(hash_of_pool_script, pool_nft_name, 1), + datum: InlineDatum(pool_datum), + reference_script: None, + }, + } + let dest = Destination { address: user_addr, datum: NoDatum } + let swap = + Swap( + (#"", #"", 10_000_000), + (dummy_policy_id, dummy_asset_name, 0), + ) + let deposit = + Deposit(( + (#"", #"", 10_000_000), + (dummy_policy_id, dummy_asset_name, 10_000_000), + )) + let escrow_datum_1 = OrderDatum { + pool_ident: None, + owner: owner, + max_protocol_fee: 2_500_000, + destination: dest, + details: swap, + extension: builtin.i_data(0), + } + let escrow_datum_2 = OrderDatum { + pool_ident: None, + owner: owner, + max_protocol_fee: 2_500_000, + destination: dest, + details: deposit, + extension: builtin.i_data(0), + } + let escrow_address = script_address(hash_of_escrow_script) + let escrow1_in = Input { + output_reference: mk_output_reference(2), + output: Output { + address: escrow_address, + value: value.from_lovelace(4_500_000 + 10_000_000), + datum: InlineDatum(escrow_datum_1), + reference_script: None, + }, + } + let escrow2_in = Input { + output_reference: mk_output_reference(3), + output: Output { + address: escrow_address, + value: value.from_lovelace(4_500_000 + 10_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 10_000_000), + datum: InlineDatum(escrow_datum_2), + reference_script: None, + }, + } + let settings_input = { + let Input {output_reference, output} = mk_valid_settings_input([scooper], 1) + let updated_output = Output { + ..output, + datum: option.or_else(options.edit_settings_datum, output.datum) + } + Input { + output_reference: output_reference, + output: updated_output, + } + } + let escrow1_out = Output { + address: option.or_else(options.edit_escrow_destination, user_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, + reference_script: None, + } + let escrow2_out = Output { + address: option.or_else(options.edit_escrow_destination, user_addr), + value: value.from_lovelace(2_000_000) + |> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990) + |> value.add(dummy_policy_id, dummy_asset_name, 196_990), + datum: NoDatum, + reference_script: None, + } + let pool_output = Output { + address: pool_address, + value: option.or_else(options.edit_pool_output_value, + value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000) + |> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000 - 9_896_088 + 10_000_000 - 196_990) + |> value.add(hash_of_pool_script, pool_nft_name, 1)), + datum: InlineDatum(pool_out_datum), + reference_script: None, + } + + let ctx = ScriptContext { + transaction: Transaction { + inputs: [pool_input, escrow1_in, escrow2_in], + reference_inputs: [settings_input], + outputs: [pool_output, escrow1_out, escrow2_out], + fee: option.or_else(options.edit_fee, value.from_lovelace(1_000_000)), + mint: value.to_minted_value( + value.from_lovelace(0) + |> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990) + ), + certificates: [], + withdrawals: dict.new(), + validity_range: interval.between(1, 2), + extra_signatories: [scooper], + redeemers: dict.new(), + datums: dict.new(), + id: mk_tx_hash(1), + }, + purpose: Spend(pool_input.output_reference), + } + let pool_redeemer = PoolScoop( + 0, + 0, + [ + (1, None, 0), + (2, None, 0), + ] + ) + let result = pool_validator.spend(settings_policy_id, pool_datum, pool_redeemer, ctx) + result +} + +const hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000" +fn pool_test_tx_input() -> Input { + let funds_input = + new_tx_input( + mk_tx_hash(0).hash, + wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513"), + 0, + NoDatum, + ) + funds_input +} + +test mint_test_two_nfts() { + let pool_id = pool_ident_from_input(pool_test_tx_input()) + let (_, new_pool_nft_token, _) = shared.pool_token_names(pool_id) + // if we add on another pool NFT token to the pool output, it should fail + !mint_test_modify( + fn(output) { Output { ..output, value: value.add(output.value, hash_of_pool_script, new_pool_nft_token, 1) } }, + identity, + identity, + identity + ) +} + +fn pool_ident_from_input (tx_input: Input) -> ByteArray { + tx_input.output_reference.transaction_id.hash + |> bytearray.concat(#"23") // '#' character + |> + bytearray.concat(pool_validator.int_to_ident(tx_input.output_reference.output_index)) + |> hash.blake2b_256 + |> bytearray.drop(4) +} + +fn mint_test_modify( + modify_pool_output: fn(Output) -> Output, + modify_lp_output: fn(Output) -> Output, + modify_ref_output: fn(Output) -> Output, + modify_datum: fn(Datum) -> Datum) -> Bool { + let settings_policy_id = #"00000000000000000000000000000000000000000000000000000000" + let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000" + let pool_address = script_address(hash_of_pool_script) + let rberry_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" + let rberry_token_name = #"524245525259" + let user_address = + wallet_address(#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513") + let settings_input = mk_valid_settings_input([], 1) + + let funds_input = pool_test_tx_input() + let pool_id = pool_ident_from_input(funds_input) + let (new_pool_ref_token, new_pool_nft_token, new_pool_lp_token) = shared.pool_token_names(pool_id) + let inline_pool_datum = modify_datum(InlineDatum( + PoolDatum { + identifier: pool_id, + assets: ((#"", #""), (rberry_policy_id, rberry_token_name)), + circulating_lp: 1_000_000_000, + fees_per_10_thousand: (5, 5), + market_open: 0, + fee_finalized: 0, + protocol_fees: 2_000_000, + } + )) + let pool_output_val = + value.from_asset(rberry_policy_id, rberry_token_name, 1_000_000_000) + |> value.add(hash_of_pool_script, new_pool_nft_token, 1) + |> value.merge(value.from_lovelace(1_002_000_000)) + let pool_output = new_tx_output(pool_address, 0, inline_pool_datum) // 1_002_000_000 = 1_000_000_000 ADA for pool + 2_000_000 ADA for protocol_fees + |> add_asset_to_tx_output(pool_output_val) + |> modify_pool_output + + let lp_output_val = + value.from_asset(hash_of_pool_script, pool_lp_name(pool_id), 1_000_000_000) + |> value.merge(value.from_lovelace(2_000_000)) + let lp_output = + new_tx_output(user_address, 0, NoDatum) // we can probably get rid of the rider, it gets auto added + |> add_asset_to_tx_output(lp_output_val) + |> modify_lp_output + + let ref_output_val = + value.from_asset(hash_of_pool_script, new_pool_ref_token, 1) + |> value.merge(value.from_lovelace(2_000_000)) + let ref_output = + new_tx_output(user_address, 0, NoDatum) // we can probably get rid of the rider, it gets auto added + |> add_asset_to_tx_output(ref_output_val) + |> modify_ref_output + + let poolMintRedeemer = CreatePool { + assets: ((#"", #""), (rberry_policy_id, rberry_token_name)), + pool_output: 0, + metadata_output: 2, + } + + let ctx = interval.between(1,2) + |> build_txn_context() + |> mint_assets(hash_of_pool_script, value.to_minted_value( + value.from_lovelace(0) + |> value.add(hash_of_pool_script, new_pool_lp_token, 1_000_000_000) + |> value.add(hash_of_pool_script, new_pool_nft_token, 1) + |> value.add(hash_of_pool_script, new_pool_ref_token, 1) + )) + |> add_tx_input(funds_input) + |> add_tx_ref_input(settings_input) + + // these must be in reverse order like so, in order to get [pool_output, lp_output, ref_output] + |> add_tx_output(ref_output) + |> add_tx_output(lp_output) + |> add_tx_output(pool_output) + + let result = pool_validator.mint(settings_policy_id, poolMintRedeemer, ctx) + result +} + +test mint_test() { + mint_test_modify(identity, identity, identity, identity) +} + diff --git a/validators/tests/settings.ak b/validators/tests/settings.ak new file mode 100644 index 0000000..f72e7b8 --- /dev/null +++ b/validators/tests/settings.ak @@ -0,0 +1,95 @@ +use aiken/interval +use aiken/transaction.{InlineDatum, NoDatum, OutputReference, TransactionId} +use aiken/transaction/credential.{VerificationKeyCredential, Address, from_script} +use aiken/transaction/value +use sundae/multisig +use types/settings.{SettingsDatum, settings_nft_name} +use tx_util/builder.{ + build_txn_context, + mint_assets, + add_tx_input, + add_tx_output, + new_tx_output, + new_tx_input, + with_asset_of_tx_input, +} +use settings as settings_validator + +fn test_mint_settings(settings_nfts_count: Int) { + let settings_nft_policy = #"00" + + let settings_nft = value.to_minted_value(value.from_asset(settings_nft_policy, settings_nft_name, settings_nfts_count)) + + let settings_datum = mk_valid_settings_datum([]) // Some([]) for authorized_scoopers means no one can scoop + + let settings_output = new_tx_output( + from_script(settings_nft_policy), + 2_000_000, + InlineDatum(settings_datum) + ) + + let protocol_boot_utxo = OutputReference { transaction_id: TransactionId { hash: #"00"}, output_index: 0 } + let protocol_boot_utxo_policy = #"00" + + let protocol_boot_utxo_input = new_tx_input( + protocol_boot_utxo.transaction_id.hash, + from_script(protocol_boot_utxo_policy), + 2_000_000, + NoDatum, + ) |> with_asset_of_tx_input(value.from_asset(protocol_boot_utxo_policy, "boot utxo name", 1)) + let ctx = + interval.between(1, 2) + |> build_txn_context() + |> mint_assets(settings_nft_policy, settings_nft) + |> add_tx_input(protocol_boot_utxo_input) + |> add_tx_output(settings_output) + + + let minted = settings_validator.mint(protocol_boot_utxo, Void, ctx) + minted +} + +test mint_invalid_settings_multiple_nft() { + !test_mint_settings(2) +} + +test mint_valid_settings() { + test_mint_settings(1) +} + +fn mk_valid_settings_datum( + scoopers: List, +) -> SettingsDatum { + SettingsDatum { + settings_admin: multisig.Signature( + #"725011d2c296eb3341e159b6c5c6991de11e81062b95108c9aa024ad", + ), + metadata_admin: Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ), + treasury_admin: multisig.Signature( + #"725011d2c296eb3341e159b6c5c6991de11e81062b95108c9aa024ad", + ), + treasury_address: Address( + VerificationKeyCredential( + #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513", + ), + None, + ), + treasury_allowance: (1, 10), + authorized_scoopers: Some(scoopers), + authorized_staking_keys: [ + VerificationKeyCredential( + #"725011d2c296eb3341e159b6c5c6991de11e81062b95108c9aa024ad" + ), + ], + base_fee: 0, + simple_fee: 2_500_000, + strategy_fee: 5_000_000, + pool_creation_fee: 0, + extensions: Void, + } +} \ No newline at end of file