Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion lib/calculation/process.ak
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,36 @@ use cardano/assets.{AssetName, PolicyId, Value, ada_policy_id}
use cardano/transaction.{
InlineDatum, Input, NoDatum, Output, OutputReference, ValidityRange,
}
use shared.{Ident, datum_of, is_script, pool_lp_name}
use shared.{AssetClass, Ident, datum_of, is_script, pool_lp_name}
use sundae/multisig
use types/order.{Destination, Fixed, Order, OrderDatum, SignedStrategyExecution}
use types/pool.{StablePoolDatum}

pub fn pool_input_to_asset_reserves(
assets: (AssetClass, AssetClass),
protocol_fees: (Int, Int, Int),
input: Output,
continuation: fn(Int, Int) -> Bool,
) -> Bool {
let (asset_a, asset_b) = assets
let (asset_a_policy_id, asset_a_name) = asset_a
let (asset_b_policy_id, asset_b_name) = asset_b
// If asset_a is ADA, then we need to not consider the protocol fees as part of this
// We don't have to check asset_b, because assets are guaranteed to be in lexicographical order.
let (fees_ada, fees_a, fees_b) = protocol_fees
let reserve_a =
assets.quantity_of(input.value, asset_a_policy_id, asset_a_name) - fees_a
let reserve_b =
assets.quantity_of(input.value, asset_b_policy_id, asset_b_name) - fees_b
let reserve_a =
if asset_a_policy_id == ada_policy_id {
reserve_a - fees_ada
} else {
reserve_a
}
continuation(reserve_a, reserve_b)
}

