diff --git a/README.md b/README.md index 8682993..4c539cb 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,22 @@ A library of specialized Aiken functions for smart contracts on Cardano. -It extends the default library with routines that will assist in quick development. \ No newline at end of file +It extends the default library with routines that will assist in quick development. + +## Using the library + +Add this to the dependency seciton of the `aiken.toml` file: + +``` +[[dependencies]] +name = "logicalmechanism/assist" +version = "main" +source = "github" +``` + +Import the module like: + +```aiken +use assist/signing +use assist/count +``` \ No newline at end of file diff --git a/lib/assist/addresses.ak b/lib/assist/addresses.ak index 7ff544c..181a6ed 100644 --- a/lib/assist/addresses.ak +++ b/lib/assist/addresses.ak @@ -13,8 +13,8 @@ use aiken/transaction/credential.{ /// credential. An empty sc means enterpise address by default. /// /// ```aiken -/// create_address(#"acab", #"") -/// create_address(datum.wallet.pkh, datum.wallet.sc) +/// addresses.create_address(#"acab", #"") +/// addresses.create_address(datum.wallet.pkh, datum.wallet.sc) /// ``` pub fn create_address( pkh: Hash, @@ -52,8 +52,8 @@ test base_wallet() { /// assumed to be not staked. /// /// ```aiken -/// create_script_address(#"acab", #"") -/// create_script_address(ref_datum.sale_script.pkh, ref_datum.sale_script.sc) +/// addresses.create_script_address(#"acab", #"") +/// addresses.create_script_address(ref_datum.sale_script.pkh, ref_datum.sale_script.sc) /// ``` pub fn create_script_address( pkh: Hash, diff --git a/lib/assist/count.ak b/lib/assist/count.ak index 9908b44..57234c3 100644 --- a/lib/assist/count.ak +++ b/lib/assist/count.ak @@ -6,14 +6,15 @@ use aiken/transaction.{Input, Output} use aiken/transaction/credential.{Address} use assist/addresses +// for testing only use tests/fake_tx /// Verify that the number of inputs from a specific script is equal the amount /// intended in the contract. The amount must be exact with the counter. /// /// ```aiken -/// script_inputs(tx.inputs, this_script_addr, 1) -/// script_inputs(tx.inputs, that_script_addr, 2) +/// count.script_inputs(tx.inputs, this_script_addr, 1) +/// count.script_inputs(tx.inputs, that_script_addr, 2) /// ``` pub fn script_inputs( inputs: List, @@ -68,8 +69,8 @@ test multiple_script_inputs() { /// intended in the contract. The amount must be exact with the counter. /// /// ```aiken -/// script_outputs(tx.outputs, this_script_addr, 1) -/// script_outputs(tx.outputs, that_script_addr, 2) +/// count.script_outputs(tx.outputs, this_script_addr, 1) +/// count.script_outputs(tx.outputs, that_script_addr, 2) /// ``` pub fn script_outputs( outputs: List, diff --git a/lib/assist/payout.ak b/lib/assist/payout.ak new file mode 100644 index 0000000..c87c63f --- /dev/null +++ b/lib/assist/payout.ak @@ -0,0 +1,167 @@ +//// This module includes code to help with payout logic. +//// + +use aiken/transaction.{Output} +use aiken/transaction/credential.{Address} +use aiken/transaction/value.{AssetName, PolicyId, Value} +use assist/addresses +// for testing only +use tests/fake_tx + +/// Find the first occurrence of an exact output that matches a specific +/// address and value. If nothing is found then return False. +/// +/// ```aiken +/// payout.find_exact(wallet_addr, validating_value, tx.outputs) +/// ``` +pub fn find_exact( + pay_address: Address, + pay_value: Value, + outputs: List, +) -> Bool { + when outputs is { + [output, ..rest] -> + // exact address and value + if output.address == pay_address && output.value == pay_value { + True + } else { + find_exact(pay_address, pay_value, rest) + } + [] -> + False + } +} + +test find_exact_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"face", #"") + let val = + value.from_lovelace(40) + find_exact(addr, val, tx.outputs) == True +} + +test missing_exact_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"acab", #"") + let val = + value.from_lovelace(40) + find_exact(addr, val, tx.outputs) == False +} + +/// Find the first occurrence of an output that contains at least some specific +/// value at some address. If nothing is found then return False. This function +/// does not search for an exact UTxO match. +/// +/// ```aiken +/// payout.find_token(wallet_addr, just_token_value, tx.outputs) +/// ``` +pub fn find_token( + pay_address: Address, + pay_value: Value, + outputs: List, +) -> Bool { + let flattened_value = + value.flatten(pay_value) + when outputs is { + [output, ..rest] -> + if + output.address == pay_address && check_all_assets( + output.value, + flattened_value, + ) == True{ + + True + } else { + find_token(pay_address, pay_value, rest) + } + [] -> + False + } +} + +// given a list of tokens check if at least the quantity is in the value. +fn check_all_assets( + out_value: Value, + flattened_value: List<(PolicyId, AssetName, Int)>, +) -> Bool { + when flattened_value is { + [(policy, token_name, quantity), ..rest] -> + if value.quantity_of(out_value, policy, token_name) >= quantity { + check_all_assets(out_value, rest) + } else { + False + } + // All the tokens exist + [] -> + True + } +} + +test find_token_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"acab", #"") + let val = + value.from_asset(#"acab", #"beef", 40) + find_token(addr, val, tx.outputs) == True +} + +test find_just_enough_token_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"acab", #"") + let val = + value.from_asset(#"acab", #"beef", 20) + find_token(addr, val, tx.outputs) == True +} + +test missing_token_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"acab", #"") + let val = + value.from_asset(#"acab", #"beef", 60) + find_token(addr, val, tx.outputs) == False +} + +/// Find the first occurrence of an output at some address. If nothing is +/// found then return False. This function does not search by value. +/// +/// ```aiken +/// payout.find_cont(that_script_addr, tx.outputs) +/// ``` +pub fn find_cont(pay_address: Address, outputs: List) -> Bool { + when outputs is { + [output, ..rest] -> + if output.address == pay_address { + True + } else { + find_cont(pay_address, rest) + } + [] -> + False + } +} + +test find_cont_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"acab", #"") + find_cont(addr, tx.outputs) == True +} + +test missing_cont_payout() { + let tx = + fake_tx.test_tx() + let addr = + addresses.create_address(#"cafe", #"") + find_cont(addr, tx.outputs) == False +} diff --git a/lib/tests/fake_tx.ak b/lib/tests/fake_tx.ak index ea02795..dcf2c43 100644 --- a/lib/tests/fake_tx.ak +++ b/lib/tests/fake_tx.ak @@ -6,6 +6,7 @@ use aiken/transaction.{ use aiken/transaction/value use assist/addresses +/// A fake transaction used for testing. pub fn test_tx() -> Transaction { let tx = Transaction {