Skip to content

Commit

Permalink
Merge pull request #2 from Anastasia-Labs/feature/merkelized-validators
Browse files Browse the repository at this point in the history
Implement Merkelized Validators Pattern
  • Loading branch information
colll78 authored Mar 25, 2024
2 parents d5c7182 + ba4e30c commit 69660c6
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 3 deletions.
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* [Multi UTxO Indexer](#multi-utxo-indexer)
* [Transaction Level Validator Minting Policy](#transaction-level-validator-minting-policy)
* [Validity Range Normalization](#validity-range-normalization)
* [Merkelized Validator](#merkelized-validator)

<!-- vim-markdown-toc -->

Expand All @@ -34,6 +35,7 @@ aiken package add anastasia-labs/aiken-design-patterns --version main
And you'll be able to import functions of various patterns:

```rs
use aiken_design_patterns/merkelized_validator as merkelized_validator
use aiken_design_patterns/multi_utxo_indexer as multi_utxo_indexer
use aiken_design_patterns/singular_utxo_indexer as singular_utxo_indexer
use aiken_design_patterns/stake_validator as stake_validator
Expand All @@ -46,14 +48,14 @@ Check out `validators/` to see how the exposed functions can be used.

Here are the steps to compile and run the included tests:

1.Clone the repo and navigate inside:
1. Clone the repo and navigate inside:

```bash
git clone https://github.com/Anastasia-Labs/aiken-design-patterns
cd aiken-design-patterns
```

2.Run the build command, which both compiles all the functions/examples and
2. Run the build command, which both compiles all the functions/examples and
also runs the included unit tests:

```sh
Expand Down Expand Up @@ -172,3 +174,45 @@ pub type NormalizedTimeRange {

The exposed function of the module (`normalize_time_range`), takes a
`ValidityRange` and returns this custom datatype.

### Merkelized Validator

Since transaction size is limited in Cardano, some validators benefit from a
solution which allows them to delegate parts of their logics. This becomes more
prominent in cases where such logics can greatly benefit from optimization
solutions that trade computation resources for script sizes (e.g. table
lookups can take up more space so that costly computations can be averted).

This design pattern offers an interface for off-loading such logics into an
external withdrawal script, so that the size of the validator itself can stay
within the limits of Cardano.

> [!NOTE]
> While currently the sizes of reference scripts are essentially irrelevant,
> they'll soon impose additional fees.
> See [here](https://github.com/IntersectMBO/cardano-ledger/issues/3952) for
> more info.

The exposed `spend` function from `merkelized_validator` expects 3 arguments:
1. The hash of the withdrawal validator that performs the computation.
2. The list of arguments expected by the underlying logic.
3. The `Dict` of all redeemers within the current script context.

This function expects to find the given stake validator in the `redeemers` list,
such that its redeemer is of type `WithdrawRedeemer` (which carries the list of
input arguments and the list of expected outputs), makes sure provided inputs
match the one's given to the validator through its redeemer, and returns the
outputs (which are carried inside the withdrawal redeemer) so that you can
safely use them.

For defining a withdrawal logic that carries out the computation, use the
exposed `withdraw` function. It expects 3 arguments:
1. The computation itself. It has to take a list of generic inputs, and return
a list of generic outputs.
2. A redeemer of type `WithdrawRedeemer<a, b>`. Note that `a` is the type of
input arguments, and `b` is the type of output arguments.
3. The script context.

It validates that the puropse is withdrawal, and that given the list of inputs,
the provided function yields identical outputs as the ones provided via the
redeemer.
37 changes: 37 additions & 0 deletions lib/aiken-design-patterns/merkelized-validator.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use aiken/dict.{Dict}
use aiken/hash.{Blake2b_224, Hash}
use aiken/transaction.{Redeemer, ScriptContext, ScriptPurpose, WithdrawFrom}
use aiken/transaction/credential.{Inline, Script, ScriptCredential}

pub type WithdrawRedeemer<a, b> {
input_args: List<a>,
results: List<b>,
}

pub fn spend(
staking_validator: Hash<Blake2b_224, Script>,
function_args: List<Data>,
redeemers: Dict<ScriptPurpose, Redeemer>,
) -> List<Data> {
expect Some(rdmr) =
redeemers
|> dict.get(WithdrawFrom(Inline(ScriptCredential(staking_validator))))
expect WithdrawRedeemer { input_args, results }: WithdrawRedeemer<Data, Data> =
rdmr

// Given input arguments must be identical to the ones provided to the
// withdrawal validator.
expect (input_args == function_args)?
results
}

pub fn withdraw(
function: fn(List<a>) -> List<b>,
redeemer: WithdrawRedeemer<a, b>,
ctx: ScriptContext,
) -> Bool {
expect ScriptContext { purpose: WithdrawFrom(_), .. } = ctx
let WithdrawRedeemer { input_args, results } = redeemer
let computed_results = function(input_args)
results == computed_results
}
12 changes: 11 additions & 1 deletion lib/aiken-design-patterns/tests.ak
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use aiken/hash.{blake2b_256}
use aiken/transaction.{InlineDatum, Output}
use aiken/transaction/credential.{Address, ScriptCredential}
use aiken/transaction/value
use aiken_design_patterns/utils.{authentic_input_is_reproduced_unchanged}
use aiken_design_patterns/utils.{
authentic_input_is_reproduced_unchanged, sum_of_squares,
}

fn test_224_01() {
bytearray.take(blake2b_256(#"01"), 28)
Expand Down Expand Up @@ -54,3 +56,11 @@ test unauthentic_utxo_reproduced() fail {
test_utxo_02(),
)
}

test sum_of_squares_test_ok() {
sum_of_squares([100, 20, 3, 4, 5]) == [10450]
}

test sum_of_squares_test_fail() fail {
sum_of_squares([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) == [1]
}
5 changes: 5 additions & 0 deletions lib/aiken-design-patterns/utils.ak
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use aiken/list.{foldl}
use aiken/transaction.{Output}
use aiken/transaction/value.{AssetName, PolicyId}

Expand Down Expand Up @@ -41,3 +42,7 @@ pub fn authentic_input_is_reproduced_unchanged(
in_quantity == 1,
}
}

pub fn sum_of_squares(xs: List<Int>) -> List<Int> {
[xs |> foldl(0, fn(x, acc) { acc + x * x })]
}
23 changes: 23 additions & 0 deletions validators/merkelized-validator-example.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use aiken/hash.{Blake2b_224, Hash}
use aiken/transaction.{ScriptContext, Transaction}
use aiken/transaction/credential.{Script}
use aiken_design_patterns/merkelized_validator.{WithdrawRedeemer} as merkelized_validator
use aiken_design_patterns/utils.{sum_of_squares}

validator(stake_validator: Hash<Blake2b_224, Script>) {
fn spend(x: Int, y: Int, ctx: ScriptContext) {
let ScriptContext { transaction: tx, .. } = ctx
let xData: Data = x
let yData: Data = y
expect [sumData] =
merkelized_validator.spend(stake_validator, [xData, yData], tx.redeemers)
expect sum: Int = sumData
sum < 42
}
}

validator {
fn withdraw(redeemer: WithdrawRedeemer<Int, Int>, ctx: ScriptContext) {
merkelized_validator.withdraw(sum_of_squares, redeemer, ctx)
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 69660c6

Please sign in to comment.