Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vesting] rewrite the private sale module #75

Merged
merged 17 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions bin/cardano-address-script
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentiellement remplacé complètement le binaire par l'intégration directe de la lib haskell https://github.com/input-output-hk/cardano-addresses

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# This script is a wrapper around cardano-address.
#
# By default, cardano-address takes its input from stdin. For now, piping with
# (|>) from Shh.Internal leads to unexpected behaviour when doing IO inside the
# PropertyM transformer of QuickCheck. With (|>)¸the command cardano-address
# sometimes does not receive stdin and hang indefinitely, probably due to a
# strictness issue of the monads involved.
#
# The purpose of this script is to provide a hack, hidding the plumbing inside a
# bash script to avoid depending on the pipe from Haskell. The parameter
# following `--` is taken as the input for cardano-address. The rest of the
# arguments are propagated as-is.
#
# In order to use cardano-address in property testing from Haskell, you must
# install this script on your path.

args=()

while [ $# -gt 0 ]; do
case "$1" in
"--")
shift
break 2
;;
*)
args+=("$1")
shift
;;
esac
done

if [ -t 0 ]; then
printf '%s\n' "$1"
else
cat -
fi | cardano-address "${args[@]}"
110 changes: 110 additions & 0 deletions src/Tokenomia/CardanoApi/Fees.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

module Tokenomia.CardanoApi.Fees
( HasDatumHash(..)
, calculateDefaultMinimumUTxOFromValue
, calculateMinimumUTxOFromValue
, calculateDefaultMinimumUTxOFromAssetId
, calculateMinimumUTxOFromAssetId
, utxoEntrySize
)
where

import Cardano.Api
( ShelleyBasedEra(..)
, AssetId(..)
, Value
, Lovelace(..)
, selectLovelace
, valueFromList
)

import Cardano.Api.Shelley
( ProtocolParameters(..)
, calcMinimumDeposit
, lovelaceToValue
, toMaryValue
)

import Cardano.Ledger.Val qualified
as Value ( size )

import Tokenomia.CardanoApi.Value ( unLovelace )
import Tokenomia.CardanoApi.PParams ( defaultCalculateMinimumUTxOParams )


data HasDatumHash
= NoDatumHash
| WithDatumHash
deriving stock (Show)

-- | Utxo entry size calculation
utxoEntrySize :: Value -> HasDatumHash -> Integer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

la maj vasil introduit les inline datum qui peuvent impacter ce calcul
https://cips.cardano.org/cips/cip32/

utxoEntrySize value datum =
utxoEntrySizeWithoutVal + Value.size (toMaryValue value) + datumHashSize datum
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tu mets explicitement toMaryValue, y a-t-il une notion de rétrocompatibilité à prendre en compte également ? quel impact pour les futurs versions ? faudra-t-il changer cette fonction ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calculation is specific to the Alonzo era, as described in : https://github.com/input-output-hk/cardano-ledger/blob/2c203ead68913d660389f04a705c734c155c2a35/doc/explanations/min-utxo-alonzo.rst

The drawback of patching fees or minUTxO calculations is that those pieces of code are very unstable and will require maintenance. The only intent is to avoid generating a valid Address to build a TxOut for doing tests without IO as explained in the issue #74. We should use the original functions when appropriate.

where
datumHashSize :: HasDatumHash -> Integer
datumHashSize NoDatumHash = 0
datumHashSize WithDatumHash = 10

utxoEntrySizeWithoutVal :: Integer
utxoEntrySizeWithoutVal = 27
Comment on lines +50 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cardano est très "paramétrisable" dans le sens où on peut jouer avec les protocol-parameters sans avoir à sortir une nouvelle version du noeud, ces utxo et datumhash sizes font-elles parti de ces fameux paramètres ? le cas échéant, faut-il regrouper dans un fichier de conf les paramètres ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The utxoEntrySizeWithoutVal and dataHashSize are not protocol parameters but lengths obtained experimentally. The documentation refers to them as "current constants" :

Note that the coinsPerUTxOWord is a protocol parameter and is subject to change. The values utxoEntrySizeWithoutVal and dataHashSize (dh) are fixed at least for the entire Alonzo era.


-- | Calculate minimumUTxO simply from Value instead of TxOut
calculateMinimumUTxOFromValue ::
forall era.
ShelleyBasedEra era
-> Value
-> HasDatumHash
-> ProtocolParameters
-> Maybe Value
calculateMinimumUTxOFromValue era value datumHash ProtocolParameters{..} =
lovelaceToValue <$>
case era of
ShelleyBasedEraShelley -> protocolParamMinUTxOValue
ShelleyBasedEraAllegra -> calculateMinimumUTxOAllegraMary
ShelleyBasedEraMary -> calculateMinimumUTxOAllegraMary
ShelleyBasedEraAlonzo -> calculateMinimumUTxOAlonzo
where
calculateMinimumUTxOAllegraMary :: Maybe Lovelace
calculateMinimumUTxOAllegraMary =
calcMinimumDeposit value
<$> protocolParamMinUTxOValue

