Skip to content

Commit

Permalink
Merge #2712
Browse files Browse the repository at this point in the history
2712:  Mint burn API only, stubbed implementation r=sevanspowell a=sevanspowell

# Issue Number

ADP-346
ADP-862

# Overview

This PR modifies the cardano-wallet API to support minting and burning. Minting and burning are not actually implemented, just stubbed. This was done to minimize the amount of context the reviewer needs to keep in mind when reviewing - we are just reviewing the form of the new minting and burning endpoints.

- Add a new "mint" endpoint under the "assets" API group.
- Add a number of new API types to represent the data the user must submit with a mint/burn request, and the data they receive back.
- Adjust swagger specification to match new API types.
- Adds Arbitrary instances for API types for property tests.
- Adds a stub for the minting and burning implementation.


Co-authored-by: Samuel Evans-Powell <[email protected]>
  • Loading branch information
iohk-bors[bot] and sevanspowell authored Jul 19, 2021
2 parents a38c5e7 + 0cc598d commit 914668d
Show file tree
Hide file tree
Showing 10 changed files with 2,569 additions and 5 deletions.
6 changes: 6 additions & 0 deletions lib/core/src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ module Cardano.Wallet
, ErrNotASequentialWallet (..)
, ErrWithdrawalNotWorth (..)
, ErrConstructTx (..)
, ErrMintBurnAssets (..)

-- ** Migration
, createMigrationPlan
Expand Down Expand Up @@ -2595,6 +2596,11 @@ data ErrConstructTx
-- ^ Temporary error constructor.
deriving (Show, Eq)

newtype ErrMintBurnAssets
= ErrMintBurnNotImplemented T.Text
-- ^ Temporary error constructor.
deriving (Show, Eq)

-- | Errors that can occur when submitting a signed transaction to the network.
data ErrSubmitTx
= ErrSubmitTxNetwork ErrPostTx
Expand Down
17 changes: 14 additions & 3 deletions lib/core/src/Cardano/Wallet/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module Cardano.Wallet.Api
, ListAssets
, GetAsset
, GetAssetDefault
, MintBurnAssets

, Addresses
, ListAddresses
Expand Down Expand Up @@ -173,6 +174,7 @@ import Cardano.Wallet.Api.Types
, ApiHealthCheck
, ApiMaintenanceAction
, ApiMaintenanceActionPostData
, ApiMintedBurnedTransactionT
, ApiNetworkClock
, ApiNetworkInformation
, ApiNetworkParameters
Expand Down Expand Up @@ -206,6 +208,7 @@ import Cardano.Wallet.Api.Types
, Iso8601Time
, KeyFormat
, MinWithdrawal
, PostMintBurnAssetDataT
, PostTransactionFeeOldDataT
, PostTransactionOldDataT
, SettingsPutData
Expand Down Expand Up @@ -291,7 +294,7 @@ type ApiV2 n apiPool = "v2" :> Api n apiPool
type Api n apiPool =
Wallets
:<|> WalletKeys
:<|> Assets
:<|> Assets n
:<|> Addresses n
:<|> CoinSelections n
:<|> ShelleyTransactions n
Expand Down Expand Up @@ -422,8 +425,9 @@ type GetAccountKey = "wallets"
See also: https://input-output-hk.github.io/cardano-wallet/api/#tag/Assets
-------------------------------------------------------------------------------}

type Assets =
ListAssets
type Assets n =
MintBurnAssets n
:<|> ListAssets
:<|> GetAsset
:<|> GetAssetDefault

Expand All @@ -448,6 +452,13 @@ type GetAssetDefault = "wallets"
:> Capture "policyId" (ApiT TokenPolicyId)
:> Get '[JSON] ApiAsset

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/mintBurnAssets
type MintBurnAssets n = "wallets"
:> Capture "walletId" (ApiT WalletId)
:> "assets"
:> ReqBody '[JSON] (PostMintBurnAssetDataT n)
:> PostAccepted '[JSON] (ApiMintedBurnedTransactionT n)

