Skip to content

Commit d43f212

Browse files
Merge pull request #53 from SundaeSwap-finance/pi/SSW-001-pool-output-address
Resolve SSW-001 + SSW-002
2 parents dc9c994 + 8c4b421 commit d43f212

File tree

2 files changed

+89
-33
lines changed

2 files changed

+89
-33
lines changed

validators/pool.ak

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ validator(settings_policy_id: PolicyId) {
8585
expect InlineDatum(output_datum) = pool_output.datum
8686
expect output_datum: PoolDatum = output_datum
8787

88+
// Ensure that the pool output is to the same payment credential; This is critical, because it ensures that the pool NFT
89+
// or liquidity aren't paid to some other script in control of an attacker.
90+
// Note that we check the stake credential is correctly updated (or not) in the various redeemer cases below.
91+
// We also check that the pool output has the correct output, which ensures it contains the pool NFT,
92+
// meaning this can't just be a "token output" with the correct payment credential, but everything paid elsewhere.
93+
expect pool_output.address.payment_credential == ScriptCredential(pool_script_hash)
94+
8895
// Similarly, destructure the pool datum we found on the output, to access the fields we need to process the scoop
8996
let PoolDatum {
9097
identifier: actual_identifier,
@@ -283,7 +290,6 @@ validator(settings_policy_id: PolicyId) {
283290
// that worked with the void datum, and paid to the real treasury with the correct datum.
284291
// TODO: should we just let the treasury admin specify the datum on the redeemer? Or include it in the settings?
285292
expect treasury_output.datum == InlineDatum(Void)
286-
expect pool_output.address.payment_credential == pool_input.address.payment_credential
287293
expect list.any(settings_datum.authorized_staking_keys, fn(a) {
288294
pool_output.address.stake_credential == Some(Inline(a))
289295
})
@@ -437,6 +443,13 @@ validator(settings_policy_id: PolicyId) {
437443
pool_output_datum.market_open <= pool_output_datum.fee_finalized,
438444
}
439445

446+
// Make sure that the pool output is paid into own_policy_id (the pool script, remember this is a multivalidator)
447+
// and that one of the valid staking addresses is attached
448+
expect pool_output.address.payment_credential == ScriptCredential(own_policy_id)
449+
expect list.any(settings_datum.authorized_staking_keys, fn(a) {
450+
pool_output.address.stake_credential == Some(Inline(a))
451+
})
452+
440453
// And then check each of the conditions above as the condition for minting
441454
and {
442455
coin_pair_ordering_is_canonical,

validators/tests/pool.ak

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use aiken/transaction.{
99
ScriptContext, Spend, Transaction, TransactionId,
1010
}
1111
use aiken/transaction/credential.{
12-
Address, VerificationKeyCredential,
12+
Address, VerificationKeyCredential, from_verification_key, from_script, with_delegation_key
1313
}
1414
use aiken/transaction/value.{Value}
1515
use shared.{
@@ -35,6 +35,8 @@ type ScoopTestOptions {
3535
edit_escrow_destination: Option<Address>,
3636
edit_fee: Option<Value>,
3737
edit_swap_fees: Option<(Int,Int)>,
38+
edit_pool_input_address: Option<Address>,
39+
edit_pool_output_address: Option<Address>,
3840
edit_pool_output_value: Option<Value>,
3941
edit_settings_datum: Option<Datum>,
4042
}
@@ -46,6 +48,8 @@ fn default_scoop_test_options() -> ScoopTestOptions {
4648
edit_escrow_destination: None,
4749
edit_fee: None,
4850
edit_swap_fees: None,
51+
edit_pool_input_address: None,
52+
edit_pool_output_address: None,
4953
edit_pool_output_value: None,
5054
edit_settings_datum: None,
5155
}
@@ -101,8 +105,6 @@ test pool_validator_ignores_fee() {
101105
}
102106

103107
test scoop_high_swap_fees() {
104-
let hash_of_pool_script =
105-
#"00000000000000000000000000000000000000000000000000000000"
106108
let pool_id = #"00000000000000000000000000000000000000000000000000000000"
107109
let pool_nft_name = shared.pool_nft_name(pool_id)
108110
let dummy_policy_id =
@@ -128,7 +130,7 @@ test scoop_high_swap_fees() {
128130
dummy_asset_name,
129131
1_000_000_000 - ( 9_802_950 + 9_611_678 ),
130132
)
131-
|> value.add(hash_of_pool_script, pool_nft_name, 1),
133+
|> value.add(pool_script_hash, pool_nft_name, 1),
132134
),
133135
}
134136
scoop(options)
@@ -153,6 +155,35 @@ test output_missing_nft() fail {
153155
scoop(options)
154156
}
155157

158+
const pool_script_hash = #"00000000000000000000000000000000000000000000000000000000"
159+
const random_hash = #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513"
160+
const other_hash = #"01010101010101010101010101010101010101010101010101010101"
161+
test scoop_pool_output_wallet_address() fail {
162+
let options =
163+
ScoopTestOptions {
164+
..default_scoop_test_options(),
165+
edit_pool_output_address: Some(from_verification_key(random_hash)),
166+
}
167+
scoop(options)
168+
}
169+
test scoop_pool_output_wrong_script() fail {
170+
let options =
171+
ScoopTestOptions {
172+
..default_scoop_test_options(),
173+
edit_pool_output_address: Some(from_script(random_hash)),
174+
}
175+
scoop(options)
176+
}
177+
test scoop_pool_output_change_staking_credential() fail {
178+
let options =
179+
ScoopTestOptions {
180+
..default_scoop_test_options(),
181+
edit_pool_input_address: Some(from_script(pool_script_hash) |> with_delegation_key(random_hash)),
182+
edit_pool_output_address: Some(from_script(pool_script_hash) |> with_delegation_key(other_hash)),
183+
}
184+
scoop(options)
185+
}
186+
156187
test scooper_not_in_settings() fail {
157188
let somebody = #"11111111111111111111111111111111111111111111111111111111"
158189
let options =
@@ -197,8 +228,6 @@ fn scoop(options: ScoopTestOptions) {
197228
#"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"
198229
let dummy_asset_name = #"53554e444145"
199230
let scooper = #"00000000000000000000000000000000000000000000000000000000"
200-
let hash_of_pool_script =
201-
#"00000000000000000000000000000000000000000000000000000000"
202231
let hash_of_escrow_script =
203232
#"00000000000000000000000000000000000000000000000000000000"
204233
let user_addr =
@@ -229,15 +258,15 @@ fn scoop(options: ScoopTestOptions) {
229258
protocol_fees: 7_000_000,
230259
}
231260
let pool_nft_name = shared.pool_nft_name(pool_id)
232-
let pool_address = script_address(hash_of_pool_script)
261+
let pool_address = script_address(pool_script_hash)
233262
let pool_input =
234263
Input {
235264
output_reference: mk_output_reference(0),
236265
output: Output {
237-
address: pool_address,
266+
address: option.or_else(options.edit_pool_input_address, pool_address),
238267
value: value.from_lovelace(1_000_000_000 + 2_000_000)
239268
|> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000)
240-
|> value.add(hash_of_pool_script, pool_nft_name, 1),
269+
|> value.add(pool_script_hash, pool_nft_name, 1),
241270
datum: InlineDatum(pool_datum),
242271
reference_script: None,
243272
},
@@ -309,7 +338,7 @@ fn scoop(options: ScoopTestOptions) {
309338
}
310339
let pool_output =
311340
Output {
312-
address: pool_address,
341+
address: option.or_else(options.edit_pool_output_address, pool_address),
313342
value: option.or_else(
314343
options.edit_pool_output_value,
315344
value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000)
@@ -318,7 +347,7 @@ fn scoop(options: ScoopTestOptions) {
318347
dummy_asset_name,
319348
1_000_000_000 - ( 9_896_088 + 9_702_095 ),
320349
)
321-
|> value.add(hash_of_pool_script, pool_nft_name, 1),
350+
|> value.add(pool_script_hash, pool_nft_name, 1),
322351
),
323352
datum: InlineDatum(pool_out_datum),
324353
reference_script: None,
@@ -358,7 +387,6 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
358387
let dummy_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"
359388
let dummy_asset_name = #"53554e444145"
360389
let scooper = #"00000000000000000000000000000000000000000000000000000000"
361-
let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000"
362390
let hash_of_escrow_script = #"00000000000000000000000000000000000000000000000000000000"
363391
let user_addr = wallet_address(
364392
#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513"
@@ -392,14 +420,14 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
392420
protocol_fees: 7_000_000,
393421
}
394422
let pool_nft_name = shared.pool_nft_name(pool_id)
395-
let pool_address = script_address(hash_of_pool_script)
423+
let pool_address = script_address(pool_script_hash)
396424
let pool_input = Input {
397425
output_reference: mk_output_reference(0),
398426
output: Output {
399427
address: pool_address,
400428
value: value.from_lovelace(1_000_000_000 + 2_000_000)
401429
|> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000)
402-
|> value.add(hash_of_pool_script, pool_nft_name, 1),
430+
|> value.add(pool_script_hash, pool_nft_name, 1),
403431
datum: InlineDatum(pool_datum),
404432
reference_script: None,
405433
},
@@ -473,7 +501,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
473501
let escrow2_out = Output {
474502
address: option.or_else(options.edit_escrow_destination, user_addr),
475503
value: value.from_lovelace(2_000_000)
476-
|> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990)
504+
|> value.add(pool_script_hash, pool_lp_name(pool_id), 9_900_990)
477505
|> value.add(dummy_policy_id, dummy_asset_name, 196_990),
478506
datum: NoDatum,
479507
reference_script: None,
@@ -483,7 +511,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
483511
value: option.or_else(options.edit_pool_output_value,
484512
value.from_lovelace(1_000_000_000 + 20_000_000 + 5_000_000 + 2_000_000)
485513
|> value.add(dummy_policy_id, dummy_asset_name, 1_000_000_000 - 9_896_088 + 10_000_000 - 196_990)
486-
|> value.add(hash_of_pool_script, pool_nft_name, 1)),
514+
|> value.add(pool_script_hash, pool_nft_name, 1)),
487515
datum: InlineDatum(pool_out_datum),
488516
reference_script: None,
489517
}
@@ -496,7 +524,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
496524
fee: option.or_else(options.edit_fee, value.from_lovelace(1_000_000)),
497525
mint: value.to_minted_value(
498526
value.from_lovelace(0)
499-
|> value.add(hash_of_pool_script, pool_lp_name(pool_id), 9_900_990)
527+
|> value.add(pool_script_hash, pool_lp_name(pool_id), 9_900_990)
500528
),
501529
certificates: [],
502530
withdrawals: dict.new(),
@@ -520,7 +548,6 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
520548
result
521549
}
522550

523-
const hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000"
524551
fn pool_test_tx_input() -> Input {
525552
let funds_input =
526553
new_tx_input(
@@ -532,12 +559,12 @@ fn pool_test_tx_input() -> Input {
532559
funds_input
533560
}
534561

535-
test mint_test_two_nfts() {
562+
test mint_test_two_nfts() fail {
536563
let pool_id = pool_ident_from_input(pool_test_tx_input())
537564
let (_, new_pool_nft_token, _) = shared.pool_token_names(pool_id)
538565
// if we add on another pool NFT token to the pool output, it should fail
539-
!mint_test_modify(
540-
fn(output) { Output { ..output, value: value.add(output.value, hash_of_pool_script, new_pool_nft_token, 1) } },
566+
mint_test_modify(
567+
fn(output) { Output { ..output, value: value.add(output.value, pool_script_hash, new_pool_nft_token, 1) } },
541568
identity,
542569
identity,
543570
identity
@@ -559,8 +586,7 @@ fn mint_test_modify(
559586
modify_ref_output: fn(Output) -> Output,
560587
modify_datum: fn(Datum) -> Datum) -> Bool {
561588
let settings_policy_id = #"00000000000000000000000000000000000000000000000000000000"
562-
let hash_of_pool_script = #"00000000000000000000000000000000000000000000000000000000"
563-
let pool_address = script_address(hash_of_pool_script)
589+
let pool_address = script_address(pool_script_hash) |> with_delegation_key(#"725011d2c296eb3341e159b6c5c6991de11e81062b95108c9aa024ad")
564590
let rberry_policy_id = #"9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"
565591
let rberry_token_name = #"524245525259"
566592
let user_address =
@@ -583,41 +609,41 @@ fn mint_test_modify(
583609
))
584610
let pool_output_val =
585611
value.from_asset(rberry_policy_id, rberry_token_name, 1_000_000_000)
586-
|> value.add(hash_of_pool_script, new_pool_nft_token, 1)
612+
|> value.add(pool_script_hash, new_pool_nft_token, 1)
587613
|> value.merge(value.from_lovelace(1_002_000_000))
588614
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
589615
|> add_asset_to_tx_output(pool_output_val)
590616
|> modify_pool_output
591617

592618
let lp_output_val =
593-
value.from_asset(hash_of_pool_script, pool_lp_name(pool_id), 1_000_000_000)
619+
value.from_asset(pool_script_hash, pool_lp_name(pool_id), 1_000_000_000)
594620
|> value.merge(value.from_lovelace(2_000_000))
595621
let lp_output =
596622
new_tx_output(user_address, 0, NoDatum) // we can probably get rid of the rider, it gets auto added
597623
|> add_asset_to_tx_output(lp_output_val)
598624
|> modify_lp_output
599625

600626
let ref_output_val =
601-
value.from_asset(hash_of_pool_script, new_pool_ref_token, 1)
627+
value.from_asset(pool_script_hash, new_pool_ref_token, 1)
602628
|> value.merge(value.from_lovelace(2_000_000))
603629
let ref_output =
604630
new_tx_output(user_address, 0, NoDatum) // we can probably get rid of the rider, it gets auto added
605631
|> add_asset_to_tx_output(ref_output_val)
606632
|> modify_ref_output
607633

608-
let poolMintRedeemer = CreatePool {
634+
let pool_mint_redeemer = CreatePool {
609635
assets: ((#"", #""), (rberry_policy_id, rberry_token_name)),
610636
pool_output: 0,
611637
metadata_output: 2,
612638
}
613639

614640
let ctx = interval.between(1,2)
615641
|> build_txn_context()
616-
|> mint_assets(hash_of_pool_script, value.to_minted_value(
642+
|> mint_assets(pool_script_hash, value.to_minted_value(
617643
value.from_lovelace(0)
618-
|> value.add(hash_of_pool_script, new_pool_lp_token, 1_000_000_000)
619-
|> value.add(hash_of_pool_script, new_pool_nft_token, 1)
620-
|> value.add(hash_of_pool_script, new_pool_ref_token, 1)
644+
|> value.add(pool_script_hash, new_pool_lp_token, 1_000_000_000)
645+
|> value.add(pool_script_hash, new_pool_nft_token, 1)
646+
|> value.add(pool_script_hash, new_pool_ref_token, 1)
621647
))
622648
|> add_tx_input(funds_input)
623649
|> add_tx_ref_input(settings_input)
@@ -627,11 +653,28 @@ fn mint_test_modify(
627653
|> add_tx_output(lp_output)
628654
|> add_tx_output(pool_output)
629655

630-
let result = pool_validator.mint(settings_policy_id, poolMintRedeemer, ctx)
656+
let result = pool_validator.mint(settings_policy_id, pool_mint_redeemer, ctx)
631657
result
632658
}
633659

634660
test mint_test() {
635661
mint_test_modify(identity, identity, identity, identity)
636662
}
637663

664+
// make sure pool_output.address is checked to be the pool address
665+
test mint_test_wrong_address () fail {
666+
let minted = mint_test_modify(
667+
// change pool nft output address to destination that shouldn't be possible
668+
fn (output) {
669+
Output{
670+
..output,
671+
// TODO: move some of this stuff to constants?
672+
address: from_verification_key(random_hash) |> with_delegation_key(#"725011d2c296eb3341e159b6c5c6991de11e81062b95108c9aa024ad")
673+
}
674+
},
675+
identity,
676+
identity,
677+
identity
678+
)
679+
minted
680+
}

0 commit comments

Comments
 (0)