calculateMinimumUTxOAlonzo :: Maybe Lovelace
calculateMinimumUTxOAlonzo =
(Lovelace (utxoEntrySize value datumHash) *)
<$> protocolParamUTxOCostPerWord
Comment on lines +68 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

si je comprends bien on devra mettre ça à jour à chaque fois qu'on entre dans une nouvelle era qui touche à ce calcul ?


-- | Calculate minimumUTxO with default protocol parameters from a Value
calculateDefaultMinimumUTxOFromValue ::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quelle est la motivation derrière ces fonctions default ? ou plutôt pourquoi avons-nous besoin des fonctions "non default" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PParams in each era have a default instance, basically setting all fields to empty values. The non-default calculate* functions take a ProtocolParameters as argument. Those parameters could be retrieved from disk with IO. But given that the calculation use only one parameter in each era that is not susceptible to change, we can provide another default updated with the hardcoded needed value, and avoid doing IO in the process. The default functions are based on the non-default ones to give a clear design and to allow more generic calculations.

forall era.
ShelleyBasedEra era -> Value -> HasDatumHash -> Maybe Value
calculateDefaultMinimumUTxOFromValue era value datumHash =
calculateMinimumUTxOFromValue era value datumHash $
defaultCalculateMinimumUTxOParams era

-- | Calculate minimumUTxO for a singleton Value from AssetId
calculateMinimumUTxOFromAssetId ::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ce cas particulier est-il nécessaire ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the simplest way to call the calculation in the GenerateNative module for example, to encompass with a single call the same minUTxO for all singleton values based on the given AssetClass.

validatePrivateSale PrivateSale{..} =
    let proportions = TranchesProportions $ proportion <$> tranchesProperties
    in
        liftEither $ do
            ε <- calculateDefaultMinimumUTxOFromAssetClass assetClass

forall era.
ShelleyBasedEra era
-> AssetId
-> ProtocolParameters
-> Maybe Integer
calculateMinimumUTxOFromAssetId era assetId parameters =
let value = valueFromList [(assetId, 1)]
in
unLovelace . selectLovelace <$>
calculateMinimumUTxOFromValue era value NoDatumHash parameters