/// Construct the initial pool state for processing a set of orders
pub fn pool_input_to_state(
pool_token_policy: PolicyId,
Expand Down
1 change: 1 addition & 0 deletions lib/tests/examples/ex_pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn mk_pool_datum() -> StablePoolDatum {
protocol_fees: (10000000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/types/pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub type StablePoolDatum {
/// Specifically, the scooper calculates it off-chain, and provides the new invariant,
/// and the contract enforces that the invariant satisfies the correct formula.
sum_invariant: Int,
// An optional multisig condition under which the linear amplification factor can be updated
linear_amplification_manager: Option<multisig.MultisigScript>,
}

/// A pool UTXO can be spent for two purposes:
Expand Down Expand Up @@ -110,4 +112,6 @@ pub type ManageRedeemer {
}
// Update the percentage fee the pool charges
UpdatePoolFees { pool_input: Int }
// Update the linear amplification factor of the pool
AdjustLinearAmplification { pool_input: Int }
}
1 change: 1 addition & 0 deletions validators/oracle.ak
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ fn mint_oracle(
protocol_fees: (2_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
},
),
reference_script: None,
Expand Down
78 changes: 75 additions & 3 deletions validators/pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use aiken/collection/pairs
use aiken/crypto.{ScriptHash}
use aiken/interval
use aiken/primitive/bytearray
use calculation/process.{pool_input_to_state, process_orders}
use calculation/process.{
pool_input_to_asset_reserves, pool_input_to_state, process_orders,
}
use calculation/stableswap.{liquidity_invariant}
use cardano/address.{Address, Inline, Script}
use cardano/assets.{AssetName, PolicyId, Value, ada_policy_id}
use cardano/script_context.{ScriptContext}
Expand All @@ -17,8 +20,9 @@ use shared.{
}
use sundae/multisig
use types/pool.{
BurnPool, CreatePool, Manage, ManageRedeemer, MintLP, PoolMintRedeemer,
PoolRedeemer, PoolScoop, StablePoolDatum, UpdatePoolFees, WithdrawFees,
AdjustLinearAmplification, BurnPool, CreatePool, Manage, ManageRedeemer,
MintLP, PoolMintRedeemer, PoolRedeemer, PoolScoop, StablePoolDatum,
UpdatePoolFees, WithdrawFees,
} as types_pool
use types/settings.{SettingsDatum, find_settings_datum}

Expand Down Expand Up @@ -336,6 +340,7 @@ validator pool(
when redeemer is {
UpdatePoolFees { pool_input } -> pool_input
WithdrawFees { pool_input, .. } -> pool_input
AdjustLinearAmplification { pool_input } -> pool_input
}
let input_index = own_input_index(transaction, out_ref)
// Manage redeemer must have the correct index of this pool input
Expand Down Expand Up @@ -963,6 +968,73 @@ validator manage(settings_policy_id: PolicyId) {
withdrawals,
)

// And make sure we don't touch the assets on the pool input; they must be spent back into the same script
and {
pool_output_address == pool_input.address,
pool_output_value == pool_input.value,
}
}
AdjustLinearAmplification { pool_input } -> {
// Find the pool input; note that we don't look for the pool NFT here, because if someone
// spends with an unauthenticated UTXO, it will fail the spend script; and if someone
// spends with a different script address, this script can't do anything fishy,
// just enforces some things about the outputs
// This is duplicated code with the other branch, but only because we don't have pool_input yet
expect Some(pool_input) = list.at(inputs, pool_input)
let pool_input = pool_input.output
expect InlineDatum(datum) = pool_input.datum
expect datum: StablePoolDatum = datum
// We need the pool output to check that only the fees or fee manager are updated
let (
Output { address: pool_output_address, value: pool_output_value, .. },
pool_output_datum,
) = find_pool_output(outputs)

let StablePoolDatum {
assets,
protocol_fees,
linear_amplification,
sum_invariant,
linear_amplification_manager: output_linear_amplification_manager,
..
} = pool_output_datum

let
pool_quantity_a,
pool_quantity_b,
<- pool_input_to_asset_reserves(assets, protocol_fees, pool_input)

expect
liquidity_invariant(
pool_quantity_a,
pool_quantity_b,
linear_amplification,
sum_invariant,
)

let expected_datum =
StablePoolDatum {
..datum,
linear_amplification: linear_amplification,
sum_invariant: sum_invariant,
linear_amplification_manager: output_linear_amplification_manager,
}
expect pool_output_datum == expected_datum

// Check that the *current* fee manager approves the update
expect Some(linear_amplification_manager) =
datum.linear_amplification_manager
expect
multisig.satisfied(
linear_amplification_manager,
extra_signatories,
validity_range,
withdrawals,
)

// Make sure the linear amplification is in the sane range
expect linear_amplification > 0 && linear_amplification <= 10000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the upper bound likely needs to be much higher, and i'm not sure we should even have an upper bound 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, will remove that upper bound

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just make sure to ask the auditors if they feel we should set an upper bound


// And make sure we don't touch the assets on the pool input; they must be spent back into the same script
and {
pool_output_address == pool_input.address,
Expand Down
10 changes: 10 additions & 0 deletions validators/tests/pool.ak
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ fn scoop(options: ScoopTestOptions) {
options.edit_initial_sum_invariant,
2000000000000000000000,
),
linear_amplification_manager: None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add one test exercising this branch, ideally

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this as test bed for a property based test in the next PR, so will be covered by that.

}
let pool_out_datum =
StablePoolDatum {
Expand All @@ -394,6 +395,7 @@ fn scoop(options: ScoopTestOptions) {
protocol_fees: (7_000_000, 0, 0),
linear_amplification: 200,
sum_invariant: sum_invariants.2nd,
linear_amplification_manager: None,
}
let pool_nft_name = shared.pool_nft_name(constants.pool_ident)
let pool_address = script_address(constants.pool_script_hash)
Expand Down Expand Up @@ -606,6 +608,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
market_open: 0,
protocol_fees: (2_000_000, 0, 0),
linear_amplification: 200,
linear_amplification_manager: None,
sum_invariant: option.or_else(
options.edit_initial_sum_invariant,
2000000000000000000000,
Expand All @@ -625,6 +628,7 @@ fn scoop_swap_deposit(options: ScoopTestOptions) {
market_open: 0,
protocol_fees: (7_000_000, 0, 0),
linear_amplification: 200,
linear_amplification_manager: None,
sum_invariant: sum_invariants.2nd,
}
let pool_nft_name = shared.pool_nft_name(constants.pool_ident)
Expand Down Expand Up @@ -848,6 +852,7 @@ fn withdraw_fees_transaction(
protocol_fees,
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
let normal_input =
new_tx_input(
Expand Down Expand Up @@ -1097,6 +1102,7 @@ fn update_pool_fees_transaction(options: ScoopTestOptions) {
protocol_fees: (2_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
let pool_rider = 2_000_000
// pool_test_tx_input deduplicate?
Expand Down Expand Up @@ -1305,6 +1311,7 @@ fn mint_test_modify(
protocol_fees: (2_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
},
),
)
Expand Down Expand Up @@ -1606,6 +1613,7 @@ fn evaporate_pool_tx(options: ScoopTestOptions, withdraw_flat: Int) {
protocol_fees: (18_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
// pool_test_tx_input deduplicate?
let pool_input =
Expand Down Expand Up @@ -1695,6 +1703,7 @@ test attempt_evaporate_pool_test() {
protocol_fees: (18_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
// pool_test_tx_input deduplicate?
let pool_input =
Expand Down Expand Up @@ -1766,6 +1775,7 @@ test burn_pool() {
protocol_fees: (2_000_000, 0, 0),
linear_amplification: 10,
sum_invariant: 10,
linear_amplification_manager: None,
}
let pool_nft_name = shared.pool_nft_name(constants.pool_ident)
let pool_address = script_address(constants.pool_script_hash)
Expand Down
Loading