Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into pi/SSW-306-efficient-…
Browse files Browse the repository at this point in the history
…sqrt
  • Loading branch information
Quantumplation committed Mar 5, 2024
2 parents 91c1330 + db3d33e commit 15f443c
Show file tree
Hide file tree
Showing 10 changed files with 1,035 additions and 706 deletions.
30 changes: 28 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,33 @@ jobs:

- uses: aiken-lang/[email protected]
with:
version: v1.0.20-alpha
version: v1.0.24-alpha

- run: aiken check
- run: |
# Run the tests
set -o pipefail
RESULT=0
aiken check 2>&1 | tee aiken.log || RESULT=$?
if [ $RESULT -ne 0 ]; then
{
echo 'FAILING_TESTS<<EOF'
grep "FAIL" aiken.log
echo EOF
} >> "$GITHUB_ENV"
cat $GITHUB_ENV
exit $RESULT
fi
- if: failure()
run: |
echo "$FAILING_TESTS"
- run: aiken build
- uses: actions/github-script@v6
if: failure() && env.FAILING_TESTS != ''
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Tests failed:\n\n\`\`\`\n${{ env.FAILING_TESTS }}\n\`\`\``
})
11 changes: 11 additions & 0 deletions aiken.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -23,4 +28,10 @@ version = "ae0852d40cc6332437492102451cf331a3c10b0d"
requirements = []
source = "github"

[[packages]]
name = "aiken-extra/tx_util"
version = "1.170.202312"
requirements = []
source = "github"

[etags]
5 changes: 5 additions & 0 deletions aiken.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
78 changes: 11 additions & 67 deletions lib/calculation/donation.ak
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use aiken/transaction.{NoDatum, Output}
use aiken/transaction/credential.{Address, VerificationKeyCredential}
use aiken/transaction.{Output}
use aiken/transaction/value.{Value, ada_policy_id, ada_asset_name}
use calculation/shared.{PoolState} as calc_shared
use shared.{SingletonValue}
use sundae/multisig
use types/order.{Destination, OrderDatum}
use types/order.{Destination}

/// 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
Expand All @@ -29,19 +27,19 @@ 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 ((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
expect assets.1st.1st == pool_state.quantity_a.1st
expect assets.1st.2nd == pool_state.quantity_a.2nd
expect assets.2nd.1st == pool_state.quantity_b.1st
expect assets.2nd.2nd == pool_state.quantity_b.2nd
expect asset_a_policy_id == pool_state.quantity_a.1st
expect asset_a_asset_name == pool_state.quantity_a.2nd
expect asset_b_policy_id == pool_state.quantity_b.1st
expect asset_b_asset_name == pool_state.quantity_b.2nd
// Compute however much of the UTXO value is *left over* after deducting the donation amount from it; If nonzero, this will need to be returned to the user
let remainder =
shared.to_value(assets.1st)
|> value.merge(shared.to_value(assets.2nd))
|> value.add(ada_policy_id, ada_asset_name, actual_protocol_fee)
|> value.negate
|> value.merge(input_value)
input_value
|> value.add(ada_policy_id, ada_asset_name, -actual_protocol_fee)
|> value.add(asset_a_policy_id, asset_a_asset_name, -asset_a_qty)
|> value.add(asset_b_policy_id, asset_b_asset_name, -asset_b_qty)

let has_remainder = remainder != value.zero()
// If we have a remainder, then we need to check the details of the output; this awkward structure
Expand Down Expand Up @@ -76,57 +74,3 @@ pub fn do_donation(
has_remainder,
)
}

test donation() {
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, 1_000_000),
(rberry.1st, rberry.2nd, 1_000_000),
)
let order =
OrderDatum {
pool_ident: None,
owner: multisig.Signature(
#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513",
),
max_protocol_fee: 2_500_000,
destination: Destination { address: addr, datum: NoDatum },
details: order.Donation {
assets: assets,
},
extension: Void,
}
// There's no remainder so do_donation totally ignores this Output record
let output =
Output {
address: addr,
value: value.from_lovelace(999_999_999_999_999_999),
datum: NoDatum,
reference_script: None,
}
let (final_pool_state, has_remainder) =
do_donation(pool_state, input_value, 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
}
83 changes: 72 additions & 11 deletions lib/shared.ak
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use aiken/transaction.{
use aiken/transaction/credential.{Credential, ScriptCredential}
use aiken/transaction/value.{AssetName, PolicyId}

use tests/examples/ex_shared.{
script_address, wallet_address, mk_output_reference
}

/// An alias type for the pool identifier, to make it slightly easier to reason about
pub type Ident = ByteArray

Expand Down Expand Up @@ -112,20 +116,20 @@ pub fn is_script(credential: Credential) -> Bool {
/// without knowing the exact script address they will use
/// This is quite subtle, so see the note above
pub fn count_orders(tx_inputs: List<Input>) -> Int {
// Can be done as a simple fold
list.foldl(
tx_inputs,
// Note: by starting at -exact_non_order_script_inputs, it's the same as if we subtracted this at the end,
// but saves one mathematical operation.
-exact_non_order_script_inputs,
fn(input, total) {
when tx_inputs is {
// Note: by using -exact_non_order_script_inputs for the base case,
// it's equivalent to subtracting at the end
[] -> -exact_non_order_script_inputs
[input, ..rest] ->
// The reason this is important is twofold:
// - We count them in the first place to ensure that we process every order by the end of the transaction
// - and we compare the script this way so that if we implement a new order script, with additional logic,
// and the correct datum format, it can be processed the pool just fine
total + if is_script(input.output.address.payment_credential) { 1 } else { 0 }
},
)
when input.output.address.payment_credential is {
ScriptCredential(_) -> count_orders(rest) + 1
_ -> count_orders(rest)
}
}
}

// Taken from unmerged PR: https://github.com/aiken-lang/stdlib/pull/73/files
Expand Down Expand Up @@ -205,4 +209,61 @@ pub fn pool_token_names(pool_ident: Ident) {
pool_nft_name(pool_ident),
pool_lp_name(pool_ident),
)
}
}