-- | Calculate minimumUTxO with default protocol parameters from AssetId
calculateDefaultMinimumUTxOFromAssetId ::
forall era.
ShelleyBasedEra era -> AssetId -> Maybe Integer
calculateDefaultMinimumUTxOFromAssetId era assetId =
calculateMinimumUTxOFromAssetId era assetId $
defaultCalculateMinimumUTxOParams era
12 changes: 12 additions & 0 deletions src/Tokenomia/CardanoApi/FromPlutus/Error.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{-# LANGUAGE DerivingStrategies #-}

module Tokenomia.CardanoApi.FromPlutus.Error
( FromPlutusError(..)
) where


data FromPlutusError
= PlutusCurrencySymbolNotPolicyId
| PlutusTokenNameNotAssetName
| PlutusAssetClassNotAssetId
deriving stock (Show)
72 changes: 72 additions & 0 deletions src/Tokenomia/CardanoApi/FromPlutus/Value.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE TupleSections #-}

module Tokenomia.CardanoApi.FromPlutus.Value
( currencySymbolAsPolicyId
, tokenNameAsAssetName
, assetClassAsAssetId
, fromPlutusValue
) where

import Data.Either.Combinators ( maybeToRight )
import Data.Functor ( (<&>) )
import Tokenomia.Common.Data.Either.Extra ( toEither )

import Cardano.Api
( PolicyId
, AssetName
, AssetId(..)
, AsType(..)
, Quantity(..)
, Value
, deserialiseFromRawBytes
, valueFromList
)

import PlutusTx.Builtins ( fromBuiltin )
import Plutus.V1.Ledger.Ada ( adaSymbol, adaToken )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a pas mal de références à Plutus.V1, je ne me rends pas compte de ce que va apporter Plutus.V2 comme modifications. Je crois que V2 arrive avec Vasil si je ne me trompe pas, à moins que ce soit déjà déployé.

import Plutus.V1.Ledger.Value qualified
as Plutus ( Value )
import Plutus.V1.Ledger.Value
( AssetClass(..)
, CurrencySymbol(..)
, TokenName(..)
, flattenValue
, assetClass
)

import Tokenomia.CardanoApi.FromPlutus.Error
( FromPlutusError(..) )


-- | Convert a CurrencySymbol to a PolicyId
currencySymbolAsPolicyId :: CurrencySymbol -> Either FromPlutusError PolicyId
currencySymbolAsPolicyId (CurrencySymbol x) =
maybeToRight PlutusCurrencySymbolNotPolicyId $
deserialiseFromRawBytes AsPolicyId (fromBuiltin x)

-- | Convert a TokenName to an AssetName
tokenNameAsAssetName :: TokenName -> Either FromPlutusError AssetName
tokenNameAsAssetName (TokenName x) =
maybeToRight PlutusTokenNameNotAssetName $
deserialiseFromRawBytes AsAssetName (fromBuiltin x)

-- | Convert an AssetClass to an AssetId
assetClassAsAssetId :: AssetClass -> Either FromPlutusError AssetId
assetClassAsAssetId (AssetClass (cs, tn))
| cs == adaSymbol =
toEither (tn == adaToken)
PlutusAssetClassNotAssetId
AdaAssetId
| otherwise =
AssetId
<$> currencySymbolAsPolicyId cs
<*> tokenNameAsAssetName tn

-- | Convert a Plutus Value to a Cardano.Api Value
fromPlutusValue :: Plutus.Value -> Either FromPlutusError Value
fromPlutusValue value =
valueFromList <$> sequence (fromPlutusSingleton <$> flattenValue value)
where
fromPlutusSingleton (cs, tn, x) =
assetClassAsAssetId (assetClass cs tn) <&> (, Quantity x)
42 changes: 42 additions & 0 deletions src/Tokenomia/CardanoApi/PParams.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{-# LANGUAGE ImportQualifiedPost #-}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

besoin de faire un point sur la gestion de ces paramètres de manière générale dans tokenomia

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}

module Tokenomia.CardanoApi.PParams
( defaultCalculateMinimumUTxOParams
, withCalculateMinimumUTxOParams
) where

import Cardano.Api ( ShelleyBasedEra(..), fromLedgerPParams )
import Cardano.Api.Shelley ( ProtocolParameters, ShelleyLedgerEra )
import Cardano.Ledger.Coin ( Coin (..) )
import Cardano.Ledger.Core ( PParams )
import Cardano.Ledger.Alonzo.PParams ( PParams'(..) )
import Cardano.Ledger.Shelley.PParams ( PParams'(..) )
import Data.Default ( def )


-- | Update the right parameters necessary to calculateMinimumUTxO
withCalculateMinimumUTxOParams ::
forall era.
Integer -> ShelleyBasedEra era -> PParams (ShelleyLedgerEra era) -> ProtocolParameters
withCalculateMinimumUTxOParams n era pparams =
fromLedgerPParams era $ updateWith (Coin n) pparams
where
updateWith =
case era of
ShelleyBasedEraShelley -> (\x up -> up {_minUTxOValue = x})
ShelleyBasedEraAllegra -> (\x up -> up {_minUTxOValue = x})
ShelleyBasedEraMary -> (\x up -> up {_minUTxOValue = x})
ShelleyBasedEraAlonzo -> (\x up -> up {_coinsPerUTxOWord = x})

-- | Default parameters necessary to calculateMinimumUTxO
defaultCalculateMinimumUTxOParams ::
forall era.
ShelleyBasedEra era -> ProtocolParameters
defaultCalculateMinimumUTxOParams era =
case era of
ShelleyBasedEraShelley -> withCalculateMinimumUTxOParams 1000000 era def
ShelleyBasedEraAllegra -> withCalculateMinimumUTxOParams 1000000 era def
ShelleyBasedEraMary -> withCalculateMinimumUTxOParams 1000000 era def
ShelleyBasedEraAlonzo -> withCalculateMinimumUTxOParams 34482 era def
16 changes: 16 additions & 0 deletions src/Tokenomia/CardanoApi/Value.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Tokenomia.CardanoApi.Value
( unLovelace
, unQuantity
) where

import Cardano.Api
( Lovelace(..)
, Quantity(..)
)


unLovelace :: Lovelace -> Integer
unLovelace (Lovelace l) = l

unQuantity :: Quantity -> Integer
unQuantity (Quantity l) = l
Comment on lines +12 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je ne sais pas pour unQuantity mais il me semble que unLovelace existe déjà

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32 changes: 32 additions & 0 deletions src/Tokenomia/Common/Aeson/AssetClass.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{-# LANGUAGE OverloadedStrings #-}

module Tokenomia.Common.Aeson.AssetClass
( assetClassFromJSON
, assetClassToJSON
) where

import Control.Arrow ( (***) )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c'est quoi ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have used bimap instead.


import Data.Aeson.Types ( Parser )
import Data.Aeson ( Value, object, withObject, (.:), (.=) )
import Data.String ( IsString(..) )

import Plutus.V1.Ledger.Value ( AssetClass(..), assetClass, toString )


-- | Alternative AssetClass Parser to the FromJSON instance
assetClassFromJSON :: Value -> Parser AssetClass
assetClassFromJSON = withObject "AssetClass" $ \o ->
assetClass
<$> (fromString <$> o .: "currencySymbol")
<*> (fromString <$> o .: "tokenName")

-- | Alternative AssetClass Value to the ToJSON instance
assetClassToJSON :: AssetClass -> Value
assetClassToJSON x =
let (currencySymbol, tokenName) = (show *** toString) $ unAssetClass x
in
object
[ "currencySymbol" .= currencySymbol
, "tokenName" .= tokenName
]
Comment on lines +18 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

les toJSON avec les lenses faisaient pas le taf ?

Loading