From c5ef920a7987c497321d28c906fe7c7a32af6337 Mon Sep 17 00:00:00 2001 From: AgustinBadi Date: Fri, 9 Feb 2024 01:31:51 -0300 Subject: [PATCH] First commit --- .github/workflows/tests.yml | 20 +++++ .gitignore | 6 ++ README.md | 55 ++++++++++++ aiken.lock | 15 ++++ aiken.toml | 14 +++ plutus.json | 167 ++++++++++++++++++++++++++++++++++++ validators/mastermind.ak | 145 +++++++++++++++++++++++++++++++ 7 files changed, 422 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 aiken.lock create mode 100644 aiken.toml create mode 100644 plutus.json create mode 100644 validators/mastermind.ak diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..8913685 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,20 @@ +name: Tests + +on: + push: + branches: ["main"] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: aiken-lang/setup-aiken@v0.1.0 + with: + version: v1 + + - run: aiken fmt --check + - run: aiken check -D + - run: aiken build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff7811b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Aiken compilation artifacts +artifacts/ +# Aiken's project working directory +build/ +# Aiken's default documentation export +docs/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..2851246 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# mastermind-aiken + +Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension. + +For example, as `validators/always_true.ak` + +```gleam +validator { + fn spend(_datum: Data, _redeemer: Data, _context: Data) -> Bool { + True + } +} +``` + +## Building + +```sh +aiken build +``` + +## Testing + +You can write tests in any module using the `test` keyword. For example: + +```gleam +test foo() { + 1 + 1 == 2 +} +``` + +To run all tests, simply do: + +```sh +aiken check +``` + +To run only tests matching the string `foo`, do: + +```sh +aiken check -m foo +``` + +## Documentation + +If you're writing a library, you might want to generate an HTML documentation for it. + +Use: + +```sh +aiken docs +``` + +## Resources + +Find more on the [Aiken's user manual](https://aiken-lang.org). diff --git a/aiken.lock b/aiken.lock new file mode 100644 index 0000000..0a72eb3 --- /dev/null +++ b/aiken.lock @@ -0,0 +1,15 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "1.7.0" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "1.7.0" +requirements = [] +source = "github" + +[etags] diff --git a/aiken.toml b/aiken.toml new file mode 100644 index 0000000..db9d377 --- /dev/null +++ b/aiken.toml @@ -0,0 +1,14 @@ +name = "ash/mastermind-aiken" +version = "0.0.0" +license = "Apache-2.0" +description = "Aiken contracts for project 'ash/mastermind-aiken'" + +[repository] +user = "ash" +project = "mastermind-aiken" +platform = "github" + +[[dependencies]] +name = "aiken-lang/stdlib" +version = "1.7.0" +source = "github" diff --git a/plutus.json b/plutus.json new file mode 100644 index 0000000..d6a0dce --- /dev/null +++ b/plutus.json @@ -0,0 +1,167 @@ +{ + "preamble": { + "title": "ash/mastermind-aiken", + "description": "Aiken contracts for project 'ash/mastermind-aiken'", + "version": "0.0.0", + "plutusVersion": "v2", + "compiler": { + "name": "Aiken", + "version": "v1.0.21-alpha+b6acdde" + }, + "license": "Apache-2.0" + }, + "validators": [ + { + "title": "mastermind.astermind", + "datum": { + "title": "dat", + "schema": { + "$ref": "#/definitions/mastermind~1GameDatum" + } + }, + "redeemer": { + "title": "rdm", + "schema": { + "$ref": "#/definitions/mastermind~1GameRedeemer" + } + }, + "compiledCode": "59066401000032323232323232323222232323232533300a32323232323232325333015301800213232323232323232533301d302000213232533301f30220021323232323232323253330243370e900218118008991919191919299981519b874800000c54ccc0a8cdc39bad300130280274800054ccc0a802054ccc0a8cdc399918008009129998178008a4000266e0120023300200230320013758605e606060606060605004e90040a99981519b87375a6002605000a900109980100f9bae301e302800514a029405280a501533302a3370e90010018a99981519b87337006eb4c004c0a009d2002375a6002605000a2a66605466e1ccdc31bad300130280274801120001533302a337126eb4c004c0a009d20141533302a00913300201f375c603c605004e29405280a5014a02a66605466e1d20040031533302a3370e66e00dd698009814013a40046eb4c004c0a001454ccc0a8cdc399b86375a6002605004e9002240042a66605466e24dd698009814013a40282a66605401226600403e6eb8c074c0a009c5280a5014a029404c94ccc0ad4ccc0accdc39bad30013029028480204cc00c080dd7180f98148140a5014a22a666056a66605666e20dd69800981481424010266e1cdd6980118148142402829404cc00c080dd7180f18148140a502303030313031303130310012302f3030303030303030303030300012232323300100100222533303000114a026464a66605e66e3c008018528899802002000981a0011bae30320013758605e606060606060606060606060606060606050004604c046603e002605400260440022c60186042014a66604466e3c04001854ccc088cdc7807002099b8700c3370400490020a5014a0a66604266e3c03c01454ccc084cdc7806801899b8700b00114a02940dd6981280098128011bae30230013023002375c60420022c6eb0c080004c02cdd59807180c0008b180f00099299980c99b8748008c0600044cc88c8cc00400400c894ccc08000452f5c026464a66603e6464a66604266e1d200200113371e00e6eb8c098c07c008528180f8009809180e9809180e80109981180119802002000899802002000981200118110009bac30023017300c3017014375c6018602e6018602e603c602e0022c6018602c0264603a603c603c0026eb4c06c004c06c008dd7180c800980c8011bae3017001163758602c00260026eacc010c038c010c0380088c8cc004004008894ccc05400452f5c0264666444646600200200644a66603600220062646603a6e9ccc074dd48031980e9ba9375c60340026603a6ea0dd6980d800a5eb80cc00c00cc07c008c074004dd7180a0009bab3015001330030033019002301700132533300f3370e900118070008991919299980919b8748000c0440044c05cc04000458c8c8cc004004008894ccc05c0045300103d87a80001323253330163375e6012602800400c266e9520003301a0024bd70099802002000980d801180c8009bac3004300f3004300f00c3015001300d001163002300c009230130012301230130013010001300800514984d958c94ccc028cdc3a40000022a66601a601000c2930b0a99980519b874800800454ccc034c020018526161533300a3370e90020008a99980698040030a4c2c2a66601466e1d20060011533300d300800614985858c020014c0040148c94ccc024cdc3a4000002264646464646464646464646464646464646464646464646464646464646464646464a66605c6062004264646464646464646464931981700511bad0013302d00b2323302f0012375a0026eb0004cc0b00308dd6800998158069191981680091bad00137580026605401c464660580024646605c00246eb4004dd60009bac0013302900f2323302b0012375a0026eb0004cc0a00408c8cc0a80048dd68009bac00133027011232330290012375a0026eb0004cc0980488dd68009981280d91bad001163758605e002605e0046eb0c0b4004c0b4008dd6181580098158011bac302900130290023758604e002604e0046eb0c094004c094008dd6181180098118011bac302100130210023758603e002603e0046eb4c074004c074008dd6980d800980d8011bad30190013019002375a602e002602e0046eb0c054004c054008dd6980980098098011bae30110013011002375c601e002600e0042c600e00244646600200200644a66601a00229309919801801980880118019807800918029baa001230033754002ae6955ceaab9e5573eae815d0aba201", + "hash": "7338c1ce2b56b281c5cfbcedcc68d3d8f4854d8b11599b71a111678d" + } + ], + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Int": { + "dataType": "integer" + }, + "List$Int": { + "dataType": "list", + "items": { + "$ref": "#/definitions/Int" + } + }, + "List$List$Int": { + "dataType": "list", + "items": { + "$ref": "#/definitions/List$Int" + } + }, + "List$List$List$Int": { + "dataType": "list", + "items": { + "$ref": "#/definitions/List$List$Int" + } + }, + "mastermind/GameDatum": { + "title": "GameDatum", + "anyOf": [ + { + "title": "GameDatum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "code_master", + "$ref": "#/definitions/ByteArray" + }, + { + "title": "code_breaker", + "$ref": "#/definitions/ByteArray" + }, + { + "title": "hash_solution", + "$ref": "#/definitions/Int" + }, + { + "title": "guess", + "$ref": "#/definitions/List$Int" + }, + { + "title": "black_pegs", + "$ref": "#/definitions/Int" + }, + { + "title": "white_pegs", + "$ref": "#/definitions/Int" + }, + { + "title": "current_turn", + "$ref": "#/definitions/Int" + }, + { + "title": "n_public", + "$ref": "#/definitions/Int" + }, + { + "title": "vk_alpha1", + "$ref": "#/definitions/List$Int" + }, + { + "title": "vk_beta2", + "$ref": "#/definitions/List$List$Int" + }, + { + "title": "vk_gamma2", + "$ref": "#/definitions/List$List$Int" + }, + { + "title": "vk_delta2", + "$ref": "#/definitions/List$List$Int" + }, + { + "title": "vk_alphabeta12", + "$ref": "#/definitions/List$List$List$Int" + }, + { + "title": "ic", + "$ref": "#/definitions/List$List$Int" + }, + { + "title": "pi_a", + "$ref": "#/definitions/List$Int" + }, + { + "title": "pi_b", + "$ref": "#/definitions/List$List$Int" + }, + { + "title": "pi_c", + "$ref": "#/definitions/List$Int" + } + ] + } + ] + }, + "mastermind/GameRedeemer": { + "title": "GameRedeemer", + "anyOf": [ + { + "title": "Start", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "Guess", + "dataType": "constructor", + "index": 1, + "fields": [] + }, + { + "title": "Clue", + "dataType": "constructor", + "index": 2, + "fields": [] + }, + { + "title": "End", + "dataType": "constructor", + "index": 3, + "fields": [] + } + ] + } + } +} \ No newline at end of file diff --git a/validators/mastermind.ak b/validators/mastermind.ak new file mode 100644 index 0000000..9db0b49 --- /dev/null +++ b/validators/mastermind.ak @@ -0,0 +1,145 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/list.{length} +use aiken/transaction.{ + InlineDatum, Input, Output, ScriptContext, Spend, Transaction, +} +use aiken/transaction/value.{flatten} + +type VerificationKey = + ByteArray + +type PubKeyHash = + Hash + +type ZeroVerificationKey { + n_public: Int, + vk_alpha1: List, + vk_beta2: List>, + vk_gamma2: List>, + vk_delta2: List>, + vk_alphabeta12: List>>, + ic: List>, +} + +type Proof { + pi_a: List, + pi_b: List>, + pi_c: List, +} + +type GameDatum { + code_master: PubKeyHash, + code_breaker: PubKeyHash, + hash_solution: Int, + guess: List, + black_pegs: Int, + white_pegs: Int, + current_turn: Int, + vk: ZeroVerificationKey, + proof: Proof, +} + +type GameRedeemer { + Start + Guess + Clue + End +} + +validator { + fn mastermind(dat: GameDatum, rdm: GameRedeemer, ctx: ScriptContext) -> Bool { + let ScriptContext { transaction, .. } = ctx + + let input_from_contract: Input = find_own_input(ctx) + expect [(policyid1, token_name1, amount1)] = + flatten(input_from_contract.output.value) + + expect [output_to_contract] = find_own_outputs(ctx) + expect [(policyid2, token_name2, amount2)] = + flatten(output_to_contract.value) + + // Check if the amount sent to the contract is equal the amount previously locked. + let is_equal: Bool = and { + policyid1 == policyid2, + token_name1 == token_name2, + amount1 == amount2, + } + + // Check if the amount sent to the contract is twice the amount initially locked. + let is_double: Bool = and { + policyid1 == policyid2, + token_name1 == token_name2, + amount1 == amount2 * 2, + } + + expect InlineDatum(out_datum) = output_to_contract.datum + expect new_datum: GameDatum = out_datum + + when rdm is { + Start -> and { + dat.current_turn == 0, + is_double, + length(dat.guess) == 4, + new_datum.current_turn == 1, + must_be_signed_by(transaction, new_datum.code_breaker), + } + + Guess -> and { + dat.current_turn + 1 == new_datum.current_turn, + dat.current_turn % 2 == 0, + // new_datum.current_turn % 2 == 0 ? + dat.current_turn <= 10, + // <10 ?? + is_equal, + must_be_signed_by(transaction, dat.code_breaker), + } + + Clue -> and { + dat.current_turn + 1 == new_datum.current_turn, + dat.current_turn % 2 == 1, + // new_datum.current_turn % 2 == 0 ? + dat.current_turn <= 10, + is_equal, + must_be_signed_by(transaction, dat.code_master), + } + + End -> or { + dat.black_pegs == 4 && must_be_signed_by( + transaction, + dat.code_breaker, + ), + dat.black_pegs < 4 && dat.current_turn == 10 && must_be_signed_by( + transaction, + dat.code_master, + ), + } + } + } +} + +// Helper functions + +// Get input from the script. +fn find_own_input(ctx: ScriptContext) -> Input { + expect Spend(output_ref) = ctx.purpose + + expect Some(input) = + ctx.transaction.inputs + |> transaction.find_input(output_ref) + input +} + +// Get outputs to the script. +fn find_own_outputs(ctx: ScriptContext) -> List { + expect Spend(output_ref) = ctx.purpose + + let own_outputs = + ctx.transaction.outputs + |> transaction.find_script_outputs(output_ref.transaction_id.hash) + own_outputs +} + +// Check if transaction is signed by... +fn must_be_signed_by(transaction: Transaction, vk: PubKeyHash) { + list.has(transaction.extra_signatories, vk) +}