{-------------------------------------------------------------------------------
Addresses
Expand Down
11 changes: 11 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Link.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module Cardano.Wallet.Api.Link
, getAsset
, listByronAssets
, getByronAsset
, mintBurnAssets

-- * Transactions
, createTransaction
Expand Down Expand Up @@ -897,3 +898,13 @@ instance HasVerb sub => HasVerb (QueryFlag sym :> sub) where

instance HasVerb sub => HasVerb (Header' opts name ty :> sub) where
method _ = method (Proxy @sub)

mintBurnAssets
:: forall w.
( HasType (ApiT WalletId) w
)
=> w
-> (Method, Text)
mintBurnAssets w = (endpoint @(Api.MintBurnAssets Net) (wid &))
where
wid = w ^. typed @(ApiT WalletId)
17 changes: 17 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ module Cardano.Wallet.Api.Server
, postSharedWallet
, patchSharedWallet
, mkSharedWallet
, mintBurnAssets

-- * Server error responses
, IsServerError(..)
Expand Down Expand Up @@ -142,6 +143,7 @@ import Cardano.Wallet
, ErrJoinStakePool (..)
, ErrListTransactions (..)
, ErrListUTxOStatistics (..)
, ErrMintBurnAssets (..)
, ErrMkTx (..)
, ErrNoSuchTransaction (..)
, ErrNoSuchWallet (..)
Expand Down Expand Up @@ -205,6 +207,7 @@ import Cardano.Wallet.Api.Types
, ApiErrorCode (..)
, ApiFee (..)
, ApiForeignStakeKey (..)
, ApiMintedBurnedTransaction (..)
, ApiMnemonicT (..)
, ApiNetworkClock (..)
, ApiNetworkInformation
Expand Down Expand Up @@ -2640,6 +2643,16 @@ getAccountPublicKey ctx mkAccount (ApiT wid) extended = do
Just Extended -> Extended
_ -> NonExtended

mintBurnAssets
:: forall ctx n
. ctx
-> ApiT WalletId
-> Api.PostMintBurnAssetData n
-> Handler (ApiMintedBurnedTransaction n)
mintBurnAssets _ctx (ApiT _wid) _body = liftHandler $ throwE $
ErrMintBurnNotImplemented
"Minting and burning are not supported yet - this is just a stub"

{-------------------------------------------------------------------------------
Helpers
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -3305,6 +3318,10 @@ instance IsServerError ErrConstructTx where
apiError err501 NotImplemented
"This feature is not yet implemented."

instance IsServerError ErrMintBurnAssets where
toServerError = \case
ErrMintBurnNotImplemented msg -> apiError err501 NotImplemented msg

instance IsServerError ErrDecodeSignedTx where
toServerError = \case
ErrDecodeSignedTxWrongPayload _ ->
Expand Down
170 changes: 170 additions & 0 deletions lib/core/src/Cardano/Wallet/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,15 @@ module Cardano.Wallet.Api.Types
, ApiSelectCoinsPayments (..)
, ApiSelectCoinsAction (..)
, ApiCoinSelection (..)
, ApiMintBurnOperation (..)
, ApiMintData(..)
, ApiBurnData(..)
, ApiCoinSelectionChange (..)
, ApiCoinSelectionInput (..)
, ApiCoinSelectionOutput (..)
, ApiCoinSelectionWithdrawal (..)
, ApiBase64
, ApiMintBurnData (..)
, ApiStakePool (..)
, ApiStakePoolMetrics (..)
, ApiStakePoolFlag (..)
Expand All @@ -93,6 +97,8 @@ module Cardano.Wallet.Api.Types
, ApiSerialisedTransaction (..)
, ApiSignedTransaction (..)
, ApiTransaction (..)
, ApiMintedBurnedTransaction (..)
, ApiMintedBurnedInfo (..)
, ApiWithdrawalPostData (..)
, ApiMaintenanceAction (..)
, ApiMaintenanceActionPostData (..)
Expand Down Expand Up @@ -150,6 +156,7 @@ module Cardano.Wallet.Api.Types
, ApiPaymentDestination (..)
, ApiValidityInterval (..)
, ApiValidityBound
, PostMintBurnAssetData(..)

-- * API Types (Byron)
, ApiByronWallet (..)
Expand Down Expand Up @@ -200,8 +207,10 @@ module Cardano.Wallet.Api.Types
, ApiConstructTransactionDataT
, PostTransactionOldDataT
, PostTransactionFeeOldDataT
, ApiMintedBurnedTransactionT
, ApiWalletMigrationPlanPostDataT
, ApiWalletMigrationPostDataT
, PostMintBurnAssetDataT

-- * API Type Conversions
, coinToQuantity
Expand Down Expand Up @@ -1053,6 +1062,33 @@ data ApiTransaction (n :: NetworkDiscriminant) = ApiTransaction
} deriving (Eq, Generic, Show)
deriving anyclass NFData

-- | The response cardano-wallet returns upon successful submission of a
-- mint/burn transaction.
data ApiMintedBurnedTransaction (n :: NetworkDiscriminant) = ApiMintedBurnedTransaction
{ transaction :: !(ApiTransaction n)
-- ^ Information about the mint/burn transaction itself.
, mintedBurned :: !(NonEmpty (ApiT ApiMintedBurnedInfo))
-- ^ Helpful information about each unique asset minted or burned (where the
-- identity is the policyId + asset name of the asset).
}
deriving (Eq, Generic, Show)
deriving anyclass NFData

data ApiMintedBurnedInfo = ApiMintedBurnedInfo
{ monetaryPolicyIndex :: !(ApiT DerivationIndex)
-- ^ The monetary policy index the asset was minted/burnt under.
, policyId :: !(ApiT W.TokenPolicyId)
-- ^ The policy ID the asset was minted/burnt under.
, assetName :: !(ApiT W.TokenName)
-- ^ The name of the asset minted/burnt.
, subject :: !(ApiT W.TokenFingerprint)
-- ^ The subject of the asset minted/burnt. This is useful to users wishing
-- to attach metadata to their asset.
, script :: !(ApiT (Script KeyHash))
-- ^ The script which this asset was minted and/or burned under
} deriving (Eq, Generic, Show)
deriving anyclass NFData

newtype ApiTxMetadata = ApiTxMetadata
{ getApiTxMetadata :: Maybe (ApiT TxMetadata)
}
Expand Down Expand Up @@ -2606,6 +2642,18 @@ instance (DecodeAddress t, DecodeStakeAddress t) => FromJSON (ApiConstructTransa
instance (EncodeAddress t, EncodeStakeAddress t) => ToJSON (ApiConstructTransaction t) where
toJSON = genericToJSON defaultRecordTypeOptions

instance
( DecodeAddress n
, DecodeStakeAddress n
) => FromJSON (ApiMintedBurnedTransaction n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance
( EncodeAddress n
, EncodeStakeAddress n
) => ToJSON (ApiMintedBurnedTransaction n) where
toJSON = genericToJSON defaultRecordTypeOptions

instance FromJSON ApiWithdrawalPostData where
parseJSON obj =
parseSelfWithdrawal <|> fmap ExternalWithdrawal (parseJSON obj)
Expand Down Expand Up @@ -3305,6 +3353,8 @@ type family ApiConstructTransactionT (n :: k) :: Type
type family ApiConstructTransactionDataT (n :: k) :: Type
type family PostTransactionOldDataT (n :: k) :: Type
type family PostTransactionFeeOldDataT (n :: k) :: Type
type family ApiMintedBurnedTransactionT (n :: k) :: Type
type family PostMintBurnAssetDataT (n :: k) :: Type
type family ApiWalletMigrationPlanPostDataT (n :: k) :: Type
type family ApiWalletMigrationPostDataT (n :: k1) (s :: k2) :: Type
type family ApiPutAddressesDataT (n :: k) :: Type
Expand Down Expand Up @@ -3341,12 +3391,18 @@ type instance PostTransactionOldDataT (n :: NetworkDiscriminant) =
type instance PostTransactionFeeOldDataT (n :: NetworkDiscriminant) =
PostTransactionFeeOldData n

type instance PostMintBurnAssetDataT (n :: NetworkDiscriminant) =
PostMintBurnAssetData n

type instance ApiWalletMigrationPlanPostDataT (n :: NetworkDiscriminant) =
ApiWalletMigrationPlanPostData n

type instance ApiWalletMigrationPostDataT (n :: NetworkDiscriminant) (s :: Symbol) =
ApiWalletMigrationPostData n s

type instance ApiMintedBurnedTransactionT (n :: NetworkDiscriminant) =
ApiMintedBurnedTransaction n

{-------------------------------------------------------------------------------
SMASH interfacing types
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -3390,3 +3446,117 @@ instance FromJSON (ApiT SmashServer) where
parseJSON = fromTextJSON "SmashServer"
instance ToJSON (ApiT SmashServer) where
toJSON = toTextJSON

{-------------------------------------------------------------------------------
Token minting types
-------------------------------------------------------------------------------}

-- | Data required when submitting a mint/burn transaction. Cardano implements
-- minting and burning using transactions, so some of these fields are shared
-- with @PostTransactionData@.
data PostMintBurnAssetData (n :: NetworkDiscriminant) = PostMintBurnAssetData
{ mintBurn :: !(NonEmpty (ApiMintBurnData n))
-- ^ Minting and burning requests.
, passphrase :: !(ApiT (Passphrase "lenient"))
-- ^ Passphrase of the wallet.
, metadata :: !(Maybe (ApiT TxMetadata))
-- ^ Metadata to attach to the transaction that mints/burns.
, timeToLive :: !(Maybe (Quantity "second" NominalDiffTime))
-- ^ Time the created mint/burn transaction is valid until.
} deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (PostMintBurnAssetData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (PostMintBurnAssetData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | Core minting and burning request information.
--
-- Assets are minted and burned under a "policy". The policy defines under what
-- circumstances a token may be minted and burned. The typical policy is "A
-- token may be minted and burned if signature 's' witnesses the transaction,
-- for some signature 's'". This is the only type of policy supported by the
-- cardano-wallet API at the moment. Because cardano-wallet manages the keys of
-- the user, we ask the user not for a specific signature, but rather for a key
-- derivation index, which we use to derive the signature to construct the
-- policy with.
data ApiMintBurnData (n :: NetworkDiscriminant) = ApiMintBurnData
{ monetaryPolicyIndex :: !(Maybe (ApiT DerivationIndex))
-- ^ The key derivation index to use to construct the policy.
, assetName :: !(ApiT W.TokenName)
-- ^ The name of the asset to mint/burn.
, operation :: !(ApiMintBurnOperation n)
-- ^ The minting or burning operation to perform.
} deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (ApiMintBurnData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (ApiMintBurnData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | A user may choose to either mint tokens or burn tokens with each operation.
data ApiMintBurnOperation (n :: NetworkDiscriminant)
= ApiMint (ApiMintData n)
-- ^ Mint tokens.
| ApiBurn ApiBurnData
-- ^ Burn tokens.
deriving (Eq, Generic, Show)

-- | The format of a minting request: mint "amount" and send it to the
-- "address".
data ApiMintData (n :: NetworkDiscriminant) = ApiMintData
{ receivingAddress :: (ApiT Address, Proxy n)
-- ^ Address that receives the minted assets.
, amount :: Quantity "assets" Natural
-- ^ Amount of assets to mint.
}
deriving (Eq, Generic, Show)

instance DecodeAddress n => FromJSON (ApiMintData n) where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance EncodeAddress n => ToJSON (ApiMintData n) where
toJSON = genericToJSON defaultRecordTypeOptions

-- | The format of a burn request: burn "amount". The user can only specify the
-- type of tokens to burn (policyId, assetName), and the amount, the exact
-- tokens selected are up to the implementation.
newtype ApiBurnData = ApiBurnData (Quantity "assets" Natural)
deriving (Eq, Generic, Show)

instance FromJSON ApiBurnData where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance ToJSON ApiBurnData where
toJSON (burn) = genericToJSON defaultRecordTypeOptions burn

instance EncodeAddress n => ToJSON (ApiMintBurnOperation n) where
toJSON = object . pure . \case
ApiMint mint -> "mint" .= mint
ApiBurn burn -> "burn" .= burn

instance DecodeAddress n => FromJSON (ApiMintBurnOperation n) where
parseJSON = Aeson.withObject "ApiMintBurnOperation" $ \o ->
case HM.keys o of
["mint"] -> ApiMint <$> o .: "mint"
["burn"] -> ApiBurn <$> o .: "burn"
[] -> fail "Must include a \"mint\" or \"burn\" property."
_ -> fail "May be either a \"mint\" or a \"burn\"."

instance FromJSON ApiMintedBurnedInfo where
parseJSON = genericParseJSON defaultRecordTypeOptions

instance ToJSON ApiMintedBurnedInfo where
toJSON = genericToJSON defaultRecordTypeOptions

instance FromJSON (ApiT ApiMintedBurnedInfo) where
parseJSON = fmap ApiT . parseJSON
instance ToJSON (ApiT ApiMintedBurnedInfo) where
toJSON = toJSON . getApiT

instance FromJSON (ApiT (Script KeyHash)) where
parseJSON = fmap ApiT . parseJSON
instance ToJSON (ApiT (Script KeyHash)) where
toJSON = toJSON . getApiT
Loading

0 comments on commit 914668d

Please sign in to comment.