Advertisement
Guest User

Untitled

a guest
Apr 21st, 2019
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.86 KB | None | 0 0
  1. -- | Vesting scheme as a PLC contract
  2.  
  3. import Control.Monad (void)
  4. import qualified Data.Map as Map
  5. import qualified Data.Set as Set
  6.  
  7. import qualified Language.PlutusTx as P
  8. import Ledger (Address, DataScript(..), RedeemerScript(..), Signature, Slot, TxOutRef, TxIn, ValidatorScript(..))
  9. import qualified Ledger as L
  10. import Ledger.Ada (Ada)
  11. import qualified Ledger.Ada as Ada
  12. import qualified Ledger.Ada.TH as ATH
  13. import qualified Ledger.Interval as Interval
  14. import qualified Ledger.Slot as Slot
  15. import qualified Ledger.Validation as V
  16. import qualified Ledger.Value as Value
  17. import Wallet (WalletAPI(..), WalletDiagnostics, PubKey)
  18. import qualified Wallet as W
  19. import qualified Wallet.API as WAPI
  20. import qualified Wallet.Emulator.Types as EM
  21. import Playground.Contract
  22.  
  23. {- |
  24. A simple vesting scheme. Money is locked by a contract and may only be
  25. retrieved after some time has passed.
  26.  
  27. This is our first example of a contract that covers multiple transactions,
  28. with a contract state that changes over time.
  29.  
  30. In our vesting scheme the money will be released in two _tranches_ (parts):
  31. A smaller part will be available after an initial number of slots have
  32. passed, and the entire amount will be released at the end. The owner of the
  33. vesting scheme does not have to take out all the money at once: They can take out any amount up to the total that has been released so far. The remaining funds stay locked and can be retrieved later.
  34.  
  35. Let's start with the data types.
  36.  
  37. -}
  38.  
  39. -- | Tranche of a vesting scheme.
  40. data VestingTranche = VestingTranche {
  41. vestingTrancheDate :: Slot,
  42. -- ^ When this tranche is released
  43. vestingTrancheAmount :: Ada
  44. -- ^ How much money is locked in this tranche
  45. } deriving (Generic, ToJSON, FromJSON, ToSchema)
  46.  
  47. P.makeLift ''VestingTranche
  48.  
  49. -- | A vesting scheme consisting of two tranches. Each tranche defines a date
  50. -- (slot) after which an additional amount of money can be spent.
  51. data Vesting = Vesting {
  52. vestingTranche1 :: VestingTranche,
  53. -- ^ First tranche
  54.  
  55. vestingTranche2 :: VestingTranche,
  56. -- ^ Second tranche
  57.  
  58. vestingOwner :: PubKey
  59. -- ^ The recipient of the scheme (who is authorised to take out money once
  60. -- it has been released)
  61. } deriving (Generic, ToJSON, FromJSON, ToSchema)
  62.  
  63. P.makeLift ''Vesting
  64.  
  65. -- | The total amount of Ada locked by a vesting scheme
  66. totalVested :: Vesting -> Ada
  67. totalVested (Vesting l r _) = Ada.plus (vestingTrancheAmount l) (vestingTrancheAmount r)
  68.  
  69. {- |
  70.  
  71. What should our data and redeemer scripts be? The vesting scheme only has a
  72. single piece of information that we need to keep track of, namely how much
  73. money is still locked in the contract. We can get this information from the
  74. contract's transaction output, so we don't need to store it in the data
  75. script. The type of our data script is therefore `()`.
  76.  
  77. The redeemer script should carry some proof that the retriever of the funds
  78. is indeed the `vestingOwner` that was specified in the contract. This proof
  79. takes the form of a transaction hash signed by the `vestingOwner`'s private
  80. key. For this we use the type 'Ledger.Crypto.Signature'
  81.  
  82. That gives our validator script the signature
  83.  
  84. `Vesting -> Signature -> () -> PendingTx -> ()`
  85.  
  86. -}
  87.  
  88. -- | The validator script
  89. vestingValidator :: Vesting -> ValidatorScript
  90. vestingValidator v = ValidatorScript val where
  91. val = L.applyScript inner (L.lifted v)
  92. inner = $$(L.compileScript [|| \(scheme :: Vesting) () () (p :: V.PendingTx) ->
  93. let
  94.  
  95. Vesting tranche1 tranche2 owner = scheme
  96. VestingTranche d1 a1 = tranche1
  97. VestingTranche d2 a2 = tranche2
  98.  
  99. V.PendingTx _ _ _ _ _ range _ _ = p
  100. -- range :: SlotRange, validity range of the pending transaction
  101.  
  102. -- We need the hash of this validator script in order to ensure
  103. -- that the pending transaction locks the remaining amount of funds
  104. -- at the contract address.
  105. ownHash = $$(V.ownHash) p
  106.  
  107. -- The total amount of Ada that has been vested:
  108. totalAmount :: Ada
  109. totalAmount = $$(ATH.plus) a1 a2
  110.  
  111. -- It will be useful to know the amount of money that has been
  112. -- released so far. This means we need to check the current slot
  113. -- against the slots 'd1' and 'd2', defined in 'tranche1' and
  114. -- 'tranche2' respectively. But the only indication of the current
  115. -- time that we have is the 'range' value of the pending
  116. -- transaction 'p', telling us that the current slot is one of the
  117. -- slots contained in 'range'.
  118. --
  119. -- We can think of 'd1' as an interval as well: It is
  120. -- the open-ended interval starting with slot 'd1'. At any point
  121. -- during this interval we may take out up to 'a1' Ada.
  122. d1Intvl = $$(Interval.from) d1
  123.  
  124. -- Likewise for 'd2'
  125. d2Intvl = $$(Interval.from) d2
  126.  
  127. -- Now we can compare the validity range 'range' against our two
  128. -- intervals. If 'range' is completely contained in 'd1Intvl', then
  129. -- we know for certain that the current slot is in 'd1Intvl', so the
  130. -- amount 'a1' of the first tranche has been released.
  131. inD1Intvl = $$(Slot.contains) d1Intvl range
  132.  
  133. -- Likewise for 'd2'
  134. inD2Intvl = $$(Slot.contains) d2Intvl range
  135.  
  136. released :: Ada
  137. released
  138. -- to compute the amount that has been released we need to
  139. -- consider three cases:
  140.  
  141. -- If we are in d2Intvl then the current slot is greater than
  142. -- or equal to 'd2', so everything has been released:
  143. | inD2Intvl = totalAmount
  144.  
  145. -- If we are not in d2Intvl but in d1Intvl then only the first
  146. -- tranche 'a1' has been released:
  147. | inD1Intvl = a1
  148.  
  149. -- Otherwise nothing has been released yet
  150. | True = $$(ATH.zero)
  151.  
  152. -- And the following amount has not been released yet:
  153. unreleased :: Ada
  154. unreleased = $$(ATH.minus) totalAmount released
  155.  
  156. -- To check whether the withdrawal is legitimate we need to
  157. -- 1. Ensure that the amount taken out does not exceed the current
  158. -- limit
  159. -- 2. Compare the provded signature with the public key of the
  160. -- vesting owner
  161. -- We will call these conditions con1 and con2.
  162.  
  163. -- con1 is true if the amount that remains locked in the contract
  164. -- is greater than or equal to 'unreleased'. We use the
  165. -- `adaLockedBy` function to get the amount of Ada paid by pending
  166. -- transaction 'p' to the script address 'ownHash'.
  167. con1 :: Bool
  168. con1 =
  169. let remainsLocked = $$(V.adaLockedBy) p ownHash
  170. in $$(ATH.geq) remainsLocked unreleased
  171.  
  172. -- con2 is true if the scheme owner has signed the pending
  173. -- transaction 'p'.
  174. con2 :: Bool
  175. con2 = $$(V.txSignedBy) p owner
  176.  
  177. in
  178.  
  179. if $$(P.and) con1 con2
  180. then ()
  181. else $$(P.error) ($$(P.traceH) "Cannot withdraw" ())
  182.  
  183. ||])
  184.  
  185. contractAddress :: Vesting -> Address
  186. contractAddress vst = L.scriptAddress (vestingValidator vst)
  187.  
  188. {- |
  189.  
  190. We need three endpoints:
  191.  
  192. * 'vestFunds' to lock the funds in a vesting scheme
  193. * 'registerVestingScheme', used by the owner to start watching the scheme's address
  194. * 'withdraw', used by the owner to take out some funds.
  195.  
  196. The first two are very similar to endpoints we defined for earlier
  197. contracts.
  198.  
  199. -}
  200.  
  201. vestFunds :: (Monad m, WalletAPI m) => Vesting -> m ()
  202. vestFunds vst = do
  203. let amt = Ada.toValue (totalVested vst)
  204. adr = contractAddress vst
  205. dataScript = DataScript (L.lifted ())
  206. W.payToScript_ W.defaultSlotRange adr amt dataScript
  207.  
  208. registerVestingScheme :: (WalletAPI m) => Vesting -> m ()
  209. registerVestingScheme vst = startWatching (contractAddress vst)
  210.  
  211. {- |
  212.  
  213. The last endpoint, `withdraw`, is different. We need to create a
  214. transaction that spends the contract's current unspent transaction output
  215. *and* puts the Ada that remains back at the script address.
  216.  
  217. -}
  218. withdraw :: (Monad m, WalletAPI m) => Vesting -> Ada -> m ()
  219. withdraw vst vl = do
  220.  
  221. let address = contractAddress vst
  222. validator = vestingValidator vst
  223.  
  224. -- We are going to use the wallet API to build the transaction "by hand",
  225. -- that is without using 'collectFromScript'.
  226. -- The signature of 'createTxAndSubmit' is
  227. -- 'SlotRange -> Set.Set TxIn -> [TxOut] -> m Tx'. So we need a slot range,
  228. -- a set of inputs and a list of outputs.
  229.  
  230. -- The transaction's validity range should begin with the current slot and
  231. -- last indefinitely.
  232. range <- fmap WAPI.intervalFrom WAPI.slot
  233.  
  234. -- The input should be the UTXO of the vesting scheme. We can get the
  235. -- outputs at an address (as far as they are known by the wallet) with
  236. -- `outputsAt`, which returns a map of 'TxOutRef' to 'TxOut'.
  237. utxos <- WAPI.outputsAt address
  238.  
  239. let
  240. -- the redeemer script with the unit value ()
  241. redeemer = RedeemerScript (L.lifted ())
  242.  
  243. -- Turn the 'utxos' map into a set of 'TxIn' values
  244. mkIn :: TxOutRef -> TxIn
  245. mkIn r = L.scriptTxIn r validator redeemer
  246.  
  247. ins = Set.map mkIn (Map.keysSet utxos)
  248.  
  249. -- Our transaction has either one or two outputs.
  250. -- If the scheme is finished (no money is left in it) then
  251. -- there is only one output, a pay-to-pubkey output owned by
  252. -- us.
  253. -- If any money is left in the scheme then there will be an additional
  254. -- pay-to-script output locked by the vesting scheme's validator script
  255. -- that keeps the remaining value.
  256.  
  257. -- We can create a public key output to our own key with 'ownPubKeyTxOut'.
  258. ownOutput <- W.ownPubKeyTxOut (Ada.toValue vl)
  259.  
  260. -- Now to compute the difference between 'vl' and what is currently in the
  261. -- scheme:
  262. let
  263. currentlyLocked = Map.foldr (\txo vl' -> vl' `Value.plus` L.txOutValue txo) Value.zero utxos
  264. remaining = currentlyLocked `Value.minus` (Ada.toValue vl)
  265.  
  266. otherOutputs = if Value.eq Value.zero remaining
  267. then []
  268. else [L.scriptTxOut remaining validator (DataScript (L.lifted ()))]
  269.  
  270. -- Finally we have everything we need for `createTxAndSubmit`
  271. _ <- WAPI.createTxAndSubmit range ins (ownOutput:otherOutputs)
  272.  
  273. pure ()
  274.  
  275. $(mkFunctions ['vestFunds, 'registerVestingScheme, 'withdraw])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement