A Toy Model/Specification for Superfluid Money Concepts
Bob requests to send €42 to Alice now.
Bob requests to send a constant flow of money at €420 per 30 day to Alice now.
Microsoft requests to send $0.42 per share to all its share holders on the next ex-dividend date.
Elon Musk requests to send a constant flow of money at €4.2B per 360 days to all its share holders with individual flow rates proportional to their number of share units, from 20st April.
FMUs | Public blockchain | |
---|---|---|
Instant | Yes, fast within the system, poor interoperability | Yes, with scalability issue |
Streaming | Micro transactions | Micro transactions, but can be costly on some system |
en mass | NOPE | Some system may have ad-hoc implementations. |
How money is represented in the system?
class (Integral v, Default v) => Value v
-- | Typed value is an otherwise unaccounted value ~v~ with an associated ~vtag~.
class ( TypedValueTag vtag
, Value v
) => TypedValue tv vtag v | tv -> v, tv -> vtag where
untypeValue :: tv -> v
default untypeValue :: Coercible tv v => tv -> v
untypeValue = coerce
-- | Tag for typed value type class using ~Typeable~ runtime information.
class Typeable vtag => TypedValueTag vtag where
tappedValueTag :: Proxy vtag -> String
Code
-- | Untapped value type. It can be freely accessed by any sub systems.
newtype UntappedValue v = UntappedValue
instance Value v => TypedValue (UntappedValue v) UntappedValueTag v
-- | Tapped value tag. It must only be accessed by its designated sub system.
class TypedValueTag tvtag => TappedValueTag tvtag
instance (Value v, TypedValueTag vtag) => TypedValue (TappedValue vtag v) vtag v
RealTimeBalance
.[untapped value, tapped value 1, tapped value 2, ...]
.class ( Value v
, Foldable rtbF
, Monoid (rtbF v)
) => RealTimeBalance rtbF v | v -> rtbF where
-- | Convert a single monetary value to a RTB value.
valueToRTB :: v -> rtbF v
-- | Net monetary value of a RTB value.
netValueOfRTB :: rtbF v -> v
netValueOfRTB = foldr (+) def
-- | Convert typed values to a RTB value.
typedValuesToRTB :: UntappedValue v -> [AnyTappedValue v] -> rtbF v
-- | Get typed values from a RTB value.
typedValuesFromRTB :: rtbF v -> (UntappedValue v, [AnyTappedValue v])
-- monoid laws
monoid_right_identity x = (x <> mempty) `sameAs` x
monoid_left_identity x = (mempty <> x) `sameAs` x
monoid_associativity a b c = ((a <> b) <> c) `sameAs` (a <> (b <> c))
-- in addition to monoid laws, it also has some other nice properties:
monoid_mappend_commutativity a b =
(a <> b) `sameAs` (b <> a)
rtb_identity_from_and_to_typed_values x = -- type of x is a RealTimeBalance
(uncurry typedValuesToRTB . typedValuesFromRTB) x `sameAs` x
rtb_conservation_of_net_value x = -- type of x is a Value
(netValueOfRTB . valueToRTB) x == x
Cit. buldas2021unifying - A Unifying Theory of Electronic Money and Payment Systems
How money should be moved in the system?
class ( SuperfluidTypes sft
, Monoid amud -- !! A MONOID, so that system may scale
) => AgreementMonetaryUnitData amud sft | amud -> sft where
-- | π function -
-- balance provided (hear: π) by the agreement monetary unit data.
balanceProvidedByAgreement
:: amud -- amud
-> SFT_TS sft -- t !! THIS IS WHAT MAKES THINGS REAL TIME
-> SFT_RTB sft -- rtb
-- | Calculate the real time balance of an monetary unit at a given time.
balanceOfAt :: (SuperfluidTypes sft, MonetaryUnit mu sft)
=> mu -> SFT_TS sft -> SFT_RTB sft
balanceOfAt mu t = foldr -- !! THIS IS THE: ν(u, t)
-- !! providedBalanceByAnyAgreement is the balanceProvidedByAgreement for the existential type
-- returned from agreementsOf.
((<>) . (flip (providedBalanceByAnyAgreement mu) t))
mempty
(agreementsOf mu)
class ( SuperfluidTypes sft
, AgreementMonetaryUnitData (AgreementMonetaryUnitDataInOperation ao) sft
) => AgreementOperation ao sft | ao -> sft where
-- ... for brevity, removed some type definitions here...
-- | ω function - apply agreement operation ~ao~ (hear: ω) onto the agreement
-- operation data ~aod~ to get a tuple of:
--
-- 1. An updated ~aod'~.
-- 2. A functorful delta of agreement monetary unit data ~aorΔ~, which then
-- can be monoid-appended to existing ~amud~. This is what can make an
-- agreement scalable.
applyAgreementOperation
:: amud ~ (AgreementMonetaryUnitDataInOperation ao)
=> ao -- ao
-> AgreementOperationData ao -- aod
-> SFT_TS sft -- t
-> ( AgreementOperationData ao
, AgreementOperationResultF ao amud) -- (aod', aorΔ)
Semantic: Party A sends an X amount of money instantly to party B.
class (Default amuLs, SuperfluidTypes sft)
=> MonetaryUnitLenses amuLs sft | amuLs -> sft where
untappedValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft))
newtype Operation sft = Transfer (SFT_MVAL sft)
instance SuperfluidTypes sft => AgreementOperation (Operation sft) sft where
data AgreementOperationResultF (Operation sft) elem = OperationPartiesF
{ transferFrom :: elem
, transferTo :: elem
} deriving stock (Functor, Foldable, Traversable)
applyAgreementOperation (Transfer amount) acd _ = let
acd' = acd
aorΔ = fmap ITMUD.MkMonetaryUnitData (OperationPartiesF
(def & set ITMUD.untappedValue (coerce (- amount)))
(def & set ITMUD.untappedValue (coerce amount)))
in (acd', aorΔ)
instance MonetaryUnitLenses amuLs sft
=> Semigroup (MonetaryUnitData amuLs sft) where
(<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) =
let c = a & over untappedValue (+ b^.untappedValue)
in MkMonetaryUnitData c
instance MonetaryUnitLenses amuLs sft
=> Monoid (MonetaryUnitData amuLs sft) where
mempty = MkMonetaryUnitData def
instance MonetaryUnitLenses amuLs sft
=> AgreementMonetaryUnitData (MonetaryUnitData amuLs sft) sft where
balanceProvidedByAgreement (MkMonetaryUnitData a) _ =
typedValuesToRTB (a^.untappedValue) []
SF.transfer
uses the ITA.Operation
defined above, this is how its semantic looks like:runToken token $ SF.transfer (ITA.OperationPartiesF bob alice) (USD 42)
Well, we just redefined 1to1 instant payment using this new semantic… big deal, what's next?
Semantic: Party A sends a flow of money at a constant rate X
to party B.
class (Default amuLs, SuperfluidTypes sft)
=> MonetaryUnitLenses amuLs sft | amuLs -> sft where
settledAt :: Lens' amuLs (SFT_TS sft)
settledUntappedValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft))
netFlowRate :: Lens' amuLs (SFT_MVAL sft)
instance MonetaryUnitLenses amuLs sft
=> Semigroup (MonetaryUnitData amuLs sft) where
(<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) =
let c = a & set settledAt ( b^.settledAt)
& over settledUntappedValue (+ b^.settledUntappedValue)
& over netFlowRate (+ b^.netFlowRate)
in MkMonetaryUnitData c
instance MonetaryUnitLenses amuLs sft
=> AgreementMonetaryUnitData (MonetaryUnitData amuLs sft) sft where
balanceProvidedByAgreement (MkMonetaryUnitData a) t = typedValuesToRTB
( UntappedValue $ uval_s + fr * fromIntegral (t - t_s) ) []
where t_s = a^.settledAt
UntappedValue uval_s = a^.settledUntappedValue
fr = a^.netFlowRate
-- ... some defintions are omitted for brevity ...
applyAgreementOperation (UpdateFlow newFlowRate) acd t' = let
acd' = ContractData { flow_last_updated_at = t'
, flow_rate = newFlowRate
}
aorΔ = OperationPartiesF
(def & set CFMUD.settledAt t'
& set CFMUD.netFlowRate (-flowRateΔ)
& set CFMUD.settledUntappedValue (UntappedValue $ -settledΔ)
)
(def & set CFMUD.settledAt t'
& set CFMUD.netFlowRate flowRateΔ
& set CFMUD.settledUntappedValue (UntappedValue settledΔ)
)
in (acd', fmap CFMUD.MkMonetaryUnitData aorΔ)
where t = flow_last_updated_at acd
fr = flow_rate acd
settledΔ = fr * fromIntegral (t' - t) -- !! KEY equition
flowRateΔ = newFlowRate - fr
Semantic: Party A sends a flow of money at a decaying rate Λ
to party B with a distribution limit of X
.
balanceProvidedByAgreement (MkMonetaryUnitData a) t = typedValuesToRTB
( UntappedValue $ ceiling $ α * exp (-λ * t_Δ) + ε ) []
where t_s = a^.settledAt
α = a^.αVal
ε = a^.εVal
λ = a^.decayingFactor
t_Δ = fromIntegral (t - t_s)
Λ
value.Challenge: How to augment previously mentioned semantics with 1 to N proportional distribution semantic?
A reminder, lenses for the ConstantFlow
:
class (Default amuLs, SuperfluidTypes sft)
=> MonetaryUnitLenses amuLs sft | amuLs -> sft where
settledAt :: Lens' amuLs (SFT_TS sft)
settledUntappedValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft))
netFlowRate :: Lens' amuLs (SFT_MVAL sft)
ConstantFlow
and universal indexed lenses gives you the 1to1 semantic agreements:
data UniversalData sft = UniversalData -- UniversalMonetaryUnitData
{ -- ...
, cfa_settled_at :: SFT_TS sft
-- ...
-- | Monetary unit lenses for the universal index.
instance SuperfluidTypes sft => CFMUD.MonetaryUnitLenses (UniversalData sft) sft where
settledAt = $(field 'cfa_settled_at)
settledUntappedValue = $(field 'cfa_settled_untapped_value)
netFlowRate = $(field 'cfa_net_flow_rate)
Constant Flow Distribution Agreement.
Sub systems created through "agreement framework" have:
1to1
and 1toN proportional distribution
semantics.How to have compositionality and add context to the system?
DISCLAIMER: Not everything mentioned here is implemented in the spec yet, but we will show the working Haskell code that demonstrates the main idea.
-- simplified for brevity
class AgreementOperationWithData a where
runAgreement :: a -> IO ()
data PaperAgreement = MkPaperAgreement String
instance AgreementOperationWithData PaperAgreement where
runAgreement (MkPaperAgreement s) = putStrLn s
-- | A contract executable within the IO monad context.
data Contract = Contract
{ contractPreCond :: IO Bool
, contractExecs :: [AnyAgreementOperationWithData]
, contractAttachedCond :: (IO Bool, AnyAgreementOperationWithData)
}
-- Define an existential type to avoid an explicit sum type...
data AnyAgreementOperationWithData = forall a.
AgreementOperationWithData a => MkAnyAOD a
instance AgreementOperationWithData AnyAgreementOperationWithData where
runAgreement (MkAnyAOD a) = runAgreement a
data Contract = Contract
{ contractPreCond :: IO Bool
, contractExecs :: [AnyAgreementOperationWithData]
, contractAttachedCond :: (IO Bool, AnyAgreementOperationWithData)
}
execContract :: Contract -> IO ()
execContract (Contract preCond execs _) = do
pred <- preCond
if pred then mapM_ runAgreement execs
else putStrLn "No bueno, amigo"
execCondContract :: Contract -> IO ()
execCondContract (Contract _ _ (cond, a)) = do
pred <- cond
if pred then runAgreement a
else putStrLn "No tan rápida, amigo"
main = do
brrr <- newIORef False
let sec_says_no = return False
let sec_says_yes = return True
let when_money_goes_brrr = readIORef brrr
let c0 = Contract
sec_says_no
[ MkAnyAOD $ (MkPaperAgreement "Elon Musk buys Twitter") ]
(return False, MkAnyAOD $ MkPaperAgreement "Not possible")
let c1 = Contract
sec_says_yes
[ MkAnyAOD $ MkPaperAgreement "Bob sends Alice $42 dollar"
, MkAnyAOD $ MkPaperAgreement "Alice sends Carol $4.2 dollar"
]
( when_money_goes_brrr,
MkAnyAOD $ MkPaperAgreement "Alice pays Bob back $420 dollars" )
putStrLn "# Contract 0"
execContract c0
putStrLn "# Contract 1"
execContract c1
execCondContract c1
putStrLn "Money goes brrr..."
writeIORef brrr True
execCondContract c1
# Contract 0
No bueno, amigo
# Contract 1
Bob sends Alice $42 dollar
Alice sends Carol $4.2 dollar
No tan rápida, amigo
Money goes brrr...
Alice pays Bob back $420 dollars
"How to write a financial contract" by Simon Peyton Jones & Microsoft Research.
European option: at a particular date you may choose to acquire an “underlying” contract, or to decline
or :: Contract -> Contract -> Contract
-- Acquire either c1 or c2 immediately
zero :: Contract
-- A worthless contract
european :: Date -> Contract -> Contract
european t u = at t (u `or` zero)
Money at dynamic level has "potential to update its context".
class ( Monad tk
, SuperfluidTypes sft
, Account acc sft
) => Token tk acc sft | tk -> acc, tk -> sft where
balanceOfAccount :: ACC_ADDR acc -> tk (SFT_RTB sft)
transfer :: CONTRACT_ACC_ADDR acc (ITA.Operation sft) -> SFT_MVAL sft -> tk ()
updateFlow :: CONTRACT_ACC_ADDR acc (CFA.Operation sft) -> CFA.FlowRate sft -> tk ()
-- ...
distributeProportionally
:: ACC_ADDR acc -- publisher
-> ProportionalDistributionIndexID -- indexId
-> SFT_MVAL sft -- amount
-> tk ()
-- ...
Limit / Colimit.