Skip to content

Commit

Permalink
Fixes and better errors
Browse files Browse the repository at this point in the history
  • Loading branch information
amirmrad committed Jan 14, 2025
1 parent 8453053 commit 320b7ec
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 27 deletions.
2 changes: 2 additions & 0 deletions src/lib/Wst/AppError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ data AppError era =
| GlobalParamsNodeNotFound -- ^ The node with the global parameters was not found
| BalancingError (CoinSelection.BalanceTxError era)
| BlockfrostErr BlockfrostError
| NoTokensToSeize -- ^ No tokens to seize
| DuplicateBlacklistNode -- ^ Attempting to add a duplicate blacklist node
| TransferBlacklistedCredential Credential -- ^ Attempting to transfer funds from a blacklisted address
| SubmitError (ValidationError era)
deriving stock (Show)
13 changes: 11 additions & 2 deletions src/lib/Wst/Offchain/BuildTx/TransferLogic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ where
import Cardano.Api qualified as C
import Cardano.Api.Shelley qualified as C
import Control.Lens (over, (^.))
import Control.Monad (when)
import Control.Monad.Except (MonadError (throwError))
import Control.Monad.Reader (MonadReader, asks)
import Convex.BuildTx (MonadBuildTx (addTxBuilder), TxBuilder (TxBuilder),
Expand Down Expand Up @@ -44,7 +45,7 @@ import SmartTokens.Contracts.ExampleTransferLogic (BlacklistProof (..))
import SmartTokens.Types.ProtocolParams
import SmartTokens.Types.PTokenDirectory (BlacklistNode (..),
DirectorySetNode (..))
import Wst.AppError (AppError (TransferBlacklistedCredential))
import Wst.AppError (AppError (DuplicateBlacklistNode, TransferBlacklistedCredential))
import Wst.Offchain.BuildTx.ProgrammableLogic (issueProgrammableToken,
seizeProgrammableToken,
transferProgrammableToken)
Expand Down Expand Up @@ -91,7 +92,7 @@ initBlacklist = Utils.inBabbage @era $ do
opPkh <- asks (fst . Env.bteOperator . Env.operatorEnv)
addRequiredSignature opPkh

insertBlacklistNode :: forall era env m. (MonadReader env m, Env.HasOperatorEnv era env, Env.HasTransferLogicEnv env, C.IsBabbageBasedEra era, C.HasScriptLanguageInEra C.PlutusScriptV3 era, MonadBuildTx era m) => C.PaymentCredential -> [UTxODat era BlacklistNode]-> m ()
insertBlacklistNode :: forall era env m. (MonadReader env m, Env.HasOperatorEnv era env, Env.HasTransferLogicEnv env, C.IsBabbageBasedEra era, C.HasScriptLanguageInEra C.PlutusScriptV3 era, MonadBuildTx era m, MonadError (AppError era) m) => C.PaymentCredential -> [UTxODat era BlacklistNode]-> m ()
insertBlacklistNode cred blacklistNodes = Utils.inBabbage @era $ do
-- mint new blacklist token
mintingScript <- asks (Env.tleBlacklistMintingScript . Env.transferLogicEnv)
Expand Down Expand Up @@ -119,6 +120,9 @@ insertBlacklistNode cred blacklistNodes = Utils.inBabbage @era $ do
newPrevNodeDatum = C.TxOutDatumInline C.babbageBasedEra $ C.toHashableScriptData newPrevNode
newPrevNodeOutput = C.TxOut prevAddr prevVal newPrevNodeDatum C.ReferenceScriptNone

when (blnKey prevNode == blnKey newNode)
$ throwError DuplicateBlacklistNode

-- spend previous node
spendingScript <- asks (Env.tleBlacklistSpendingScript . Env.transferLogicEnv)
spendPlutusInlineDatum prevNodeRef spendingScript ()
Expand Down Expand Up @@ -330,6 +334,7 @@ registerTransferScripts = case C.babbageBasedEra @era of
C.BabbageEraOnwardsConway -> Utils.inConway @era $ do
transferMintingScript <- asks (Env.tleMintingScript . Env.transferLogicEnv)
transferSpendingScript <- asks (Env.tleTransferScript . Env.transferLogicEnv)
transferSeizeSpendingScript <- asks (Env.tleIssuerScript . Env.transferLogicEnv)

let
hshMinting = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferMintingScript
Expand All @@ -338,5 +343,9 @@ registerTransferScripts = case C.babbageBasedEra @era of
hshSpending = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferSpendingScript
credSpending = C.StakeCredentialByScript hshSpending

hshSeizeSpending = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferSeizeSpendingScript
credSeizeSpending = C.StakeCredentialByScript hshSeizeSpending

addConwayStakeCredentialCertificate credMinting
addConwayStakeCredentialCertificate credSpending
addConwayStakeCredentialCertificate credSeizeSpending
18 changes: 14 additions & 4 deletions src/lib/Wst/Offchain/Endpoints/Deployment.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ module Wst.Offchain.Endpoints.Deployment(
import Cardano.Api (Quantity)
import Cardano.Api.Shelley qualified as C
import Control.Monad (when)
import Control.Monad.Except (MonadError)
import Control.Monad.Except (MonadError (..))
import Control.Monad.Reader (MonadReader, asks)
import Convex.BuildTx qualified as BuildTx
import Convex.Class (MonadBlockchain, MonadUtxoQuery)
import Convex.CoinSelection qualified
import Data.Foldable (maximumBy)
import Data.Function (on)
import GHC.IsList (IsList (..))
import SmartTokens.Core.Scripts (ScriptTarget (..))
import SmartTokens.Types.PTokenDirectory (DirectorySetNode (..))
import Wst.AppError (AppError)
import Wst.AppError (AppError (NoTokensToSeize))
import Wst.Offchain.BuildTx.DirectorySet (InsertNodeArgs (inaNewKey))
import Wst.Offchain.BuildTx.DirectorySet qualified as BuildTx
import Wst.Offchain.BuildTx.ProgrammableLogic qualified as BuildTx
Expand All @@ -38,7 +39,6 @@ import Wst.Offchain.Query (UTxODat (..))
import Wst.Offchain.Query qualified as Query



{-| Build a transaction that deploys the directory and global params. Returns the
transaction and the 'TxIn' that was selected for the one-shot NFTs.
-}
Expand Down Expand Up @@ -216,7 +216,17 @@ seizeCredentialAssetsTx :: forall era env m.
seizeCredentialAssetsTx sanctionedCred = do
opPkh <- asks (fst . Env.bteOperator . Env.operatorEnv)
directory <- Query.registryNodes @era
seizeTxo <- head <$> Query.userProgrammableOutputs sanctionedCred
let getTxOutValue (C.TxOut _a v _d _r) = v
-- simple fold to choose the UTxO with the most total assets
nonAda v = foldl (\acc -> \case
(C.AdaAssetId, _) -> acc
(_aid, q) -> acc + q
) 0 $ toList v

getNonAdaTokens = nonAda . C.txOutValueToValue . getTxOutValue . uOut
seizeTxo <- maximumBy (compare `on` getNonAdaTokens) <$> Query.userProgrammableOutputs sanctionedCred
when (getNonAdaTokens seizeTxo == 0) $
throwError NoTokensToSeize
paramsTxIn <- Query.globalParamsNode @era
(tx, _) <- Env.balanceTxEnv_ $ do
BuildTx.seizeSmartTokens paramsTxIn seizeTxo (C.PaymentCredentialByKey opPkh) directory
Expand Down
8 changes: 5 additions & 3 deletions src/lib/Wst/Server.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

{-| servant server for stablecoin POC
-}
Expand Down Expand Up @@ -130,7 +131,8 @@ queryBlacklistedNodes _ (SerialiseAddress addr) = do
. P.fromBuiltin
. blnKey
. uDatum
Env.withEnv $ Env.withTransfer transferLogic (fmap (fmap getHash) (Query.blacklistNodes @era))
nonHeadNodes (P.fromBuiltin . blnKey . uDatum -> hsh) = hsh /= ""
Env.withEnv $ Env.withTransfer transferLogic (fmap getHash . filter nonHeadNodes <$> (Query.blacklistNodes @era))

txOutValue :: C.IsMaryBasedEra era => C.TxOut C.CtxUTxO era -> C.Value
txOutValue = L.view (L._TxOut . L._2 . L._TxOutValue)
Expand Down
37 changes: 19 additions & 18 deletions src/test/unit/Wst/Test/UnitTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,10 @@ deployDirectorySet = do
pure scriptRoot

insertDirectoryNode :: (MonadUtxoQuery m, MonadBlockchain C.ConwayEra m, MonadFail m) => DirectoryScriptRoot -> m ()
insertDirectoryNode scriptRoot = failOnError $ Env.withEnv $ do
asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ do
Endpoints.insertNodeTx dummyNodeArgs >>= void . sendTx . signTxOperator admin
Query.registryNodes @C.ConwayEra
>>= void . expectN 2 "registry outputs"
insertDirectoryNode scriptRoot = failOnError $ Env.withEnv $ asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ do
Endpoints.insertNodeTx dummyNodeArgs >>= void . sendTx . signTxOperator admin
Query.registryNodes @C.ConwayEra
>>= void . expectN 2 "registry outputs"

{-| Issue some tokens with the "always succeeds" validator
-}
Expand Down Expand Up @@ -190,9 +189,8 @@ blacklistTransfer = failOnError $ Env.withEnv $ do

aid <- issueTransferLogicProgrammableToken scriptRoot

asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ do
Endpoints.deployBlacklistTx
>>= void . sendTx . signTxOperator admin
asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ Endpoints.deployBlacklistTx
>>= void . sendTx . signTxOperator admin

opPkh <- asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ do
opPkh <- asks (fst . Env.bteOperator . Env.operatorEnv)
Expand All @@ -202,13 +200,11 @@ blacklistTransfer = failOnError $ Env.withEnv $ do

transferLogic <- Env.withDirectoryFor scriptRoot $ Env.transferLogicForDirectory (C.verificationKeyHash . Operator.verificationKey . Operator.oPaymentKey $ admin)

asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ do
Endpoints.blacklistCredentialTx userPaymentCred
>>= void . sendTx . signTxOperator admin
asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ Endpoints.blacklistCredentialTx userPaymentCred
>>= void . sendTx . signTxOperator admin

asWallet Wallet.w2 $ Env.withDirectoryFor scriptRoot $ Env.withTransfer transferLogic $ do
Endpoints.transferSmartTokensTx aid 30 (C.PaymentCredentialByKey opPkh)
>>= void . sendTx . signTxOperator (user Wallet.w2)
asWallet Wallet.w2 $ Env.withDirectoryFor scriptRoot $ Env.withTransfer transferLogic $ Endpoints.transferSmartTokensTx aid 30 (C.PaymentCredentialByKey opPkh)
>>= void . sendTx . signTxOperator (user Wallet.w2)

seizeUserOutput :: (MonadUtxoQuery m, MonadFail m, MonadMockchain C.ConwayEra m) => DirectoryScriptRoot -> m ()
seizeUserOutput scriptRoot = failOnError $ Env.withEnv $ do
Expand All @@ -217,9 +213,8 @@ seizeUserOutput scriptRoot = failOnError $ Env.withEnv $ do

aid <- issueTransferLogicProgrammableToken scriptRoot

asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ do
Endpoints.deployBlacklistTx
>>= void . sendTx . signTxOperator admin
asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ Endpoints.deployBlacklistTx
>>= void . sendTx . signTxOperator admin

asAdmin @C.ConwayEra $ Env.withDirectoryFor scriptRoot $ Env.withTransferFromOperator $ do
Endpoints.transferSmartTokensTx aid 50 (C.PaymentCredentialByKey userPkh)
Expand All @@ -240,7 +235,6 @@ seizeUserOutput scriptRoot = failOnError $ Env.withEnv $ do
Query.userProgrammableOutputs (C.PaymentCredentialByKey opPkh)
>>= void . expectN 2 "user programmable outputs"


dummyNodeArgs :: InsertNodeArgs
dummyNodeArgs =
InsertNodeArgs
Expand All @@ -267,17 +261,24 @@ registerTransferScripts :: (MonadFail m, MonadReader env m, Env.HasTransferLogic
registerTransferScripts pkh = failOnError $ do
transferMintingScript <- asks (Env.tleMintingScript . Env.transferLogicEnv)
transferSpendingScript <- asks (Env.tleTransferScript . Env.transferLogicEnv)
transferSeizeSpendingScript <- asks (Env.tleIssuerScript . Env.transferLogicEnv)

let
hshMinting = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferMintingScript
credMinting = C.StakeCredentialByScript hshMinting

hshSpending = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferSpendingScript
credSpending = C.StakeCredentialByScript hshSpending

hshSeizeSpending = C.hashScript $ C.PlutusScript C.plutusScriptVersion transferSeizeSpendingScript
credSeizeSpending = C.StakeCredentialByScript hshSeizeSpending


txBody <- BuildTx.execBuildTxT $ do

addConwayStakeCredentialCertificate credSpending
addConwayStakeCredentialCertificate credMinting
addConwayStakeCredentialCertificate credSeizeSpending

BuildTx.addRequiredSignature pkh

Expand Down

0 comments on commit 320b7ec

Please sign in to comment.