Bài giảng 3: Vesting và Cardano Testnet
Plutus Pioneer Program - Cohort 3 January 26, 2022
Offical Video by Lars Brünjes: PPP-Cohort3-Lecture3
Google Doc version can be found HERE
Video thực hành Deploy smartcontract Vesting trên testnet Preprod.
Table of Contents
Preparation for Lecture 3
Trước khi chúng ta có thể bắt đầu trong Bài giảng 3, trước tiên chúng ta phải cập nhật môi trường phát triển của mình. Bạn có thể sao chép và dán trực tiếp bất kỳ mã nào trong hướng dẫn này vào Terminal hoặc IDE của mình.
Đầu tiên, hãy vào thư mục plutus-pioneer-program để lấy nội dung bài giảng tuần 3. Hành hình:
~/plutus-pioneer-program$ git pull
Bây giờ bạn có thể điều hướng đến thư mục week03 hiện tại và mở tệp cabal.project:
~/plutus-pioneer-program/code/week03$ cat cabal.project
Lấy thẻ plutus-apps bên trong tệp cabal.project:
location: https://github.com/input-output-hk/plutus-apps.git
tag:4edc082309c882736e9dec0132a3c936fe63b4ea
Quay trở lại thư mục plutus-apps và cập nhật nó vào thẻ git hiện tại:
~/plutus-apps$ git checkout main
~/plutus-apps$ git pull
~/plutus-apps$ git checkout 4edc082309c882736e9dec0132a3c936fe63b4ea
Bây giờ bạn đã được cập nhật và có thể chạy nix-shell trong thư mục này. Chạy nix-shell:
. ~/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Quay trở lại thư mục week03 để bắt đầu chạy các lệnh cabal:
[nix-shell:~/plutus-pioneer-program/code/week03]$ cabal update
[nix-shell:~/plutus-pioneer-program/code/week03]$ cabal build
[nix-shell:~/plutus-pioneer-program/code/week03]$ cabal repl
Nếu thành công, bây giờ bạn sẽ thấy trong Terminal :
Ok, 7 modules loaded.
Prelude week03.Deploy>
Bài giảng này cũng sẽ khám phá Cardano Testnet Preprod. Để tương tác với nó sau này, trước tiên chúng ta cần đồng bộ hóa nút cục bộ, có thể mất hơn 1 giờ. Hãy bắt đầu trong nền:
Giữ cabal mở trên Terminal 1 và mở Terminal mới 2. Đi tới thư mục ứng dụng plutus và chạy nix-shell trước:
Terminal 2
. ~/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Chúng ta có thể kiểm tra phiên bản của Cardano Node và bằng các lệnh:
Terminal 2
[nix-shell:~/plutus-apps]$ cardano-node --version
Output:
cardano-node 1.35.4 - linux-x86_64 - ghc-8.10
git rev ebc7be471b30e5931b35f9bbc236d21c375b91bb
Terminal 2
[nix-shell:~/plutus-apps]$ cardano-cli --version
Output:
cardano-cli 1.35.4 - linux-x86_64 - ghc-8.10
git rev ebc7be471b30e5931b35f9bbc236d21c375b91bb
Nếu chưa có cardano-node và cardano-cli chúng ta có thể download file binary của iohk về máy
cd ~/.cabal/bin
sudo wget https://update-cardano-mainnet.iohk.io/cardano-node-releases/cardano-node-1.35.4-linux.tar.gz
sudo tar zxvf cardano-node-1.35.4-linux.tar.gz
sau đó kiểm tra lại bước trên
Tiếp đến chúng ta chạy node trên testnet preprod
tạo cấu trúc thư mục cntool
echo export CNODE_HOME=/opt/cardano/cnode >> $HOME/.bashrc
source ~/.bashrc
sudo whoami
mkdir "$HOME/tmp";
cd "$HOME/tmp"
curl -sS -o prereqs.sh https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/prereqs.sh
chmod 755 prereqs.sh
./prereqs.sh -n preprod
Cập nhật PATH
echo export PATH=~/.cabal/bin:$PATH >> ~/.bashrc
export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH"
echo export CNODE_HOME=/opt/cardano/cnode >> $HOME/.bashrc
source ~/.bashrc
echo export CARDANO_NODE_SOCKET_PATH="$CNODE_HOME/sockets/node0.socket" >> $HOME/.bashrc
source ~/.bashrc
echo export PATH="$HOME/.cargo/bin:$PATH" >> ~/.bashrc
source ~/.bashrc
sau đó vào thư mục để chạy node
cd $CNODE_HOME/scripts
./cnode.sh
kết quả như thế này và để nguyên Terminal đó để đồng bộ
.............ncTargetNumberOfActivePeers = 20, ncEnableP2P = EnabledP2PMode}
Listening on http://127.0.0.1:12798
Quá trình này sẽ mất 1 giờ or hơn để đồng bộ hóa. Bạn sẽ được đồng bộ hóa 100% sau khi bạn bắt đầu thấy một khối mới cứ sau 20 giây, thay vì nhiều khối mỗi giây. Để thiết bị đầu cuối này mở và bây giờ chúng ta có thể bắt đầu.
Plutus Playground Timeout
Nếu máy chủ sân chơi plutus gặp thời gian chờ trước khi hoàn thành, chúng ta có thể sử dụng lệnh này thay thế để kéo dài thời gian chạy:
[nix-shell:~/plutus-apps/plutus-playground-server]$
plutus-playground-server -i 120s
Script Context
Trong bài giảng này, chúng ta sẽ khám phá script context
. Nếu chúng ta còn nhớ bài giảng 2, script context
là phần thứ ba của dữ liệu trên chuỗi xác định mục đích cho việc chạy:
data ScriptContext
Constructors
ScriptContext
scriptContextTxInfo :: TxInfo
scriptContextPurpose ::ScriptPurpose
data ScriptPurpose
Purpose of the script that is currently running
Constructors
Minting CurrencySymbol
Spending TxOutRef
Rewarding StakingCredential
Certifying DCert
data TxInfo
A pending transaction. This is the view as seen by validator scripts, so some details are stripped out.
Constructors
TxInfo
txInfoInputs :: [TxInInfo]
Transaction inputs
txInfoOutputs :: [TxOut]
Transaction outputs
txInfoFee :: Value
The fee paid by this transaction.
txInfoMint :: Value
The Value minted by this transaction.
txInfoDCert :: [DCert]
Digests of certificates included in this transaction
txInfoWdrl :: [(StakingCredential, Integer)]
Withdrawals
txInfoValidRange :: POSIXTimeRange
The valid range for the transaction.
txInfoSignatories :: [PubKeyHash]
Signatures provided with the transaction, attested that they all signed the tx
txInfoData :: [(DatumHash, Datum)]
txInfoId :: TxId Hash of the pending transaction (excluding witnesses)
Nếu xem tài liệu về cardano, chúng ta có thể thấy một ví dụ đơn giản:
Ví dụ trực quan: một đứa trẻ muốn đi đu quay, nhưng trước khi lên, chúng phải cao hơn biển báo an toàn. Chúng ta có thể diễn đạt ý tưởng đó bằng mã giả, như:
if isTallEnough(attraction=ferrisWheel,passenger=michael):
getOnFerrisWheel()
def isTallEnough(attraction,kid):
return kid["height"] >= attraction["minimumHeight"]
def getOnFerrisWheel():
print ("get On the Ferris Wheel")
ferrisWheel = {"minimumHeight":120}
michael = {"height":135}
Trong ví dụ này, những điều sau đây được áp dụng:
- The datum à thông tin về giao dịch này:
michael.height
. - The context là trạng thái của thế giới, tại thời điểm đó có nghĩa là:
ferrisWheel.minimumHeight
. - Reedemer, là hành động để thực hiện:
getOnFerrisWheel()
- The validator script Tập lệnh trình xác thực là chức năng sử dụng tất cả thông tin đó
isTallEnough
Handling Time
Trong mô hình Cardano eUTxO, giao dịch vẫn có thể thất bại. Điều này là do một giao dịch có thể tiêu thụ một đầu vào, khi giao dịch đó đến trên chuỗi khối tại nút để xác thực, nó có thể đã được một người khác sử dụng. Nhưng trong trường hợp đó, giao dịch đơn giản là thất bại mà không phải trả phí. Nhưng điều không bao giờ có thể xảy ra hoặc sẽ không bao giờ xảy ra trong các trường hợp bình thường, đó là tập lệnh xác thực chạy và sau đó không thành công. Lỗi sẽ xảy ra trước khi nó được gửi. Đó là một tính năng tuyệt vời, tuy nhiên nó có nhiều ẩn ý về cách thể hiện thời gian.
Chúng tôi muốn có thể thể hiện logic xác thực nói rằng một giao dịch nhất định chỉ hợp lệ sau khi đạt đến một thời gian nhất định hoặc trước khi đạt đến một thời điểm nhất định. Chúng ta đã thấy một ví dụ về điều đó trong ví dụ đầu tiên, ví dụ đấu giá, giá thầu chỉ được phép cho đến khi đạt đến thời hạn. Điểm cuối gần chỉ có thể được gọi sau khi thời hạn đã qua.
Nếu bạn nghĩ về điều đó, thì đó dường như là một mâu thuẫn bởi vì thời gian rõ ràng là đang trôi. Khi bạn cố gắng xác thực một giao dịch mà bạn đang xây dựng trong ví của mình, tất nhiên, thời gian xảy ra trong ví có thể khác với thời gian giao dịch đến một nút để xác thực. Không rõ làm thế nào để kết hợp hai thứ này lại với nhau để một mặt xử lý thời gian, nhưng mặt khác đảm bảo rằng việc xác thực là xác định theo nghĩa là nếu nó thành công trong ví thì nó cũng sẽ thành công trong nút.
Cardano giải quyết vấn đề này bằng cách thêm trường phạm vi thời gian POSIX này và trường phạm vi hợp lệ của thông tin TX vào một giao dịch. Với điều này, chúng ta có thể tuyên bố một giao dịch là hợp lệ trong khoảng thời gian xác định được chỉ định trong giao dịch. Khi một nút đang xác thực một giao dịch, một trong những bước kiểm tra trước này trước khi xác thực, là nút sẽ kiểm tra thời gian hiện tại và so sánh nó với phạm vi thời gian được chỉ định trong giao dịch. Nếu thời gian hiện tại không nằm trong phạm vi thời gian này, thì quá trình xác thực không thành công ngay lập tức mà không bao giờ chạy tập lệnh trình xác thực. Điều đó cũng có nghĩa là nếu những lần kiểm tra trước này thành công, thì chúng ta có thể giả định rằng thời gian hiện tại rơi vào khoảng thời gian này. Điều này bảo tồn các thuộc tính eUTxO xác định.
Theo mặc định, tất cả các giao dịch sử dụng phạm vi thời gian vô hạn. Điều này bắt đầu từ đầu thời gian hoặc tại khối Genesis và kéo dài vĩnh viễn. Các giao dịch này sẽ luôn hợp lệ, bất kể thời gian chúng đến một nút để xác thực. Các trường hợp ngoại lệ duy nhất mà chúng ta đã thấy cho đến nay là những trường hợp trong ví dụ đấu giá, trong đó giá thầu và giá đóng không thể sử dụng khoảng thời gian vô hạn vì chúng ta đảm bảo rằng giá thầu diễn ra trước thời hạn và giá đóng sau thời hạn. Tuy nhiên, theo mặc định, tất cả các giao dịch bao gồm các giao dịch mà bạn gửi từ Daedalus chẳng hạn, sẽ luôn sử dụng phạm vi thời gian vô hạn.
Có một sự phức tạp nhỏ là Ouroboros, giao thức đồng thuận cung cấp năng lượng cho Cardano, không sử dụng thời gian POSIX; nó sử dụng khe cắm. Plutus sử dụng thời gian thực, vì vậy chúng ta cần có khả năng chuyển đổi qua lại giữa thời gian thực và thời gian. Ngay bây giờ, độ dài khe là một giây. Biết rằng, thật dễ dàng để chuyển đổi qua lại giữa thời gian thực và số vị trí. Tuy nhiên, điều này có thể thay đổi trong tương lai thông qua thay đổi tham số thông qua một hard fork. Và, tất nhiên, chúng ta không thể biết trước điều đó.
Chẳng hạn, hiện tại chúng ta không biết độ dài của vị trí sẽ là bao nhiêu trong 10 năm nữa. Điều này có nghĩa là chúng ta không được có giới hạn trên nhất định. Chúng tôi biết thời lượng của vị trí sẽ là bao nhiêu trong 36 giờ tới vì nếu có thay đổi về tham số giao thức, thì chúng ta sẽ biết điều đó trước ít nhất 36 giờ. Bạn không thể chỉ định phạm vi thời gian tùy ý trong khoảng thời gian giao dịch. Nó chỉ được tối đa là 36 giờ trong tương lai hoặc có thể là vô thời hạn.
Vì vậy, hãy xem loại phạm vi thời gian POSIX này.
type POSIXTimeRange = Interval POSIXTime
An Interval of POSIXTimes.
Trong đó Khoảng thời gian là:
data Interval a
An interval of as.
The interval may be either closed or open at either end, meaning that the endpoints may or may not be included in the interval.
The interval can also be unbounded on either side.
Constructors
Interval
ivFrom :: LowerBound a
ivTo :: UpperBound a
Trong đó Giới hạn dưới là:
data LowerBound a
The lower bound of an interval.
Constructors
LowerBound (Extended a) Closure
Trường hợp đóng Closure
là:
type Closure = Bool
Whether a bound is inclusive or not.
Trường hợp mở rộng là:
data Extended a
A set extended with a positive and negative infinity.
Constructors
NegInf
Finite a
PosInf
Một số chức năng hữu ích để xác định giới hạn:
after :: Ord a => a -> Interval a -> Bool
Check if a value is later than the end of a Interval.
before :: Ord a => a -> Interval a -> Bool
Check if a value is earlier than the beginning of an Interval.
isEmpty :: (Enum a, Ord a) => Interval a -> Bool
Check if an Interval is empty.
contains :: Ord a => Interval a -> Interval a -> Bool
a contains b is true if the Interval b is entirely contained in a. That is, a contains b if for every entry s, if member s b then member s a.
hull :: Ord a => Interval a -> Interval a -> Interval a
'hull a b' is the smallest interval containing a and b.
intersection :: Ord a => Interval a -> Interval a -> Interval a
'intersection a b' is the largest interval that is contained in a and in b, if it exists.
overlaps :: (Enum a, Ord a) => Interval a -> Interval a -> Bool
Check whether two intervals overlap, that is, whether there is a value that is a member of both intervals.
member :: Ord a => a -> Interval a -> Bool
Check whether a value is in an interval.
never :: Interval a
An Interval that is empty.
always :: Interval a
An Interval that covers every slot.
to :: a -> Interval a
to a is an Interval that includes all values that are smaller than or equal to a.
from :: a -> Interval a
from a is an Interval that includes all values that are greater than or equal to a.
singleton :: a -> Interval a
interval :: a -> a -> Interval a
interval a b includes all values that are greater than or equal to a and smaller than or equal to b. Therefore it includes a and b.
upperBound :: a -> UpperBound a
lowerBound :: a -> LowerBound a
strictLowerBound :: a -> LowerBound a
strictUpperBound :: a -> UpperBound a
Bây giờ chúng ta có thể thực hành một số thay thế cabal. Đầu tiên chúng ta sẽ nhập Plutus.V1.Ledger.Interval.
Prelude week03.Deploy> import Plutus.V1.Ledger.Interval
Ví dụ khoảng thời gian :
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
interval (10 :: Integer) 20
Output:
Interval {ivFrom = LowerBound (Finite 10) True, ivTo = UpperBound (Finite 20) True}
Ví dụ Thành viên:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 9 $ interval (10 :: Integer) 20
Output:
False
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 10 $ interval (10 :: Integer) 20
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 21 $ interval (10 :: Integer) 20
Output:
False
Ví dụ bắt đầu từ:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 10 $ from (30 :: Integer)
Output:
False
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 30 $ from (30 :: Integer)
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 31 $ from (30 :: Integer)
Output:
True
Ví dụ đến:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 10 $ to (30 :: Integer)
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 30 $ to (30 :: Integer)
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
member 31 $ to (30 :: Integer)
Output:
False
Example Intersection:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
intersection (interval (10 :: Integer) 20) $ interval 18 30
Output:
Interval {ivFrom = LowerBound (Finite 18) True, ivTo = UpperBound (Finite 20) True}
Ví dụ trong khoảng:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
contains (to (100 :: Integer)) $ interval 30 80
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
contains (to (100 :: Integer)) $ interval 30 100
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
contains (to (100 :: Integer)) $ interval 30 101
Output:
False
Ví dụ Overlaps:
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
overlaps (to (100 :: Integer)) $ interval 30 101
Output:
True
Prelude Plutus.V1.Ledger.Interval week03.Deploy>
overlaps (to (100 :: Integer)) $ interval 101 110
Output:
False
Vesting Contract
Bây giờ chúng ta sẽ xem xét một ví dụ về hợp đồng trao quyền Vesting Contract
. Hãy tưởng tượng bạn muốn tặng một món quà ADA cho một đứa trẻ. Bạn muốn đứa trẻ sở hữu ADA, tuy nhiên, bạn chỉ muốn đứa trẻ có quyền truy cập vào ADA khi chúng đến một độ tuổi cụ thể. Sử dụng plutus, rất dễ dàng thực hiện một kế hoạch trao quyền thỏa mãn các điều kiện đó.
Trước tiên, chúng ta xem xét mốc thời gian được thông qua với hai mẩu thông tin; Người thụ hưởng và thời hạn:
data VestingDatum = VestingDatum
{ beneficiary :: PaymentPubKeyHash
, deadline :: POSIXTime
} deriving Show
Sau đó, chúng ta xem xét chức năng trình xác thực:
{-# INLINABLE mkValidator #-}
mkValidator :: VestingDatum -> () -> ScriptContext -> Bool
mkValidator dat () ctx = traceIfFalse "beneficiary's signature missing" signedByBeneficiary &&
traceIfFalse "deadline not reached" deadlineReached
where
info :: TxInfo
info = scriptContextTxInfo ctx
signedByBeneficiary :: Bool
signedByBeneficiary = txSignedBy info $ unPaymentPubKeyHash $ beneficiary dat
deadlineReached :: Bool
deadlineReached = contains (from $ deadline dat) $ txInfoValidRange info
Chúng tôi đã xác định mốc thời gian là dat và context là ctx. Sau đó, chúng ta kiểm tra đúng người thụ hưởng bằng cách tạo hàm SignByBeneficiary và thời hạn bằng hàm deadlineReached.
Sau đó mã hóa mốc thời gian và redeemer:
data Vesting
instance Scripts.ValidatorTypes Vesting where
type instance DatumType Vesting = VestingDatum
type instance RedeemerType Vesting = ()
Bây giờ chúng ta cần xử lý việc biên dịch:
typedValidator :: Scripts.TypedValidator Vesting
typedValidator = Scripts.mkTypedValidator @Vesting
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @VestingDatum @()
Tiếp theo là mã soạn sẵn cho trình xác thực, hàm băm và địa chỉ:
validator :: Validator
validator = Scripts.validatorScript typedValidator
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash typedValidator
scrAddress :: Ledger.Address
scrAddress = scriptAddress validator
Cuối cùng, mã ngoài chuỗi off-chain
:
data GiveParams = GiveParams
{ gpBeneficiary :: !PaymentPubKeyHash
, gpDeadline :: !POSIXTime
, gpAmount :: !Integer
} deriving (Generic, ToJSON, FromJSON, ToSchema)
type VestingSchema =
Endpoint "give" GiveParams
.\/ Endpoint "grab" ()
give :: AsContractError e => GiveParams -> Contract w s e ()
give gp = do
let dat = VestingDatum
{ beneficiary = gpBeneficiary gp
, deadline = gpDeadline gp
}
tx = Constraints.mustPayToTheScript dat $ Ada.lovelaceValueOf $ gpAmount gp
ledgerTx <- submitTxConstraints typedValidator tx
void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
logInfo @String $ printf "made a gift of %d lovelace to %s with deadline %s"
(gpAmount gp)
(show $ gpBeneficiary gp)
(show $ gpDeadline gp)
grab :: forall w s e. AsContractError e => Contract w s e ()
grab = do
now <- currentTime
pkh <- ownPaymentPubKeyHash
utxos <- Map.filter (isSuitable pkh now) <$> utxosAt scrAddress
if Map.null utxos
then logInfo @String $ "no gifts available"
else do
let orefs = fst <$> Map.toList utxos
lookups = Constraints.unspentOutputs utxos <>
Constraints.otherScript validator
tx :: TxConstraints Void Void
tx = mconcat [Constraints.mustSpendScriptOutput oref unitRedeemer | oref <- orefs] <>
Constraints.mustValidateIn (from now)
ledgerTx <- submitTxConstraintsWith @Void lookups tx
void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
logInfo @String $ "collected gifts"
where
isSuitable :: PaymentPubKeyHash -> POSIXTime -> ChainIndexTxOut -> Bool
isSuitable pkh now o = case _ciTxOutDatum o of
Left _ -> False
Right (Datum e) -> case PlutusTx.fromBuiltinData e of
Nothing -> False
Just d -> beneficiary d == pkh && deadline d <= now
endpoints :: Contract () VestingSchema Text ()
endpoints = awaitPromise (give' `select` grab') >> endpoints
where
give' = endpoint @"give" give
grab' = endpoint @"grab" $ const grab
mkSchemaDefinitions ''VestingSchema
mkKnownCurrencies []
Bây giờ chúng ta có thể kiểm tra điều này trong Plutus Playground.
Để bắt đầu với Plutus Playground, chúng ta cần có hai Terminal đang chạy, cả hai đều nằm trong nix-shell.
Hãy bắt đầu với terminal 1. Đi tới thư mục plutus-apps và chạy nix-shell trước:
Terminal 3
. ~/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Tiếp theo, chúng ta đi đến thư mục plutus-playground-server và chạy:
Terminal 3
[nix-shell:~/plutus-apps/plutus-playground-server]$ plutus-playground-server
Nếu thành công, bạn sẽ thấy đầu ra:
Terminal 3
Interpreter Ready
Hãy bắt đầu với terminal 2. Đi tới thư mục plutus-apps và chạy nix-shell trước:
Terminal 4
. ~/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Tiếp theo, chúng ta đi đến thư mục plutus-playground-client và chạy:
Terminal 4
[nix-shell:~/plutus-apps/plutus-playground-client]$ npm run start
Nếu thành công, bạn sẽ thấy đầu ra:
Terminal 4
[wdm]: Compiled successfully.
or
[wdm]: Compiled with warnings.
Giữ cả hai Terminal mở và giờ đây chúng ta có thể truy cập Plutus Playground từ trình duyệt.
Mở trình duyệt và truy cập địa chỉ:
https://localhost:8009
Bạn sẽ nhận được một cảnh báo phàn nàn về việc đây là một trang web nguy hiểm, dù sao hãy bỏ qua thông báo để nhấp qua. Giờ đây, bạn có thể biên dịch và chạy thành công hợp đồng quà tặng bằng cách sao chép/dán nó vào Plutus Playground và sử dụng hai nút ở góc trên cùng bên phải: “Biên dịch” và “Mô phỏng”.
Trước khi chúng ta thực hiện mô phỏng của mình, chúng ta cần tìm mã hash của ví thanh toán pubkeyhash
cho ví 2 và 3. Chúng tôi có thể thực hiện việc này:
Prelude week03.Deploy> import Wallet.Emulator
Prelude Wallet.Emulator week03.Deploy>
mockWalletPaymentPubKeyHash $ knownWallet 2
Output:
80a4f45b56b88d1139da23bc4c3c75ec6d32943c087f250b86193ca7
Prelude Wallet.Emulator week03.Deploy>
mockWalletPaymentPubKeyHash $ knownWallet 3
Output:
2e0ad60c3207248cecd47dbde3d752e0aad141d6b8f81ac2c6eca27c
Chúng tôi có thể sao chép/dán các giá trị băm đó vào simulation cho ví 2 và 3.
Chúng tôi cũng cần chuyển đổi các vị trí thành POSIXTime, điều mà chúng ta cũng có thể thực hiện trong Terminal này:
Prelude week03.Deploy> import Ledger.TimeSlot
Prelude Ledger.TimeSlot week03.Deploy> import Data.Default
Prelude Ledger.TimeSlot Data.Default week03.Deploy>
slotToBeginPOSIXTime def 10
Output:
POSIXTime {getPOSIXTime = 1596059101000}
Prelude Ledger.TimeSlot Data.Default week03.Deploy>
slotToBeginPOSIXTime def 20
Output:
POSIXTime {getPOSIXTime = 1596059111000}
Các ví sẽ trông giống như:
Khe Genesis 0 trông giống như:
Khe 1, TX 0:
Khe 2, TX 0:
Khe 3, TX 0:
Slot 12, TX 0:
Slot 12, TX 1:
Số dư cuối kỳ:
Parameterized Contract
Bây giờ chúng ta sẽ xem xét một ví dụ tương tự về hợp đồng trao quyền, ngoại trừ việc chúng ta sẽ chuyển một tham số thay vì một mốc thời gian. Trước tiên chúng ta có thể xem mkValidator, trong đó datum hiện là kiểu unit():
mkValidator :: VestingParam -> () -> () -> ScriptContext -> Bool
mkValidator p () () ctx = traceIfFalse "beneficiary's signature missing" signedByBeneficiary && traceIfFalse "deadline not reached" deadlineReached
where
info :: TxInfo
info = scriptContextTxInfo ctx
signedByBeneficiary :: Bool
signedByBeneficiary = txSignedBy info $ unPaymentPubKeyHash $ beneficiary p
deadlineReached :: Bool
deadlineReached = contains (from $ deadline p) $ txInfoValidRange info
Sau đó mã hóa datum tới kiểu unit:
data Vesting
instance Scripts.ValidatorTypes Vesting where
type instance DatumType Vesting = ()
type instance RedeemerType Vesting = ()
Sửa đổi phần compiler:
typedValidator :: VestingParam -> Scripts.TypedValidator Vesting
typedValidator p = Scripts.mkTypedValidator @Vesting
($$(PlutusTx.compile [|| mkValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode p)
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @() @()
Tiếp theo là mã cho trình xác thực, hàm băm và địa chỉ:
validator :: VestingParam -> Validator
validator = Scripts.validatorScript . typedValidator
valHash :: VestingParam -> Ledger.ValidatorHash
valHash = Scripts.validatorHash . typedValidator
scrAddress :: VestingParam -> Ledger.Address
scrAddress = scriptAddress . validator
Tiếp theo là mã ngoài chuỗi:
data GiveParams = GiveParams
{ gpBeneficiary :: !PaymentPubKeyHash
, gpDeadline :: !POSIXTime
, gpAmount :: !Integer
} deriving (Generic, ToJSON, FromJSON, ToSchema)
type VestingSchema =
Endpoint "give" GiveParams
.\/ Endpoint "grab" POSIXTime
give :: AsContractError e => GiveParams -> Contract w s e ()
give gp = do
let p = VestingParam
{ beneficiary = gpBeneficiary gp
, deadline = gpDeadline gp
}
tx = Constraints.mustPayToTheScript () $ Ada.lovelaceValueOf $ gpAmount gp
ledgerTx <- submitTxConstraints (typedValidator p) tx
void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
logInfo @String $ printf "made a gift of %d lovelace to %s with deadline %s"
(gpAmount gp)
(show $ gpBeneficiary gp)
(show $ gpDeadline gp)
grab :: forall w s e. AsContractError e => POSIXTime -> Contract w s e ()
grab d = do
now <- currentTime
pkh <- ownPaymentPubKeyHash
if now < d
then logInfo @String $ "too early"
else do
let p = VestingParam
{ beneficiary = pkh
, deadline = d
}
utxos <- utxosAt $ scrAddress p
if Map.null utxos
then logInfo @String $ "no gifts available"
else do
let orefs = fst <$> Map.toList utxos
lookups = Constraints.unspentOutputs utxos <>
Constraints.otherScript (validator p)
tx :: TxConstraints Void Void
tx = mconcat [Constraints.mustSpendScriptOutput oref unitRedeemer | oref <- orefs] <>
Constraints.mustValidateIn (from now)
ledgerTx <- submitTxConstraintsWith @Void lookups tx
void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
logInfo @String $ "collected gifts"
endpoints :: Contract () VestingSchema Text ()
endpoints = awaitPromise (give' `select` grab') >> endpoints
where
give' = endpoint @"give" give
grab' = endpoint @"grab" grab
mkSchemaDefinitions ''VestingSchema
mkKnownCurrencies []
Bây giờ chúng ta có thể kiểm tra điều này trong Plutus Playground.
Nhìn vào thiết lập ví:
Khe Genesis 0 trông giống như:
Slot 1, TX 0:
Slot 3, TX 0:
Slot 12, TX 0:
Slot 12, TX 1:
Số dư cuối kỳ:
Cardano Testnet Smartconctact trên mạng Preprod
Mạng thử nghiệm Cardano preprod
Bây giờ chúng ta sẽ xem xét Cardano CLI và cách nó tương tác với testnet. Hy vọng rằng tại thời điểm này, nút cục bộ của bạn hiện đã được đồng bộ hóa từ công việc được thực hiện trong phần “chuẩn bị cho bài giảng 3”.
Giao diện dòng lệnh (CLI) cung cấp một tập hợp các công cụ để tạo khóa, xây dựng giao dịch, tạo chứng chỉ và thực hiện các tác vụ quan trọng khác. Nó được tổ chức theo một hệ thống phân cấp các lệnh con và mỗi cấp độ đi kèm với tài liệu tích hợp sẵn về cú pháp lệnh và các tùy chọn.
Phần này cung cấp tài liệu tham khảo về các lệnh cốt lõi cardano-cli
và các lệnh con liên quan của chúng:
cardano-cli
cardano-cli
The set of cardano-cli
commands include:
address
: payment address commandsstake-address
: stake address commandstransaction
: transaction commandsnode
: node operation commandsstake-pool
: stake pool commandsquery
: node query commands. Commands in this group query the local node whose Unix domain socket is obtained from the CARDANO_NODE_SOCKET_PATH environment variable.genesis
: genesis block commandstext-view
: commands for dealing with text view files that are stored on disk, such as transactions or addressesgovernance
: governance commands
cardano-cli address
The address
command contains the following subcommands:
key-gen
: creates a single address key pairkey-hash
: prints the hash of an address to stdoutbuild
: builds a payment address, with optional delegation to a stake addressbuild-script
: builds a token locking scriptinfo
: prints details about the address
cardano-cli stake-address
The stake-address
command contains the following subcommands:
key-gen
: creates a single address key pairbuild
: builds a stake addresskey-hash
: prints the hash of a stake verification keyregistration-certificate
: creates a registration certificatedelegation-certificate
: creates a stake address delegation certificatederegistration-certificate
: creates a de-registration certificate
cardano-cli transaction
The transaction
command contains the following subcommands:
build-raw
: builds a low-level transaction (uses the--cardano-mode
,--byron-mode
,--shelley-mode
flags)build
: builds an automatically balanced transaction (automatically calculates fees)sign
: signs the transactionassemble
: combines and assembles the transaction witness(es) with a transaction body to create a transactionwitness
: witnesses a transactionsubmit
: submits the transaction to the local node whose Unix domain socket is obtained from the CARANO_NODE_SOCKET_PATH environment variable (uses the--cardano-mode
,--byron-mode
,--shelley-mode
flags)calculate-min-fee
: calculates the minimum fee for the transactioncalculate-min-required-utxo
: calculates the minimum required ADA for a transaction outputhash-script-data
: calculates the hash of script data (datums)txid
: retrieves the transaction IDpolicyid
: retrieves the policy IDview
: pretty prints a transaction
cardano-cli node
The node
command contains the following subcommands:
key-gen
: creates a key pair for a node operator's offline key and a new certificate issue counterkey-gen-KES
: creates a key pair for a node KES operational keykey-gen-VRF
: creates a key pair for a node VRF operational keykey-hash-VRF
: creates a key hash for a node VRF operational keynew-counter
: keeps track of the number of KES evolutions for a given operational certificate hot keyissue-op-cert
: issues a node operational certificate
cardano-cli stake-pool
The stake-pool
command contains the following subcommands:
registration-certificate
: creates a stake pool registration certificatede-registration-certificate
: creates a stake pool de-registration certificateid
: builds pool id from the offline keymetadata-hash
: retrieves the metadata hash
cardano-cli query
The query
command contains the following subcommands:
protocol-parameters
(advanced): retrieves the node's current pool parameters (a raw dump ofLedger.ChainDepState
).tip
: gets the node's current tip (slot number, hash, and block number)stake-pools
: gets the node's current set of stake pool idsutxo
: retrieves the node's current UTxO, filtered by addressledger-state
(advanced): dumps the current state of the node (a raw dump ofLedger.NewEpochState
)stake-distribution
: gets the node's current set of stake pool idsprotocol-state
(advanced): dumps the node's current protocol statestake-address-info
: gets the current delegations and reward accounts filtered by stake address.stake-distribution
: gets the node's current aggregated stake distributionstake-snapshot
(advanced): gets the stake snapshot information for a stake poolpool-params
(advanced): gets the current and future parameters for a stake poolleadership-schedule
: gets the slots in which the node is slot leader for the current or following epochkes-period-info
(advanced): returns diagnostic information about your operational certificate
cardano-cli governance
The governance
command contains the following subcommands:
create-mir-certificate
: creates an MIR (move instantaneous rewards) certificatecreate-update-proposal
: creates an update proposalcreate-genesis-key-certificate
: retrieves the genesis key certificate
cardano-cli genesis
The genesis
command contains the following subcommands:
key-gen-genesis
: creates a genesis key pairkey-gen-delegate
: creates a genesis delegate key pairkey-gen-utxo
: creates a genesis UTxO key pairkey-hash
: prints the identifier, or hash, of a public keyget-ver-key
: derives verification key from a signing keyinitial-addr
: gets the address for an initial UTxO based on the verification keyinitial-txin
: gets the transaction ID for an initial UTxO based on the verification key.create
: creates a genesis file from a genesis template, as well as genesis keys, delegation keys, and spending keys.create-staked
: creates a staked genesis filehash
: retrieves the hash value
unit cardano-cli text-view
Thetext-view
command contains the following subcommand:decode-cbor
: prints a text view file as decoded CBOR.
Để kiểm tra các hợp đồng của chúng ta, trước tiên chúng ta cần tạo các cặp khóa trên mạng thử nghiệm. Chúng ta có thể bắt đầu bằng cách mở một Terminal mới để chạy nix-shell, đảm bảo không đóng nút đồng bộ hóa trong terminal khác:
. ~/.nix-profile/etc/profile.d/nix.sh
~/plutus-apps$ nix-shell
Kiểm tra mạng đã đồng bộ xong chưa bằng lệnh
cardano-cli query tip --testnet-magic 1
Kết quả sẽ thấy như sau là đã đồng bộ xong và có thể chạy thử nghiệm được.
{
"block": 388004,
"epoch": 38,
"era": "Babbage",
"hash": "e65317fa3ef1eef097d251ad31d4d3e8d90198cf7095d4ab9756a42f62d0cda8",
"slot": 15078737,
"syncProgress": "100.00"
}
Chuyển đến thư mục con week03 trong thư mục tiên phong plutus, sau đó bên trong thư mục testnet. Trước tiên, chúng ta sẽ tạo khóa công khai và khóa riêng 01.vkey và 01.skey tương ứng bằng lệnh:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address key-gen --verification-key-file 01.vkey --signing-key-file 01.skey
Bây giờ chúng ta sẽ tạo khóa công khai và khóa riêng thứ hai lần lượt là 02.vkey và 02.skey bằng lện
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address key-gen --verification-key-file 02.vkey --signing-key-file 02.skey
Nhìn vào 01.vkey:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat 01.vkey
Output:
{
"type": "PaymentVerificationKeyShelley_ed25519",
"description": "Payment Verification Key",
"cborHex": "58201dd3552d73e7fef875031da2b2deeacc8cc9d1d70751850408d51a4061dd3e96"
}
Bây giờ chúng ta có thể tạo địa chỉ trên testnet cho 01.vkey và xuất địa chỉ đó vào tệp 01.addr bằng lệnh sau:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address build --payment-verification-key-file 01.vkey --testnet-magic 1 --out-file 01.addr
Nhìn vào 01.addr:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat 01.addr
Output:
addr_test1vreew5etr04emz52u2jdm4fp37q9kx4pq963tt0ahflmdzgn3y7v0
Bây giờ chúng ta có thể tạo địa chỉ trên testnet cho 02.vkey và xuất địa chỉ đó vào tệp 02.addr bằng lệnh sau:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address build --payment-verification-key-file 02.vkey --testnet-magic 1 --out-file 02.addr
Nhìn vào 02.addr:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat 02.addr
Output:
addr_test1vprfsx932ejfehn64vkkchpsu95hw28ae0hudqkk3c50zesxpmzkh
Bây giờ chúng ta cần tạo một số ADA để gửi đến địa chỉ đầu tiên của chúng ta. Điều này có thể được thực hiện từ trang sau bằng cách sử dụng faucet Cardano.
https://testnets.cardano.org/en/testnets/cardano/tools/faucet/
Điều quan trọng cần lưu ý ở đây là địa chỉ của bạn cho 01.addr sẽ khác với địa chỉ được tạo trong hướng dẫn này! Đảm bảo bạn gửi ADA testnet đến địa chỉ bạn đã tạo trong CLI!
Bây giờ chúng ta sẽ có thể truy vấn địa chỉ. Điều quan trọng cần lưu ý ở đây, nút cục bộ của bạn phải được đồng bộ hóa với chuỗi khối vào thời điểm này, nếu không bạn sẽ không thể thấy tiền!
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli query utxo --address $(cat 01.addr) --testnet-magic 1
Output:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
6524545abf8050de175d489e0ec8f9f789e7e975932e528a3b398367caa0a9f8 0 10000000000 lovelace + TxOutDatumNone
Bây giờ chúng ta sẽ gửi một số tiền đến địa chỉ thứ hai của mình bằng cách sử dụng tập lệnh được tạo sẵn send.sh:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat send.sh
Output:
--alonzo-era \
--testnet-magic 1 \
--change-address $(cat 01.addr) \
--tx-in 6524545abf8050de175d489e0ec8f9f789e7e975932e528a3b398367caa0a9f8#0 \
--tx-out "$(cat 02.addr) 10000000 lovelace" \
--out-file tx.body
cardano-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file 01.skey \
--testnet-magic 1 \
--out-file tx.signed
cardano-cli transaction submit \
--testnet-magic 1 \
--tx-file tx.signed
Lưu ý quan trọng, bạn cần thay đổi tx-in
thành hàm băm TxHash
của 01.addr nơi chúng ta đã gửi tiền ở bước trước! Cũng lưu ý rằng nó kết thúc bằng #0 chỉ định chỉ số TxIx
giao dịch ở đây là 0
và chạy:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
./send.sh
Output:
Estimated transaction fee: Lovelace 165545
Transaction successfully submitted.
Sau khi đợi khoảng 20 giây, chúng ta có thể truy vấn địa chỉ đầu tiên để xem tiền đã được gửi chưa:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli query utxo --address $(cat 01.addr) --testnet-magic 1
Output:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
d1c42f58ac97fc1b8a9d2966bdce8687358a475dba9ea9d4083044d258c38d31 1 9989834455 lovelace + TxOutDatumNone
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli query utxo --address $(cat 02.addr) --testnet-magic 1
Output:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
d1c42f58ac97fc1b8a9d2966bdce8687358a475dba9ea9d4083044d258c38d31 0 10000000 lovelace + TxOutDatumNone
Để bắt đầu sử dụng Plutus bằng Cardano-CLI, chúng ta cần tuần tự hóa và ghi vào đĩa các loại Plutus khác nhau. Tuy nhiên, trước tiên chúng ta cần lấy PaymentPubKeyHash
của ví 2:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address key-hash --payment-verification-key-file 02.vkey --out-file 02.pkh
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat 02.pkh
Output:
469818b156649cde7aab2d6c5c30e1697728fdcbefc682d68e28f166
tạo biến
signer_hash="469818b156649cde7aab2d6c5c30e1697728fdcbefc682d68e28f166"
Nhìn vào Deploy.hs, chúng ta cần thay thế hàm băm khóa công khai thanh toán của người thụ hưởng bằng hàm băm mà chúng ta đã tạo ở trên. Lưu ý rằng hàm băm của bạn sẽ khác với hàm băm trong hướng dẫn này. Chúng tôi cũng thay thế thời hạn bằng một thời gian trong tương lai. (bạn có thể sử dụng Epoch Converter để tìm dấu thời gian trong tương lai)
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Week03.Deploy
( writeJSON
, writeValidator
, writeUnit
, writeVestingValidator
) where
import Cardano.Api
import Cardano.Api.Shelley (PlutusScript (..))
import Codec.Serialise (serialise)
import Data.Aeson (encode)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Short as SBS
import PlutusTx (Data (..))
import qualified PlutusTx
import qualified Ledger
import Week03.Parameterized
dataToScriptData :: Data -> ScriptData
dataToScriptData (Constr n xs) = ScriptDataConstructor n $ dataToScriptData <$> xs
dataToScriptData (Map xs) = ScriptDataMap [(dataToScriptData x, dataToScriptData y) | (x, y) <- xs]
dataToScriptData (List xs) = ScriptDataList $ dataToScriptData <$> xs
dataToScriptData (I n) = ScriptDataNumber n
dataToScriptData (B bs) = ScriptDataBytes bs
writeJSON :: PlutusTx.ToData a => FilePath -> a -> IO ()
writeJSON file = LBS.writeFile file . encode . scriptDataToJson ScriptDataJsonDetailedSchema . dataToScriptData . PlutusTx.toData
writeValidator :: FilePath -> Ledger.Validator -> IO (Either (FileError ()) ())
writeValidator file = writeFileTextEnvelope @(PlutusScript PlutusScriptV1) file Nothing . PlutusScriptSerialised . SBS.toShort . LBS.toStrict . serialise . Ledger.unValidatorScript
writeUnit :: IO ()
writeUnit = writeJSON "testnet/unit.json" ()
writeVestingValidator :: IO (Either (FileError ()) ())
writeVestingValidator = writeValidator "testnet/vesting.plutus" $ validator $ VestingParam
{ beneficiary = Ledger.PaymentPubKeyHash "469818b156649cde7aab2d6c5c30e1697728fdcbefc682d68e28f166"
, deadline = 15300036
}
Biên dịch smartcontract plutus
Mở cabal repl trong Terminal khác và chạy:
cd ~plutus-apps/
nix-shell
cd ~/plutus-pioneer-program/code/week03
[nix-shell:~/plutus-pioneer-program/code/week03]$ cabal repl
Prelude week03.Deploy> writeUnit
Prelude week03.Deploy> writeVestingValidator
Output:
Right ()
Tạo giao dịch với SmartContract
B1 Tạo địa chỉ ví smartcontract
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli address build --payment-script-file vesting.plutus --testnet-magic 1 --out-file vesting.addr
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cat vesting.addr
Output:
addr_test1wq43pks2g94gzh2rqgjrf5ee6l7pzhju8vwpjpca2m8dchsn6t6a8
B2: Lấy Txin của địa chỉ gửi
cardano-cli query utxo --address $(cat 01.addr) --testnet-magic 1
TxHash TxIx Amount
--------------------------------------------------------------------------------------
f9f0655084b8ab67df1ac1fb5c016379ea03068505bd1d0e97b94bdd4b4fe734 1 9989834455 lovelace + TxOutDatumNone
tạo biến
TXIN1="f9f0655084b8ab67df1ac1fb5c016379ea03068505bd1d0e97b94bdd4b4fe734#1"
B3: tạo giao dịch gửi tADA lên Smartcontract
Nhìn vào tập lệnh give.sh, chúng ta thay đổi tx-in
thành địa chỉ query 1 utxo mà chúng ta đã tạo trước đó:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
TXIN1="f9f0655084b8ab67df1ac1fb5c016379ea03068505bd1d0e97b94bdd4b4fe734#1"
cardano-cli transaction build \
--alonzo-era \
--testnet-magic 1 \
--change-address $(cat 01.addr) \
--tx-in $TXIN1 \
--tx-out "$(cat vesting.addr) 2000002 lovelace" \
--tx-out-datum-hash-file unit.json \
--out-file tx.body
cardano-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file 01.skey \
--testnet-magic 1 \
--out-file tx.signed
cardano-cli transaction submit \
--testnet-magic 1 \
--tx-file tx.signed
Bây giờ chúng ta có thể xem tập lệnh grab.sh. Chúng ta sẽ thay đổi hàm băm Txin
thành hàm băm của vesting.addr mà chúng ta đã gửi lên. Chúng tôi sẽ thay đổi tài sản thế chấp thành hàm băm của 02.addr từ trước đó. Chúng tôi cũng sẽ thay đổi hàm băm của người ký thành hàm băm của 02.pkh. Cuối cùng, chúng ta cần thay đổi invalid-before
để phản ánh vị trí hiện tại; mà chúng ta đã truy vấn ở bước cuối cùng:
B4: Lấy Txin của địa chỉ Smartcontract vừa gửi lên
cardano-cli query utxo --address $(cat vesting.addr) --testnet-magic 1
TXINSM="5d3ddaf94078cc7d3ee7e3a42e2676576a689e216d53e4d72b0301624de0f5c1#0"
B5: Lấy slot hiện tại
cardano-cli query tip --testnet-magic 1
{
"block": 398631,
"epoch": 39,
"era": "Babbage",
"hash": "348a710d1eab461b20b94c36a41f8d8ca537ce6054658ce32d04e04b725737da",
"slot": 15300036,
"syncProgress": "100.00"
}
B6: tải protocol.json
cardano-cli query protocol-parameters --testnet-magic 1 --out-file protocol.json
B7: Lấy Txin của địa chỉ nhận (trả phí)
cardano-cli query utxo --address $(cat 02.addr) --testnet-magic 1
TXIN2="d1c42f58ac97fc1b8a9d2966bdce8687358a475dba9ea9d4083044d258c38d31#0"
B8: Ví người thụ hưởng nhận quà (tADA)
thay đổi các thông số --tx-in; tx-in-collateral; --invalid-before; --required-signer-hash (vẫn hợp đồng cũ thì không đổi)
TXINSM="5d3ddaf94078cc7d3ee7e3a42e2676576a689e216d53e4d72b0301624de0f5c1#0"
TXIN2="d1c42f58ac97fc1b8a9d2966bdce8687358a475dba9ea9d4083044d258c38d31#0"
signer_hash="469818b156649cde7aab2d6c5c30e1697728fdcbefc682d68e28f166"
cardano-cli transaction build \
--alonzo-era \
--testnet-magic 1 \
--change-address $(cat 02.addr) \
--tx-in $TXINSM \
--tx-in-script-file vesting.plutus \
--tx-in-datum-file unit.json \
--tx-in-redeemer-file unit.json \
--tx-in-collateral $TXIN2 \
--required-signer-hash $signer_hash \
--invalid-before 15300036 \
--protocol-params-file protocol.json \
--out-file tx.body
cardano-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file 02.skey \
--testnet-magic 1 \
--out-file tx.signed
cardano-cli transaction submit \
--testnet-magic 1 \
--tx-file tx.signed
Nếu sai thì sẽ báo lỗi sau
Command failed: transaction build Error: The followings tx inputs were expected to be key witnessed but are actually script witnessed: ["6e7bd85be4c37d9c8deb70236bdd9c61f22812ec5779055778e53a762b9ac54b#0"]
Nếu không
Output:
Estimated transaction fee: Lovelace 365397
Transaction successfully submitted.
Sau khi đợi khoảng 20 giây, chúng ta có thể truy vấn địa chỉ thứ hai để xem cuối cùng đã nhận được tiền từ món quà chưa:
[nix-shell:~/plutus-pioneer-program/code/week03/testnet]$
cardano-cli query utxo --address $(cat 02.addr) --testnet-magic 1
TxHash TxIx Amount
--------------------------------------------------------------------------------------
582e65894b72410057dce6cdabb5be18d401a2e124732c1c2f8272e5f0e1699a 0 1639485 lovelace + TxOutDatumNone
d1c42f58ac97fc1b8a9d2966bdce8687358a475dba9ea9d4083044d258c38d31 0 10000000 lovelace + TxOutDatumNone
Địa chỉ Smartcontract đã không còn UTxO
cardano-cli query utxo --address $(cat vesting.addr) --testnet-magic 1
TxHash TxIx Amount
--------------------------------------------------------------------------------------
Homework Part 1
- Điều này sẽ hợp lệ nếu một trong hai người thụ hưởng1 đã ký giao dịch và vị trí hiện tại là trước hoặc vào thời hạn
- Hoặc nếu người thụ hưởng2 đã ký giao dịch và thời hạn đã qua.
Phần đầu tiên của bài tập về nhà, chúng ta cần viết một hàm trình xác thực sẽ trả về giá trị true nếu người thụ hưởng1 đã ký giao dịch và vị trí hiện tại là trước hoặc vào thời hạn chót. Nó cũng phải trả về true nếu người thụ hưởng2 đã ký giao dịch và thời hạn đã qua.
Trước tiên chúng ta cần chuyển datum (dat) và context (ctx) vào trình xác thực:
mkValidator :: VestingDatum -> () -> ScriptContext -> Bool
mkValidator dat () ctx
Sau đó, chúng ta cần viết logic thỏa mãn cả hai điều kiện đã được mô tả ở trên:
| (unPaymentPubKeyHash (beneficiary1 dat) `elem` sigs) && (to (deadline dat) `contains` range) = True
| (unPaymentPubKeyHash (beneficiary2 dat) `elem` sigs) && (from (1 + deadline dat) `contains` range) = True
| otherwise = False
where
info :: TxInfo
info = scriptContextTxInfo ctx
sigs :: [PubKeyHash]
sigs = txInfoSignatories info
range :: POSIXTimeRange
range = txInfoValidRange info
Chúng tôi kiểm tra cả hai điều kiện, một điều kiện mà người thụ hưởng1 ký và nó ở trước hoặc vào thời hạn, và cũng là trường hợp người thụ hưởng2 ký và nó ở sau thời hạn. Nếu không, tất cả những thứ khác trả về false. Mã sẽ giống như:
{-# INLINABLE mkValidator #-}
-- This should validate if either beneficiary1 has signed the transaction and the current slot is before or at the deadline
-- or if beneficiary2 has signed the transaction and the deadline has passed.
mkValidator :: VestingDatum -> () -> ScriptContext -> Bool
mkValidator dat () ctx
| (unPaymentPubKeyHash (beneficiary1 dat) `elem` sigs) && (to (deadline dat) `contains` range) = True
| (unPaymentPubKeyHash (beneficiary2 dat) `elem` sigs) && (from (1 + deadline dat) `contains` range) = True
| otherwise = False
where
info :: TxInfo
info = scriptContextTxInfo ctx
sigs :: [PubKeyHash]
sigs = txInfoSignatories info
range :: POSIXTimeRange
range = txInfoValidRange info
Thử nghiệm trong Plutus Playground ta thấy:
Slot 0, Tx 0
Slot 1, Tx 0
Slot 1, Tx 1
Slot 6, Tx 0
Slot 7, Tx 0
Số dư cuối kỳ:
Homework Part 2
Phần thứ hai của bài tập về nhà, chúng ta cần viết hàm trình xác thực cho hợp đồng trao quyền, thay vào đó chúng ta chuyển pubkeyhash làm tham số và POSIXTime như là datum.
mkValidator :: PaymentPubKeyHash -> POSIXTime -> () -> ScriptContext -> Bool
Chúng tôi có thể bắt đầu bằng cách kiểm tra xem chữ ký của người thụ hưởng có tồn tại hay không và thời hạn đã đến chưa. Đầu tiên chúng ta vượt qua:
mkValidator pkh s () ctx =
Bây giờ chúng ta lấy logic như được mô tả ở trên:
traceIfFalse "beneficiary's signature missing" checkSig &&
traceIfFalse "deadline not reached" checkDeadline
where
info :: TxInfo
info = scriptContextTxInfo ctx
checkSig :: Bool
checkSig = unPaymentPubKeyHash pkh `elem` txInfoSignatories info
checkDeadline :: Bool
checkDeadline = from s `contains` txInfoValidRange info
Implementing the compilation: Thực hiện biên soạn:
typedValidator :: PaymentPubKeyHash -> Scripts.TypedValidator Vesting
typedValidator p = Scripts.mkTypedValidator @Vesting
($$(PlutusTx.compile [|| mkValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode p)
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @POSIXTime @()
Cuối cùng là mã cho trình xác thực và địa chỉ. Ở đây chúng ta cần thêm PaymentPubKeyHash:
validator :: PaymentPubKeyHash -> Validator
validator = Scripts.validatorScript . typedValidator
scrAddress :: PaymentPubKeyHash -> Ledger.Address
scrAddress = scriptAddress . validator
Mã cuối sẽ giống như này:
{-# INLINABLE mkValidator #-}
mkValidator :: PaymentPubKeyHash -> POSIXTime -> () -> ScriptContext -> Bool
mkValidator pkh s () ctx =
traceIfFalse "beneficiary's signature missing" checkSig &&
traceIfFalse "deadline not reached" checkDeadline
where
info :: TxInfo
info = scriptContextTxInfo ctx
checkSig :: Bool
checkSig = unPaymentPubKeyHash pkh `elem` txInfoSignatories info
checkDeadline :: Bool
checkDeadline = from s `contains` txInfoValidRange info
data Vesting
instance Scripts.ValidatorTypes Vesting where
type instance DatumType Vesting = POSIXTime
type instance RedeemerType Vesting = ()
typedValidator :: PaymentPubKeyHash -> Scripts.TypedValidator Vesting
typedValidator p = Scripts.mkTypedValidator @Vesting
($$(PlutusTx.compile [|| mkValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode p)
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @POSIXTime @()
validator :: PaymentPubKeyHash -> Validator
validator = Scripts.validatorScript . typedValidator
scrAddress :: PaymentPubKeyHash -> Ledger.Address
scrAddress = scriptAddress . validator
Mô phỏng Plutus Playground sẽ giống như sau:
Slot 0, Tx 0
Slot 1, Tx 0
Slot 2, Tx 0
Slot 12, Tx 0
Slot 22, Tx 0
Số dư cuối kỳ: