# Aiken Common Design Patterns(常見設計模式)
> Reference:https://aiken-lang.org/fundamentals/common-design-patterns
[Aiken 學習目錄](/e7DvPvg8RSWhcTK3wnfaJA)
[上一章內容 Cardano eUTxO](/tXPeGQjWSq-NRaAX6xMPeA)
隨著Plutus從V1發展到V2,現在又到了V3,許多設計模式已完善且開始被使用了。本文件旨在成為這些設計模式和實踐方法的非詳盡參考。
## Enforcing Uniqueness(強化獨特性)
強制執行政策、資產名稱或新的輸出的唯一性在許多情境中都很有用。
### "One-Shot" Minting Policies(「一次性」鑄幣政策)
驗證器(validator)使用`OutputReference`作為參數,鑄幣驗證器確保交易的輸入中包含相應的UTxO。這樣的機制確保鑄幣政策只會且僅會驗證一次(因為根據定義,一個未花費的交易輸出只能被花費一次)。在某些設計中,此邏輯僅用於部分redeemers,以允許更靈活的鑄幣政策。
>1.驗證器參數化:驗證器可以使用 OutputReference 進行參數化。這意味著在鑄幣驗證過程中,驗證器會檢查交易的輸入是否包含對應的 UTxO(未花費的交易輸出)。
2.確保唯一性:透過要求交易中包含特定的 UTxO 作為輸入,鑄幣驗證器保證鑄幣政策只會被驗證一次,因為每個 UTxO 只能被花費一次,這是 UTxO 模型的基本特性。
3.靈活的鑄幣政策:在一些設計中,此邏輯僅應用於部分 redeemers(消耗特定 UTxO 時附帶的用戶輸入),以允許更靈活的鑄幣政策。這樣一來,可以控制哪些特定的鑄幣操作需要滿足此唯一性要求,而其他操作則可具有更靈活的行為。
讓我們來看一個例子🌰。
NFT(非同質化代幣)可以通過一次性鑄幣政策來創建,此政策確保每個鑄造的代幣值都通過交易輸入中的特定UTxO來驗證。該鑄幣政策使用`OutputReference`作為驗證器參數,以確認交易花費了該UTxO。此外,此政策保證僅鑄造一個代幣,確保了NFT的唯一性。
首先將`OutputReference`定義為參數,並根據redeemer提供的值來設定操作類型以鑄造或銷毀代幣
>1.定義 OutputReference:首先將 OutputReference 定義為驗證器的參數,該參數指定了交易中要使用的特定 UTxO。
2.基於 Redeemer 的值來設定行動類型:
•行動類型:設置行動類型為鑄造或銷毀代幣(mint 或 burn)。
•依據 Redeemer 決定:此操作類型取決於 redeemer 中提供的值。Redeemer 是交易附帶的輸入數據,指定了此次操作應該進行鑄造還是銷毀。
```aiken=
use cardano/transaction.{OutputReference, Transaction}
use cardano/assets.{PolicyId}
pub type Action {
Minting
Burning
}
validator one_shot(utxo_ref: OutputReference) {
mint(redeemer: Action, policy_id: PolicyId, self: Transaction) {
todo @"mint and burn"
}
}
```
驗證器必須處理鑄造/銷毀操作並確保只有一個資產被鑄造,否則將會失敗
```aiken {1, 12-17}
use aiken/collection/dict
use cardano/transaction.{OutputReference, Transaction}
use cardano/assets.{PolicyId}
pub type Action {
Minting
Burning
}
validator one_shot(utxo_ref: OutputReference) {
mint(redeemer: Action, policy_id: PolicyId, self: Transaction) {
// It checks that only one minted asset exists and will fail otherwise
expect [Pair(_asset_name, quantity)] = self.mint
|> assets.tokens(policy_id)
|> dict.to_pairs()
todo @"Check if output is consumed"
}
}
```
為了確保唯一性,我們需要確保在驗證器參數中定義的`OutputReference`對應的 UTxO 有被消耗。這是因為每個`OutputReference`是一個由交易ID和輸出索引整數組成的唯一組合。請務必記住,交易ID是一個`Hash<Blake2b_256, Transaction>`,也是唯一標識(identifier),不再重複。
>1.OutputReference 的唯一性:每個 OutputReference 是一組由 交易 ID(Transaction ID) 和 輸出索引整數(Output Index Integer) 組成的唯一組合。這確保了每個 UTxO 都是唯一的,因為交易 ID 和輸出索引的組合不會重複。
2.交易 ID 作為唯一標識符:交易 ID 是一個基於 Blake2b_256 演算法的哈希值(Hash<Blake2b_256, Transaction>),這個哈希值是交易的唯一標識符。在整個區塊鏈中,交易 ID 是唯一的,因此不會重複。
3.消耗 UTxO 確保唯一性:由於 UTxO 模型的特性,每個 UTxO 只能被消耗一次,因此當參數中的特定 OutputReference 對應的 UTxO 被消耗後,就無法再次使用。這樣的機制就可以在驗證過程中強制確保操作的唯一性。
```aiken {2, 18-25}
use aiken/collection/dict
use aiken/collection/list
use cardano/transaction.{OutputReference, Transaction}
use cardano/assets.{PolicyId}
pub type Action {
Minting
Burning
}
validator one_shot(utxo_ref: OutputReference) {
mint(redeemer: Action, policy_id: PolicyId, self: Transaction) {
// It checks that only one minted asset exists and will fail otherwise
expect [Pair(_asset_name, quantity)] = self.mint
|> assets.tokens(policy_id)
|> dict.to_pairs()
let Transaction{inputs, mint , ..} = self
// Check if the specified UTxO reference (utxo_ref) is consumed by any input
let is_output_consumed = list.any(inputs, fn(input) { input.output_reference == utxo_ref })
when redeemer is {
Minting ->
is_output_consumed? && (quantity == 1)?
Burning -> (quantity == -1)? // No need to check if output is consumed for burning
}
}
}
1.cardano aiken validator one_shot(utxo_ref: OutputReference) {
•定義一個 “one_shot” 驗證器,參數 utxo_ref 是一個 UTxO(未花費交易輸出)的參考,用於指定哪個 UTxO 被消耗。
2.mint(redeemer: Action, policy_id: PolicyId, self: Transaction) {
•定義 mint 函數,接受三個參數:
•redeemer:一個 Action 類型,用於指示當前操作是 “Minting”(鑄造)還是 “Burning”(銷毀)。
•policy_id:一個 PolicyId,標識當前使用的政策。
•self:當前的交易物件。
3.expect [Pair(_asset_name, quantity)] = self.mint |> assets.tokens(policy_id) |> dict.to_pairs()
•檢查交易中鑄造的資產數量是否僅有一個,如果不符合條件則驗證失敗。
•self.mint 取得鑄造的資產數據。
•assets.tokens(policy_id) 會根據 policy_id 過濾出符合此政策的資產。
•dict.to_pairs() 將資產資料轉換成鍵值對的列表形式。
•expect [Pair(_asset_name, quantity)] 檢查列表中僅包含一個資產名稱和數量配對(quantity 表示資產數量)。
4.let Transaction{inputs, mint , ..} = self
•將當前的交易解構為多個部分,其中包括 inputs(輸入列表)和 mint(鑄造的資產數量)。
5.let is_output_consumed = list.any(inputs, fn(input) { input.output_reference == utxo_ref })
•定義變數 is_output_consumed,判斷 inputs 列表中是否包含指定的 utxo_ref,即是否消耗了該 UTxO。
•使用 list.any 函數遍歷 inputs,並檢查每個 input 的 output_reference 是否等於 utxo_ref。
6.when redeemer is {
•根據 redeemer 的值來進行不同的判斷,可能的值包括 Minting 或 Burning。
7.Minting -> is_output_consumed? && (quantity == 1)?
•當 redeemer 為 Minting(鑄造)時:
•驗證指定的 UTxO 是否已被消耗(is_output_consumed?)。
•驗證鑄造的數量是否等於 1(quantity == 1)。
•只有這兩個條件同時成立,驗證器才會通過。
8.Burning -> (quantity == -1)?
•當 redeemer 為 Burning(銷毀)時:
•驗證銷毀的數量是否為 -1(quantity == -1)。
•對於銷毀操作,不需要檢查 UTxO 是否被消耗。
總結來說,這段代碼的邏輯是:
•在鑄造資產時,必須消耗特定的 UTxO,並且鑄造的資產數量必須為 1。
•在銷毀資產時,僅需確保銷毀數量為 -1。
```
## Receipts(收據)
驗證器可以透過要求資產名稱是特定於要進行驗證的交易的任何唯一值來為交易鑄造唯一的收據。換句話說,如果我們確保每個交易只鑄造一個收據,我們可以使用`blake2b_256`和`cbor.serialise`來獲取一個唯一值,該值可以分配給來自第一個`Input`的`OutputReference`的收據所需的`AssetName`我們的交易輸入。
```aiken
use aiken/builtin.{blake2b_256}
use aiken/cbor
use aiken/collection/dict
use aiken/collection/list
use cardano/assets.{PolicyId}
use cardano/transaction.{Transaction}
validator receipt {
// This validator expects a minting transaction
mint(_r: Data, policy_id: PolicyId, self: Transaction) {
let Transaction { inputs, mint, .. } = self
// Select the first input and concatenate its output reference and index to
// generate the expected token name
expect Some(first_input) = list.at(inputs, 0)
expect [Pair(asset_name, quantity)] =
mint |> assets.tokens(policy_id) |> dict.to_pairs()
let expected_token_name =
first_input.output_reference
|> cbor.serialise
|> blake2b_256
// Compare the asset name with the first utxo output reference
asset_name == expected_token_name && quantity == 1
}
// The validator will fail if the transaction is not for minting.
else(_) {
fail
}
}
1.validator receipt {
•定義了一個名為 “receipt” 的驗證器,用於處理鑄造交易的驗證。
2.mint(_r: Data, policy_id: PolicyId, self: Transaction) {
•定義 mint 函數,表示該驗證器專門處理鑄造交易。接受三個參數:
•_r:數據類型 Data(未使用,但必須作為參數傳入)。
•policy_id:一個 PolicyId,用於識別資產政策。
•self:當前交易 Transaction 對象。
3.let Transaction { inputs, mint, .. } = self
•將 self 交易解構為 inputs(交易的輸入列表)和 mint(鑄造的資產數據)。
4.expect Some(first_input) = list.at(inputs, 0)
•使用 list.at 函數獲取 inputs 列表中的第一個輸入,並期望其為 Some(first_input)。如果沒有找到第一個輸入,驗證失敗。
5.expect [Pair(asset_name, quantity)] = mint |> assets.tokens(policy_id) |> dict.to_pairs()
•確保鑄造的資產數據僅包含一個資產名稱和數量對:
•mint |> assets.tokens(policy_id) 過濾出符合 policy_id 的資產。
•dict.to_pairs() 將其轉換為鍵值對形式。
•檢查該列表是否僅包含一對 Pair(asset_name, quantity),否則驗證失敗。
6.let expected_token_name = first_input.output_reference |> cbor.serialise |> blake2b_256
•將 first_input 的 output_reference 使用 cbor 序列化,然後用 blake2b_256 進行哈希運算,生成預期的資產名稱 expected_token_name。
7.asset_name == expected_token_name && quantity == 1
•驗證生成的 expected_token_name 是否與交易中的 asset_name 相符,並確認資產數量為 1。
•只有當這兩個條件同時成立時,交易才會通過驗證。
8.else(_) { fail }
•若交易不是鑄造交易或不符合上述條件,驗證器將失敗。
總結
這段代碼的邏輯是:
•在鑄造資產時,驗證該資產的名稱是否符合第一個 UTxO 的 output_reference 的哈希值,且鑄造的數量是否為 1。
•如果這些條件不滿足,驗證器會返回失敗。
```
我們可以使用這個驗證器為交易產生唯一的收據。它將獲取第一個 UTxO 引用並將其與資產名稱進行比較。
## Unique Outputs(唯一輸出)
### Problem: Double Satisfaction(問題:雙重滿足)
為了防止稱為「雙重滿足」的漏洞(請參閱下面的更多內容),必須確保在交易中發生的所有可能的驗證中,與給定輸入關聯的輸出僅計算一次。
在 eUTxO 模型中,一個常見的反模式是根據特定於給定輸入的邏輯來預測支出 - 而不確保相應輸出的唯一性。
讓我們來看一個簡短的例子:Bob 想要出售 20 SCOIN,並希望至少獲得 5 ADA 作為回報;合約要求至少向 Bob 支付 5 ADA。
逐步交換:
1.Bob 將 20 SCOIN 發送給驗證器,其中包含包含他的 VerificationKeyHash 的datum以及獲得 20 SCOIN 所需的價格 (5 ADA)。
2.Alice 進行了一筆新交易,獲得 20 SCOIN 並向 Bob 支付 5 ADA。
3.Alice將獲得 20 SCOIN。
4.Bob 將獲得 5 ADA。
```Aiken
use aiken/collection/list
use aiken/crypto.{Blake2b_224, Hash, VerificationKey}
use cardano/address
use cardano/assets.{lovelace_of, merge}
use cardano/transaction.{Output, OutputReference, Transaction}
type VerificationKeyHash =
Hash<Blake2b_224, VerificationKey>
pub type DatumSwap {
beneficiary: VerificationKeyHash,
price: Int,
}
validator exploitable_swap {
spend(
optional_datum: Option<DatumSwap>,
_redeemer: Data,
_own_ref: OutputReference,
self: Transaction,
) {
expect Some(datum) = optional_datum
let beneficiary = address.from_verification_key(datum.beneficiary)
let user_outputs =
list.filter(
self.outputs,
fn(output) { output.address == beneficiary },
)
let value_paid =
list.foldl(
user_outputs,
assets.zero,
fn(output, total) { merge(output.value, total) },
)
(lovelace_of(value_paid) >= datum.price)?
}
}
1.type VerificationKeyHash = Hash<Blake2b_224, VerificationKey>
•定義了一個 VerificationKeyHash 類型,用於表示經過 Blake2b_224 哈希的驗證密鑰。
2.pub type DatumSwap { beneficiary: VerificationKeyHash, price: Int, }
•定義一個 DatumSwap 結構類型,包含兩個字段:
•beneficiary:一個 VerificationKeyHash,表示接受付款的受益人。
•price:一個整數 Int,表示需要支付的金額。
3.validator exploitable_swap {
•定義了一個名為 exploitable_swap 的驗證器,用於處理支出交易的驗證邏輯。
4.spend(optional_datum: Option<DatumSwap>, _redeemer: Data, _own_ref: OutputReference, self: Transaction) {
•定義 spend 函數,負責驗證支出交易。它包含以下參數:
•optional_datum:可選的 DatumSwap 資料。
•_redeemer:一個數據類型 Data,但在此未使用。
•_own_ref:一個 OutputReference 類型,也未在此使用。
•self:當前的 Transaction 對象。
5.expect Some(datum) = optional_datum
•檢查 optional_datum 是否包含有效的 DatumSwap 資料。如果不是 Some(datum),則驗證失敗。
6.let beneficiary = address.from_verification_key(datum.beneficiary)
•使用 datum.beneficiary(受益人的驗證密鑰哈希)來生成其地址,並將結果儲存在 beneficiary 變量中。
7.let user_outputs = list.filter(self.outputs, fn(output) { output.address == beneficiary })
•遍歷當前交易的所有輸出(self.outputs),並篩選出地址為受益人的輸出,將結果儲存在 user_outputs 列表中。
8.let value_paid = list.foldl(user_outputs, assets.zero, fn(output, total) { merge(output.value, total) })
•計算 user_outputs 中支付的總額:
•使用 list.foldl 將 user_outputs 中的 output.value 加總。
•assets.zero 作為初始值,merge(output.value, total) 將每個輸出的值加總至 total。
9.(lovelace_of(value_paid) >= datum.price)?
•檢查 value_paid 中的 lovelace 是否大於或等於 datum.price(所需的金額)。如果是,則通過驗證;否則驗證失敗。
總結
這段代碼實現了以下邏輯:
•驗證交易是否將足夠的金額(datum.price)支付給指定的受益人地址。
•具體來說,交易輸出必須包含足夠的 lovelace 發送到受益人地址,以通過此驗證器的驗證。
```
到目前為止,一切都很好,但如果我們有一些UTxO以類似的價格鎖定在驗證器中怎麼辦?
Bob 希望出售 20 個 XCOIN 和 20 個 SCOIN,並希望每個 UTxO 至少獲得 10 個 ADA;合約要求至少向 Bob 支付 10 ADA。現在Alice來支付給Bob 10 ADA,在同一筆交易中她同時拿走了 20 SCOIN 和 20 XCOIN,因為合約僅確保至少 10 ADA 支付給Bob。
因此,這個驗證器可能會導致對相同輸入的兩次滿足,任何人都可以支付一次並以相同或更低的價格解鎖每個 UTxO。
### Solution: Tagged Outputs(解:標記輸出)
我們能做什麼?我們必須確保每個輸入都有相應的唯一輸出,以支付或斷言predicate)將腳本的任何輸入花費在與 dApp 業務邏輯(business logic)相關的所有輸入和輸出上的邏輯。
此外,我們必須記住,驗證器中的程式碼將針對我們嘗試使用的驗證器鎖定的每個 UTxO 執行。因此,我們必須確保驗證器的多次執行(對於每個輸入驗證)不會對輸出進行多次計數。
這可以透過輸入唯一的值*標記*輸出來實現。輸入的`OutputReference`中存在足夠的資訊來創建必須在輸出中找到的唯一標記。
```aiken
use aiken/collection/list
use aiken/crypto.{Blake2b_224, Hash, VerificationKey}
use cardano/address
use cardano/assets.{lovelace_of, merge}
use cardano/transaction.{InlineDatum, Output, OutputReference, Transaction}
type VerificationKeyHash =
Hash<Blake2b_224, VerificationKey>
pub type DatumSwap {
beneficiary: VerificationKeyHash,
price: Int,
}
validator swap {
spend(
optional_datum: Option<DatumSwap>,
_redeemer: Data,
own_ref: OutputReference,
self: Transaction,
) {
expect Some(datum) = optional_datum
let beneficiary = address.from_verification_key(datum.beneficiary)
// We will get all UTxO outputs with a datum equal to the UTxO input's reference
// we are validating. We have to remember that this code will be executed for every
// UTxO locked to the validator address that we are trying to unlock.
let user_outputs_restricted =
list.filter(
self.outputs,
fn(output) {
when output.datum is {
InlineDatum(output_datum) ->
// Note that we use a soft-cast here and not an expect, because the transaction
// might still contain other kind of outputs that we simply chose to ignore.
// Using expect here would cause the entire transaction to be rejected for any
// output that doesn't have a datum of that particular shape.
if output_datum is OutputReference {
and {
output.address == beneficiary,
own_ref == output_datum,
}
} else {
False
}
_ -> False
}
},
)
// We sum all output values and check that the amount paid is greater than or equal to the price
// asked by the seller.
let value_paid =
list.foldl(
user_outputs_restricted,
assets.zero,
fn(n, acc) { merge(n.value, acc) },
)
(lovelace_of(value_paid) >= datum.price)?
}
}
1.// We will get all UTxO outputs with a datum equal to the UTxO input's reference
•此代碼的目標是找到所有 datum 與當前 UTxO 輸入參考 (own_ref) 相等的 UTxO 輸出。
2.// we are validating. We have to remember that this code will be executed for every
// UTxO locked to the validator address that we are trying to unlock.
•提醒:這段代碼會針對每個被鎖定在驗證器地址上的 UTxO 執行,以便解鎖這些 UTxO。
3.let user_outputs_restricted = list.filter(self.outputs, fn(output) { ... })
•使用 list.filter 遍歷當前交易中的所有輸出 (self.outputs),並根據給定條件篩選出符合條件的輸出,將結果儲存在 user_outputs_restricted 中。
4.fn(output) { ... }
•定義篩選條件的匿名函數 (fn),其中每個輸出 (output) 都需符合特定條件才會被選中。
5.when output.datum is { InlineDatum(output_datum) -> ... _ -> False }
•使用 when 表達式來檢查 output.datum 的類型:
•如果 output.datum 是 InlineDatum 類型,提取其 datum 值為 output_datum。
•否則返回 False,表示此輸出不符合條件,跳過此輸出。
6.if output_datum is OutputReference { and { output.address == beneficiary, own_ref == output_datum } }
•當 output_datum 是 OutputReference 類型時,檢查以下條件:
•output.address == beneficiary:確保此輸出的地址為受益人地址。
•own_ref == output_datum:檢查 output_datum 是否等於 own_ref(當前正在解鎖的 UTxO 參考)。
•如果上述條件都滿足,則返回 True,將此輸出包括在篩選結果中;否則返回 False。
7.else { False }
•如果 output_datum 不是 OutputReference 類型,則返回 False,表示此輸出不符合篩選條件。
8._ -> False
•如果 output.datum 不是 InlineDatum 類型,則返回 False,表示忽略此類型的輸出。
總結
此代碼的目的是找到與當前解鎖的 UTxO 參考 (own_ref) 相符的輸出條件,具體條件如下:
•輸出具備 InlineDatum 類型的 datum。
•datum 的類型是 OutputReference,並且與當前的 UTxO 參考 (own_ref) 相同。
•輸出的地址必須是指定的受益人地址 (beneficiary)。
```
## State Thread Token(a.k.a STT)(狀態線程令牌(又稱STT))
擁有一個可變的狀態通常是有用的,它可以隨每筆交易改變,也可以定期改變。確保datum不被「欺騙」的一種方式是確保具有該datum的輸入或參考輸入包含已經使用上述方法之一生成為唯一的NFT。
在本例中,我們將創建一個STT,它跟蹤使用該STT的每筆交易的總和。為此,我們將創建一個具有兩個職責的多驗證器:鑄造和支出政策。
STT鑄造政策允許我們創建新的令牌,其計數器datum初始化為0。
```aiken
use aiken/collection/dict
use aiken/collection/list
use cardano/address.{Script}
use cardano/assets.{PolicyId, policies}
use cardano/transaction.{InlineDatum, OutputReference, Transaction}
use config
validator counter_stt(utxo_ref: OutputReference) {
mint(_redeemer: Data, policy_id: PolicyId, self: Transaction) {
let Transaction { inputs, outputs, mint, .. } = self
expect [Pair(_asset_name, quantity)] =
mint |> assets.tokens(policy_id) |> dict.to_pairs()
let is_output_consumed =
list.any(inputs, fn(input) { input.output_reference == utxo_ref })
expect Some(nft_output) =
list.find(
outputs,
fn(output) { list.has(policies(output.value), policy_id) },
)
expect InlineDatum(datum) = nft_output.datum
expect counter: Int = datum
is_output_consumed? && (1 == quantity)? && counter == 0
}
// "Create the spending part to handle the STT"
}
```
下面是驗證器的一部分,它確保每筆交易都增加計數器的值:
- 我們檢查交易是否由運算子(operator)簽名
- 我們獲取`ScriptHash`來識別NFT,它與`PolicyId`相同
- 我們檢查輸入NFT是否存在並具有整數datum
- 我們檢查輸出是否存在,是否有一個整數daum
- 我們檢查輸出datum是否等於輸入datum+1
```aike=
validator counter_stt(utxo_ref: OutputReference) {
// Mint code...
spend(
_optional_datum: Option<Data>,
_redeemer: Data,
own_ref: OutputReference,
self: Transaction,
) {
let Transaction { inputs, outputs, .. } = self
// Getting the script hash from this validator. Note that since the
// `mint` handler is defined as part of the same validator, they share
// the same hash digest. Thus, our `payment_credential` is ALSO our STT
// minting policy.
expect Some(own_input) =
list.find(inputs, fn(input) { input.output_reference == own_ref })
expect Script(own_script_hash) = own_input.output.address.payment_credential
// Checking if the transaction is signed by the operator.
let is_signed_by_operator =
list.has(self.extra_signatories, config.operator)
// One input should hold the STT, with the expected format.
expect Some(stt_input) =
list.find(
inputs,
fn(input) { list.has(policies(input.output.value), own_script_hash) },
)
expect InlineDatum(input_datum) = stt_input.output.datum
expect counter_input: Int = input_datum
// The STT must be forwarded to an output
expect Some(stt_output) =
list.find(
outputs,
fn(output) { list.has(policies(output.value), own_script_hash) },
)
expect InlineDatum(output_datum) = stt_output.datum
expect counter_output: Int = output_datum
expect stt_input.output.address == stt_output.address
// Final validations
is_signed_by_operator? && (counter_output == counter_input + 1)?
}
}
```
>需要指出的一件重要事情是,我們從配置中提取運算符值(operator value),該值來自aiken.toml文件的`config`部分:
```
[config.default.operator]
encoding = "hex"
bytes = "00000000000000000000000000000000000000000000000000000000"
```
## Forwarding Validation & Other Withdrawal Tricks (驗證轉移和其他提領技巧)
透過強制從特定的腳本進行提領,我們可以有效地將驗證「轉交」給該腳本,並以提領腳本的目的來執行驗證。之所以能夠實現,特別是因為始終可以提領 0 Lovelace 的金額。
>1.「從特定腳本提領(withdrawal)」:
•在 Cardano 中,花費一個由腳本保護的 UTXO 通常被稱為「提領」。這個過程需要滿足該腳本的驗證條件。
2.「將驗證轉交給該腳本執行」:
•這裡的意思是,我們可以設計交易,使其依賴於另一個腳本的驗證邏輯,而不是完全由當前腳本處理所有邏輯。這種方式被稱為 驗證轉移(forward validation)。
3.「以提領腳本(withdraw script)為目的來評估該腳本」:
•轉交後的腳本會以「提領的目的」進行執行,這意味著這個腳本會驗證某筆 UTXO 是否符合它的條件來被花費。
4.「可以提領 0 Lovelace」的特性:
•在 Cardano 中,即使一個輸出(UTXO)的價值為 0(沒有實際資金),也可以進行花費(提領)。這意味著,腳本可以藉由操作價值為 0 的輸出來觸發驗證,而不必涉及真實的資金移動。
我們可以利用這一點,讓一個腳本成為一個或多個 UTxO 的擁有者,而這些 UTxO 本身是由一個更簡單的腳本鎖定的。與其通常確保擁有者的公鑰哈希 (PKH) 存在於必需的簽名者中,我們使用一個小型腳本,將驗證過程轉交給交易中存在的另一個單一腳本來完成。
透過在花費驗證器中使用這個技巧,我們可以減少多次授權花費所帶來的開銷。實際上,與其為每個輸入多次執行相同的邏輯(每個輸入一次),我們只需要針對提領腳本執行一次。由於驗證器無論執行目的是什麼,都能訪問整個交易作為上下文,因此在大多數情況下,這種方法是可行的。
目前,許多正在運行的去中心化應用(dApps)已經使用這種方法來優化評估預算,並提高效率。
## Going further(更進一步)
### Anastasia Labs' design patterns
>https://github.com/Anastasia-Labs/design-patterns
一個旨在抽象化一些不太直觀且鮮為人知的 eUTxO 設計模式的庫,使這些模式對開發者更易於訪問。
### Plutonomicon
>https://github.com/Plutonomicon/plutonomicon
面向開發者的Pultus智能合約平台實踐指南