// Test for count_orders, with 16 inputs of which 10 are orders.
test count_orders_test() {
let hash_of_pool_script =
#"00000000000000000000000000000000000000000000000000000000"
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(0),
datum: NoDatum,
reference_script: None,
},
}

let hash_of_escrow_script =
#"00000000000000000000000000000000000000000000000000000000"
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(0),
datum: NoDatum,
reference_script: None,
},
}
let escrow2_in = Input {
output_reference: mk_output_reference(3),
output: Output {
address: escrow_address,
value: value.from_lovelace(0),
datum: NoDatum,
reference_script: None,
},
}
let other_address = wallet_address(#"fede0000000000000000000000000000000000000000000000000000")
let other_in = Input {
output_reference: mk_output_reference(4),
output: Output {
address: other_address,
value: value.from_lovelace(0),
datum: NoDatum,
reference_script: None,
},
}

let inputs = [
pool_input, other_in, escrow1_in, escrow2_in, other_in, escrow1_in,
escrow2_in, other_in, escrow1_in, escrow2_in, other_in, escrow1_in,
escrow2_in, other_in, escrow1_in, escrow2_in
]

count_orders(inputs) == 10
}
61 changes: 61 additions & 0 deletions lib/tests/aiken/donation.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use aiken/transaction.{NoDatum, 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}

test donation() {
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, 1_000_000),
(rberry.1st, rberry.2nd, 1_000_000),
)
let order =
OrderDatum {
pool_ident: None,
owner: multisig.Signature(
#"6af53ff4f054348ad825c692dd9db8f1760a8e0eacf9af9f99306513",
),
max_protocol_fee: 2_500_000,
destination: Destination { address: addr, datum: NoDatum },
details: order.Donation {
assets: assets,
},
extension: Void,
}
// There's no remainder so do_donation totally ignores this Output record
let output =
Output {
address: addr,
value: value.from_lovelace(999_999_999_999_999_999),
datum: NoDatum,
reference_script: None,
}
let (final_pool_state, has_remainder) =
do_donation(pool_state, input_value, 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
}
Loading

0 comments on commit 15f443c

Please sign in to comment.