diff --git a/.gitignore b/.gitignore index ff7811b..be35dd5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ artifacts/ build/ # Aiken's default documentation export docs/ + +test.json \ No newline at end of file diff --git a/lib/aiken-content-ownership/diagnostic.ak b/lib/aiken-content-ownership/diagnostic.ak new file mode 100644 index 0000000..d4a1241 --- /dev/null +++ b/lib/aiken-content-ownership/diagnostic.ak @@ -0,0 +1,7 @@ +use aiken/cbor.{serialise} +use aiken_content_ownership/placeholder.{mock_utxo_ref} + +test diag() { + // serialise(mock_utxo_ref(0)) == #"d8799fd8799f58205a077cbcdffb88b104f292aacb9687ce93e2191e103a30a0cc5505c18b719f98ff00ff" + serialise(mock_utxo_ref(1)) == #"d8799fd8799f58205a077cbcdffb88b104f292aacb9687ce93e2191e103a30a0cc5505c18b719f98ff01ff" +} diff --git a/lib/aiken-content-ownership/placeholder.ak b/lib/aiken-content-ownership/placeholder.ak new file mode 100644 index 0000000..a044d12 --- /dev/null +++ b/lib/aiken-content-ownership/placeholder.ak @@ -0,0 +1,32 @@ +use aiken/transaction.{NoDatum, Output, OutputReference, TransactionId} +use aiken/transaction/credential.{Address, VerificationKeyCredential} +use aiken/transaction/value.{PolicyId, from_lovelace} + +pub fn mock_policy_id() -> PolicyId { + #"1c1b7afe8affbee1505cf3ec5a58bd2734d4ffdfcc9b9f059625bd76" +} + +pub fn mock_policy_id_2() -> PolicyId { + #"1c1b7afe8affbee1505cf3ec5a58bd2734d4ffdfcc9b9f059625bd77" +} + +pub fn mock_utxo_ref(output_index: Int) -> OutputReference { + OutputReference { + transaction_id: TransactionId( + #"5a077cbcdffb88b104f292aacb9687ce93e2191e103a30a0cc5505c18b719f98", + ), + output_index, + } +} + +pub fn mock_output() -> Output { + Output { + address: Address { + payment_credential: VerificationKeyCredential(""), + stake_credential: None, + }, + value: from_lovelace(1000000), + datum: NoDatum, + reference_script: None, + } +} diff --git a/plutus.json b/plutus.json index 944883a..7018743 100644 --- a/plutus.json +++ b/plutus.json @@ -23,18 +23,59 @@ { "title": "utxo_ref", "schema": { - "$ref": "#/definitions/ByteArray" + "$ref": "#/definitions/aiken~1transaction~1OutputReference" } } ], - "compiledCode": "58ff010000323232323232323232232223253330083232323232533300d3370e9000000899299980719b87480000044c8c8c8c94ccc048cdc3a400000229445281808000991980080080111299980a8008a60103d87a80001323253330143371e0206eb8c8c004c04cc004c04cc004c04c00c8c0680044cdd2a40006603000497ae01330040040013019002301700137586028002601800a2944c030020528180580098080009808001180700098030010a4c26cac64a66601066e1d20000011533300b300600314985854ccc020cdc3a40040022a666016600c0062930b0b18030011bae001230053754002460066ea80055cd2ab9d5573caae7d5d02ba157441", - "hash": "b457d64c34c592b7722c0c72a742246d750dca684a41e918b159a03a" + "compiledCode": "5902020100003232323232323232322223253330073232323232533300c3370e9000000899191919299980819b87480000084c8c8c8c94ccc050cdc3a400000229445281809000991980080080111299980b8008a60103d87a80001323253330163375e026600e6028004266e9520003301a0024bd70099802002000980d801180c8009bac3016001300e008132323300100100222533301600114a226464a66602aa66602a66e3cdd71803001004099b88375a60346036603600490000a5113300400400114a060340046eb0c060004c8c8cc004004008894ccc05800452f5c0264666444646600200200644a6660380022006264666444660426e9ccc084dd4804998109ba900333021375000497ae0001375c60360026eb4c070004cc00c00cc080008c078004dd7180a8009bab301600133003003301a002301800132323300100100222533301600114bd6f7b630099191919299980b99b8f488100002100313301b337606ea4008dd3000998030030019bab3018003375c602c004603400460300026eacc054c058c058c058c058c0380208c054004c034028dd7180900098050010a50300a001300f001300f002300d001300500214984d958c94ccc01ccdc3a40000022a666014600a0062930b0a99980399b874800800454ccc028c01400c52616163005002230053754002460066ea80055cd2ab9d5573caae7d5d02ba157441", + "hash": "be3105aa3627cda80601b9f9d14ffec737fccdaf92c3771c4d013641" } ], "definitions": { "ByteArray": { "dataType": "bytes" }, + "Int": { + "dataType": "integer" + }, + "aiken/transaction/OutputReference": { + "title": "OutputReference", + "description": "An `OutputReference` is a unique reference to an output on-chain. The `output_index`\n corresponds to the position in the output list of the transaction (identified by its id)\n that produced that output", + "anyOf": [ + { + "title": "OutputReference", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "transaction_id", + "$ref": "#/definitions/aiken~1transaction~1TransactionId" + }, + { + "title": "output_index", + "$ref": "#/definitions/Int" + } + ] + } + ] + }, + "aiken/transaction/TransactionId": { + "title": "TransactionId", + "description": "A unique transaction identifier, as the hash of a transaction body. Note that the transaction id\n isn't a direct hash of the `Transaction` as visible on-chain. Rather, they correspond to hash\n digests of transaction body as they are serialized on the network.", + "anyOf": [ + { + "title": "TransactionId", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "hash", + "$ref": "#/definitions/ByteArray" + } + ] + } + ] + }, "one_time_minting_policy/Redeemer": { "title": "Redeemer", "anyOf": [ diff --git a/validators/one_time_minting_policy.ak b/validators/one_time_minting_policy.ak index a5debe5..9a20a30 100644 --- a/validators/one_time_minting_policy.ak +++ b/validators/one_time_minting_policy.ak @@ -1,22 +1,30 @@ use aiken/list -use aiken/transaction.{Input, Mint, ScriptContext, Transaction} +use aiken/transaction.{ + Input, Mint, OutputReference, ScriptContext, Transaction, placeholder, +} +use aiken/transaction/value.{ + PolicyId, add, flatten, from_asset, from_minted_value, to_minted_value, +} +use aiken_content_ownership/placeholder.{ + mock_output, mock_policy_id, mock_policy_id_2, mock_utxo_ref, +} type Redeemer { RMint RBurn } -validator(utxo_ref: ByteArray) { +validator(utxo_ref: OutputReference) { fn one_time_minting_policy(redeemer: Redeemer, context: ScriptContext) -> Bool { let ScriptContext { purpose, transaction } = context when purpose is { - Mint(_) -> + Mint(current_policy) -> when redeemer is { RMint -> { let Transaction { inputs, .. } = transaction let hash_equal = fn(input: Input) { - let hash = input.output_reference.transaction_id.hash + let hash = input.output_reference utxo_ref == hash } let target_input_exist = list.find(inputs, hash_equal) @@ -25,9 +33,104 @@ validator(utxo_ref: ByteArray) { None -> False } } - RBurn -> True + RBurn -> { + let burn_value = flatten(from_minted_value(transaction.mint)) + list.all( + burn_value, + fn(x) { + if x.1st == current_policy { + x.3rd < 0 + } else { + True + } + }, + ) + } } _ -> False } } } + +test success_mint() { + let redeemer = RMint + let input_utxo = mock_utxo_ref(1) + let policy_id: PolicyId = + #"1c1b7afe8affbee1505cf3ec5a58bd2734d4ffdfcc9b9f059625bd76" + + let tx = + Transaction { + ..placeholder(), + mint: to_minted_value(from_asset(policy_id, "", 1)), + inputs: [Input { output_reference: input_utxo, output: mock_output() }], + } + let ctx = ScriptContext { purpose: Mint(policy_id), transaction: tx } + + one_time_minting_policy(input_utxo, redeemer, ctx) +} + +test fail_mint_no_utxo_ref_supply() { + let redeemer = RMint + let policy_id = mock_policy_id() + + let ctx = + ScriptContext { purpose: Mint(policy_id), transaction: placeholder() } + + !one_time_minting_policy(mock_utxo_ref(0), redeemer, ctx) +} + +test success_burn() { + let redeemer = RBurn + let policy_id = mock_policy_id() + + let tx = + Transaction { + ..placeholder(), + mint: to_minted_value(from_asset(policy_id, "", -1)), + } + let ctx = ScriptContext { purpose: Mint(policy_id), transaction: tx } + one_time_minting_policy(mock_utxo_ref(0), redeemer, ctx) +} + +test success_burn_with_other_minting() { + let redeemer = RBurn + let policy_id = mock_policy_id() + + let tx = + Transaction { + ..placeholder(), + mint: to_minted_value( + from_asset(policy_id, "", -1) |> add(mock_policy_id_2(), "", 1), + ), + } + let ctx = ScriptContext { purpose: Mint(policy_id), transaction: tx } + one_time_minting_policy(mock_utxo_ref(0), redeemer, ctx) +} + +test fail_burn_with_mint() { + let redeemer = RBurn + let policy_id = mock_policy_id() + + let tx = + Transaction { + ..placeholder(), + mint: to_minted_value(from_asset(policy_id, "", 1)), + } + let ctx = ScriptContext { purpose: Mint(policy_id), transaction: tx } + !one_time_minting_policy(mock_utxo_ref(0), redeemer, ctx) +} + +test fail_burn_with_mix() { + let redeemer = RBurn + let policy_id = mock_policy_id() + + let tx = + Transaction { + ..placeholder(), + mint: to_minted_value( + from_asset(policy_id, "", -1) |> add(policy_id, "1", 1), + ), + } + let ctx = ScriptContext { purpose: Mint(policy_id), transaction: tx } + !one_time_minting_policy(mock_utxo_ref(0), redeemer, ctx) +}