Description
Overview
This task is to fix and improve the vesting feature. The current data structure
used in the native script generation module has several flaws.
-
The input specification of a private sale does not map investor address to
their allocation. Currently, a list is used instead, this allows the same
address to appear several times. -
The tranche proportions are not given as a proper ratio, but as a quantity
out of an implicit total. This is error prone and yield to uncanny
calculations. -
The integer partition of the total allocation in tranches does not take into
account the incorrect possibility of rounding amounts to zero neither the
minimum of ADA per UTxO required to build the scripts. -
The current datatypes expect slots instead of POSIX time. Some logic for
finding the right slots for the native scripts properties is specific to the
vesting feature. Conversion between POSIX time and slots should be handled by
this module. Also, the current specification has times seen as relative
durations, not as absolute points in time. This contrains the tranches to be
sequential and contiguous. -
The splitting in tranches operation yields in a single step a consolidated
data structure, as a single map with lists as values, representing tranches
informations inside the map structure. While it is efficient, this design can be
difficult to grasp. There is no intermediate datatype and the implementation
further blur this design choice by doing several tasks within the splitting
operation such as allocation partitioning, slot calculation and address
deserialization.Besides leading to weak composability, the intermediate custom map lose
asset class information required later, forcing to have both the intermediate
map and the original structure in the signature of final output generators.Another approach would be to use a list of maps as the resulting data
structure of the splitting operation, instead of a map to lists. The only
drawback is the algorithmic complexity of maps union, which should take into
account that all maps share the same keys. -
While this custom intermediate map has a structure very close to the output
corresponding to the database file, this is not so true for the distribution
file. This may have been a source of confusion in the implementation of the
distribution file generator. It is broken by accumulating again all the
partitioned allocations to be sent to the first tranche script.Also for better interoperability and traceability, it would be nice if the
database file also contained the computed script address and amounts required by
the distribution file. Then, the distribution file data would be a strict subset
of the database file data. This would allow a nice pipeline : splitting, feeding
database generator, feeding distribution generator. -
Furthermore, the current feature has no tests and no arbitrary instances.
Changed Specification
The new specification gives the following datatypes. Note the proximity between
PrivateSale
and PrivateSaleTranche
.
type Allocation = Natural
newtype InvestorAddress = InvestorAddress { unInvestorAddress :: Text }
data TrancheProperties = TrancheProperties
{ proportion :: Ratio Natural
, unlockTime :: POSIXTime
}
data PrivateSale = PrivateSale
{ tranchesProperties :: NonEmpty TrancheProperties
, assetClass :: AssetClass
, allocationByAddress :: NEMap InvestorAddress Allocation
}
data PrivateSaleTranche = PrivateSaleTranche
{ trancheUnlockTime :: POSIXTime
, trancheAssetClass :: AssetClass
, trancheAllocationByAddress :: NEMap InvestorAddress Allocation
}
data NativeScript = NativeScript
{ requireSignature :: PubKeyHash
, requireTimeAfter :: POSIXTime
}
data NativeScriptInfo = NativeScriptInfo
{ requiring :: NativeScript
, recipient :: Recipient
}
data DatabaseOutput = DatabaseOutput
{ lockedAssetClass :: AssetClass
, lockedFunds :: NEMap InvestorAddress (NonEmpty NativeScriptInfo)
}
Features
The resolution of this issue requires the following tasks:
- Redesign the solution with new data structures
- Partition total allocation based on tranches ratios
- Assert partitioned allocations can have a minimum
- Handle POSIX time to slot conversion logic
- Split
PrivateSale
in list ofPrivateSaleTranche
- Generate final structure for database
- Generate final structure for distribution
- Write all property based tests
Minimum UTxO
The calculation of the minimum UTxO is a feature frequently needed. The issue
is furthermore to improve the way of obtaining this value by not depending
anymore on cardano-cli
and use instead Cardano.Api
.
For user-friendliness, calculateMinimumUTxO
is reimplemented for accepting
simple value or asset class instead of fully constructed TxOut
that requires
valid address. This implies the following tasks:
- Reimplement
calculateMinimumUTxO
- Construct default protocol parameters
- Convert from Plutus values
The validity of the calculation is based on the source Cardano/Api/Fees.hs
and the documentation from cardano-ledger
(after fixing a small typo with
IntersectMBO/cardano-ledger#3012).
Tests
In order to write the tests, the following extra components are required:
- Generator of random well-formed address with
cardano-address
- Arbitrary instances for several Plutus datatypes
- Arbitrary instances for well-formed inputs and intermediate structures