Plutus Pioneer Program – Week 2 筆記

Plutus Pioneer Program – Lecture #2

區塊鏈上的智能合約

智能合約分為兩部分:

  • 區塊鏈上讓節點對合約做驗證,並且操作被記錄在區塊鏈上的帳目
  • 區塊鏈外建立並上傳合約,在使用者的錢包中可以核對是否能夠成功與合約互動

接下來(兩周)會先介紹在區塊鏈上建立智能合約的基礎步驟。

在這個教學的初期,我們先以最粗略的資料型態 Data 來作為基礎。可以看到以下是 Data 的建構子:

-- Haskell
module PlutusTx.Data

-- able to represent arbitrary custom data
data Data = 
   Constr Integer [Data]
 | Map [(Data, Data)]
 | List [Data]
 | I Integer
 | B BS.ByteString

在 Nix 底下可以進入 GHCI 來得到所有結構的詳細資訊:

$ cd plutux/
$ nix-shell
$ cd plutus-poineer-program/code/week02
$ cabal repl # opens GHCI

#################  GHCI  ##################
# Show construction information for Data
Prelude > :i Data


在 Plutus 上的智能合約叫做 Validator Script,是讓使用者往區塊鏈上執行寫入的控制腳本。一個控制腳本會含有三個部分:

  • 資料元(Datum):附屬於該合約的資料,會被寫到記錄到區塊鏈上
  • 參與者(Redeemer):參與合約的資料,由合約參與者提供
  • 合約內容(Validation context):合約本身所夾帶的基本內容,例如簽名者、合約有效時間區間、手續費

基本練習

Gift.hs

第一個例子 Gift.hs 中展示了合約中的基本架構

  • 區塊鏈上:
    • mkValidator 函數來取得智能合約的三個部分
    mkValidator :: Data -> Data -> Data -> () mkValidator _ _ _ = ()
  • 區塊鏈下:
    • 產生合約地址(script address)
    validator :: Validator validator = mkValidatorScript $$(PlutusTx.compile [|| mkValidator ||]) valHash :: Ledger.ValidatorHash valHash = Scripts.validatorHash validator scrAddress :: Ledger.Address scrAddress = ScriptAddress valHash

例子中有兩個動作:

type GiftSchema =
    BlockchainActions
        .\\/ Endpoint "give" Integer
        .\\/ Endpoint "grab" ()

  • 動作 Give 上傳合約
give :: (HasBlockchainActions s, AsContractError e) => Integer -> Contract w s e ()
give amount = do
    let tx = mustPayToOtherScript valHash (Datum $ Constr 0 []) $ Ada.lovelaceValueOf amount
    ledgerTx <- submitTx tx
    void $ awaitTxConfirmed $ txId ledgerTx
    logInfo @String $ printf "made a gift of %d lovelace" amount

  • 動作 Grab 用 utxoAt 取得合約並作為 validator 來執行合約
grab :: forall w s e. (HasBlockchainActions s, AsContractError e) => Contract w s e ()
grab = do
    utxos <- utxoAt scrAddress
    let orefs   = fst <$> Map.toList utxos
        lookups = Constraints.unspentOutputs utxos      <>
                  Constraints.otherScript validator
        tx :: TxConstraints Void Void
        tx      = mconcat [mustSpendScriptOutput oref $ Redeemer $ I 17 | oref <- orefs]
    ledgerTx <- submitTxConstraintsWith @Void lookups tx
    void $ awaitTxConfirmed $ txId ledgerTx
    logInfo @String $ "collected gifts"

Burn.hs

mkValidator 函數轉為 error,這樣 give 動作執行並被貨幣鎖到智能合約上後,任何人都無法成功執行合約來把他取下來。等於是把貨幣燒掉的意思。

FortyTwo.hs

原本 mkValidator 並沒有實際讀入資料,這個例子調整 redeemer(可以想成是合約參與者輸入密碼的概念)來參與合約的執行。

mkValidator :: Data -> Data -> Data -> ()
mkValidator _ r _
    | r == I 42 = ()
    | otherwise = traceError "wrong redeemer"

在動作 grab 的地方也要修改讓它能讀入數字。

grab :: forall w s e. (HasBlockchainActions s, AsContractError e) => Integer -> Contract w s e ()

Typed.hs, isData.hs

上一個例子中用粗略的資料型態 Data 來作為 mkValidator 函數的參數型態。但是實際上我們會想要自己構造資料結構來儲存自己的智能合約資料還有邏輯。

這裡將剛剛數字的輸入型態從 Data 改為 Integer,這樣輸入字串時就無法成功寫出合約。

mkValidator :: () -> Integer -> ValidatorCtx -> Bool
mkValidator () r _
    | r == 42   = True
    | otherwise = False

而自定義的合約也需要宣告參與合約的資料型別為哪些。(這就像在其他物件導向語言一樣要宣告自己的 structure)

data MyTypedScript
instance Scripts.ScriptType MyTypedScript where
    type instance DatumType MyTypedScript = ()
    type instance RedeemerType MyTypedScript = Integer

再來有了 structure,就可以宣告自己的 script instance。(可以想像成是宣告變數)

inst :: Scripts.ScriptInstance MyTypedScript
inst = Scripts.validator @MyTypedScript
    $$(PlutusTx.compile [|| mkValidator ||])
    $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = Scripts.wrapValidator @() @Integer

validator :: Validator
validator = Scripts.validatorScript inst

在現實世界中的 validation 不可能是根據 raw data type Data 來執行,這樣會非常不安全,而且也需要更複雜的結構來儲存邏輯。在 Plutus 中,有 IsDataFromData 還有 ToData 的函數介面,只要你的 custom data type 有建構對這兩個函數構造建構子,就可以把客製化的結構放進 validator script 之中。

而 Plutus 提供了 template 來讓使用者可以生成這些建構子介面。利用 PlutusTx.unstableMakeIsData 可以生成建構子。

newtype MySillyRedeemer = MySillyRedeemer Integer
    deriving Show

PlutusTx.unstableMakeIsData ''MySillyRedeemer

接下來就可以使用 PlutusTx.toData 來將資料結構傳入智能合約中。

tx = mconcat [mustSpendScriptOutput oref $ Redeemer $ PlutusTx.toData $ MySillyRedeemer r | oref <- orefs]

Homework 1

照著語法,把 raw boolean tuple 傳入並且跟 tutorial 影片中一樣,構造 Typed 的 script 後再創造 script instance。基本上就是把 Typed.hs 自己親手操作一遍。

Homework 2

跟上一個 homework 一樣,只是換成現在是 custom structure。基本上就是把 isData.hs 自己親手操作一遍。

(文章歡迎轉載,請註明出處為 eopxd.com)

Author: eopXD

Hi 我是 eop ,希望人生過得有趣有挑戰XD

Leave a Reply