Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into rrruko/ssw-310-deposi…
Browse files Browse the repository at this point in the history
…t-formula-simplification
  • Loading branch information
Quantumplation committed Apr 11, 2024
2 parents 298f858 + a890bde commit 392c2c2
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 110 deletions.
9 changes: 9 additions & 0 deletions lib/shared.ak
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,12 @@ test count_orders_test() {
pub fn oracle_sft_name() {
"oracle"
}

pub fn fees_in_legal_range(fees: (Int, Int)) {
and {
fees.1st >= 0,
fees.2nd >= 0,
fees.1st <= 10000,
fees.2nd <= 10000,
}
}
16 changes: 15 additions & 1 deletion lib/tests/examples/ex_shared.ak
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use aiken/bytearray
use aiken/cbor
use aiken/transaction.{OutputReference, TransactionId}
use aiken/transaction/credential.{Address, ScriptCredential, VerificationKeyCredential}
use aiken/transaction/credential.{Address, Inline, StakeCredential, ScriptCredential, VerificationKeyCredential}

pub const examples_enabled: Int = 1
pub fn print_example(d: Data) -> Bool {
Expand Down Expand Up @@ -34,3 +34,17 @@ pub fn script_address(hash: ByteArray) -> Address {
pub fn wallet_address(hash: ByteArray) -> Address {
Address(VerificationKeyCredential(hash), None)
}

pub fn compare_stake(left: StakeCredential, right: StakeCredential) -> Ordering {
let left = when left is {
Inline(ScriptCredential(x)) -> x
Inline(VerificationKeyCredential(x)) -> x
_ -> fail
}
let right = when right is {
Inline(ScriptCredential(x)) -> x
Inline(VerificationKeyCredential(x)) -> x
_ -> fail
}
bytearray.compare(left, right)
}
2 changes: 2 additions & 0 deletions lib/types/pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ pub type PoolMintRedeemer {
/// This is safe because we validate that the token is paid to the metadata admin
metadata_output: Int
}
/// to burn the pool NFT (when permitted by the spending validator)
BurnPool { identifier: Ident }
}
90 changes: 28 additions & 62 deletions plutus.json

Large diffs are not rendered by default.

24 changes: 10 additions & 14 deletions validators/order.ak
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use types/order.{Cancel, OrderDatum, OrderRedeemer, Scoop}
/// That stake_script then checks that a pool NFT is present on the UTXO, where most of the subtle and important logic is implemented.
validator(stake_script_hash: Hash<Blake2b_224, Script>) {
// For the purposes of spending the order, we don't care what the datum is, so avoid deserializing it
fn spend(datum: Data, redeemer: OrderRedeemer, ctx: ScriptContext) -> Bool {
pub fn spend(datum: Data, redeemer: OrderRedeemer, ctx: ScriptContext) -> Bool {
when redeemer is {
Cancel -> {
// We only expect the datum here, to avoid any related costs in the other branch, which doesn't need it
Expand All @@ -41,19 +41,15 @@ validator(stake_script_hash: Hash<Blake2b_224, Script>) {
)
}
Scoop -> {
// Assume that the stake_script_hash is the first withdrawal in the list
// Since the ledger doesn't reorder withdrawals (TODO: confirm this!!)
// If we ever have other withdrawals (for example, additional constraints enforced by a different order)
// then that script can look for the appropriate one, and we can just ensure this one is first in the list.
expect [head] = dict.to_list(ctx.transaction.withdrawals)
when head is {
// We match against the script hash like this to ignore the withdrawal amount
// TODO: we could make this more efficient by CBOR encoding the `Inline(ScriptCredential())` into the `stake_script_hash` parameter
// or perhaps even the whole withdrawal list to compare it all at once!
// and just doing a direct equaltiy comparison. Since this runs for each order, this could represent significant savings!
(Inline(ScriptCredential(script)), _) -> script == stake_script_hash
_ -> False
}
dict.foldl(ctx.transaction.withdrawals, False, fn(withdrawal, _amt, acc) {
when withdrawal is {
// TODO: we could make this more efficient by CBOR encoding the `Inline(ScriptCredential())` into the `stake_script_hash` parameter
// or perhaps even the whole withdrawal list to compare it all at once!
// and just doing a direct equaltiy comparison. Since this runs for each order, this could represent significant savings!
Inline(ScriptCredential(script)) -> acc || script == stake_script_hash
_ -> acc
}
})
}
}
}
Expand Down
24 changes: 15 additions & 9 deletions validators/pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use shared.{AssetClass, Ident, spent_output, pool_nft_name, pool_lp_name, count_
use sundae/multisig
use types/pool.{
CreatePool, MintLP, PoolDatum, PoolMintRedeemer, PoolRedeemer, PoolScoop,
WithdrawFees, UpdatePoolFees,
WithdrawFees, UpdatePoolFees, BurnPool,
}
use types/settings.{SettingsDatum, find_settings_datum}
/// The core / base "pooled AMM" script for the SundaeSwap v3 protocol
Expand Down Expand Up @@ -331,6 +331,9 @@ validator(settings_policy_id: PolicyId) {
..
} = pool_output_datum

expect shared.fees_in_legal_range(bid_fees_per_10_thousand)
expect shared.fees_in_legal_range(ask_fees_per_10_thousand)

let expected_datum = PoolDatum {
..datum,
bid_fees_per_10_thousand,
Expand Down Expand Up @@ -496,14 +499,8 @@ validator(settings_policy_id: PolicyId) {
pool_output_datum.assets == (asset_a, asset_b),
pool_output_datum.circulating_lp == initial_lq,
pool_output_datum.market_open <= pool_output_datum.fee_finalized,
pool_output_datum.bid_fees_per_10_thousand.1st >= 0,
pool_output_datum.bid_fees_per_10_thousand.2nd >= 0,
pool_output_datum.bid_fees_per_10_thousand.1st <= 10000,
pool_output_datum.bid_fees_per_10_thousand.2nd <= 10000,
pool_output_datum.ask_fees_per_10_thousand.1st >= 0,
pool_output_datum.ask_fees_per_10_thousand.2nd >= 0,
pool_output_datum.ask_fees_per_10_thousand.1st <= 10000,
pool_output_datum.ask_fees_per_10_thousand.2nd <= 10000,
shared.fees_in_legal_range(pool_output_datum.bid_fees_per_10_thousand),
shared.fees_in_legal_range(pool_output_datum.ask_fees_per_10_thousand),
}

// Make sure that the pool output is paid into own_policy_id (the pool script, remember this is a multivalidator)
Expand Down Expand Up @@ -545,6 +542,15 @@ validator(settings_policy_id: PolicyId) {
(ctx.transaction.mint |> value.from_minted_value |> value.quantity_of(own_policy_id, pool_nft_name)) == 0,
}
}
BurnPool(pool_ident) -> {
// Burning an asset is only possible when spending it, so if we enforce
// that the mints consist of exactly 1 burn for the specified pool NFT
// then we can defer to the pool spending validator
expect Mint(own_policy_id) = ctx.purpose
let pool_nft_name = shared.pool_nft_name(pool_ident)
let expected_mint = shared.to_value((own_policy_id, pool_nft_name, -1))
value.from_minted_value(ctx.transaction.mint) == expected_mint
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion validators/tests/constants.ak
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Script hashes
pub const settings_policy_id = #"00000000000000000000000000000000000000000000000000000000"
pub const pool_script_hash = #"00000000000000000000000000000000000000000000000000000001"
pub const order_script_hash = #"00000000000000000000000000000000000000000000000000000002"
pub const order_script_hash = #"00000000000000000000000000000000000000000000000000000002"
pub const stake_script_hash = #"00000000000000000000000000000000000000000000000000000003"

pub const random_hash = #"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513"
pub const other_hash = #"01010101010101010101010101010101010101010101010101010101"
Expand Down
86 changes: 86 additions & 0 deletions validators/tests/order.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use aiken/dict.{Dict}
use aiken/interval
use aiken/transaction.{
InlineDatum, Input, Output,
ScriptContext, Spend, Transaction,
}
use aiken/transaction/credential.{
Inline, StakeCredential, ScriptCredential,
}
use aiken/transaction/value
use tests/examples/ex_shared.{
mk_output_reference, mk_tx_hash, script_address, compare_stake,
}
use order as order_validator
use tests/constants
use types/order.{Scoop}

test scoop_order_test() {
scoop_order(
dict.from_list(
[(Inline(ScriptCredential(constants.stake_script_hash)), 0)],
compare_stake,
),
)
}

test scoop_order_extra_withdrawals() {
scoop_order(
dict.from_list(
[
(Inline(ScriptCredential(constants.random_hash)), 100),
(Inline(ScriptCredential(constants.other_hash)), 500),
(Inline(ScriptCredential(constants.stake_script_hash)), 0),
],
compare_stake,
),
)
}

test scoop_order_missing_stake_script_withdrawal() fail {
scoop_order(
dict.from_list(
[
(Inline(ScriptCredential(constants.random_hash)), 100),
(Inline(ScriptCredential(constants.other_hash)), 500),
],
compare_stake,
),
)
}

fn scoop_order(withdrawals: Dict<StakeCredential, Int>) {
let order_address = script_address(constants.order_script_hash)
let order_datum = Void // Not needed by scoop
let order_redeemer = Scoop
let order_input =
Input {
output_reference: mk_output_reference(1),
output: Output {
address: order_address,
value: value.from_lovelace(2_000_000),
datum: InlineDatum(order_datum),
reference_script: None,
},
}
let ctx =
ScriptContext {
transaction: Transaction {
inputs: [order_input],
reference_inputs: [],
outputs: [],
fee: value.from_lovelace(1_000_000),
mint: value.to_minted_value(value.from_lovelace(0)),
certificates: [],
withdrawals: withdrawals,
validity_range: interval.between(1, 2),
extra_signatories: [],
redeemers: dict.new(),
datums: dict.new(),
id: mk_tx_hash(1),
},
purpose: Spend(order_input.output_reference),
}
let result = order_validator.spend(constants.stake_script_hash, order_datum, order_redeemer, ctx)
result
}
Loading

0 comments on commit 392c2c2

Please sign in to comment.