anilhelvaci / agoric-sdk

monorepo for the Agoric Javascript smart contract platform
Apache License 2.0
0 stars 0 forks source link

Multiple vault types for a given collateral type #1

Open anilhelvaci opened 1 month ago

anilhelvaci commented 1 month ago

Context

See https://github.com/Agoric/agoric-sdk/issues/6518

Development Analysis

Components Expected to be Affected

Testing Considerations

Open Questions

Is it possible to have more than one retail vault manager?

In other words, for a given and already accepted collateral type, is it safe to assume that all incoming new vault managers will be of type institutional or can EC choose to create a new retail vault manager?

I believe the answer to this question is most likely "yes". See https://github.com/anilhelvaci/agoric-sdk/issues/3

Can EC add new accounts to the whitelist for a particular vault manager?

Can EC remove accounts from the whitelist for a particular vault manager?

Regression Considerations

Changing the way we index vault managers would probably end up changing a lot of the existing tooling around vaultFactory.

Deliverables

All the iterations will come with their corresponding FE implementations.

Iteration One: Enable only institutional vault managers first

anilhelvaci commented 3 weeks ago

Example FE flow for iteration one

sequenceDiagram
    participant user as institutionalUser
    participant int as dapp-inter
    participant vs as vStorage
    participant vf as vaultFactory

    user ->> int: connect keplr
    int ->> vs: query = list of institutional managers and the white-list
    vs -->> int: [manager0, manager1]
    Note over int, int: find which managers the user is whitelisted 
    int ->> int: display retail AND institutional managers
    int ->> int: display all vaults belonging to the user
    int -->> user: page loaded
    user ->> int: Click "open vault" for institutional manager
    int ->> int: calculate prevOfferId 
    Note over int,int: Inst users can send offers using continuing invitations
    Note over int,int: e.g {address}-manager-{managerIndex}-{?iteration}
    int ->> vf: Send vault offer
    Note over int, vf: offer = {invitationSpec = {source = continuing, prevOfferId, makeVaultInvitation}}
    vf -->> int: success
anilhelvaci commented 2 weeks ago

New Approach to Retail VMs

After giving some deeper thoughts, it is clear that we need identifiers for both retails and institutional VMs. Which leads me to think that we can leverage these identifiers to store our VMs. Below is a high level illustration of how I think we can store the ever increasing VMs.

flowchart LR
    A(EC) --> |Add new VM| D(VaultDirector)
    D(VaultDirector) --> C{Retail or Institutional}
    C -->|Retail| F(Retail VMs Store) 
    --> |collateralBrand| E(Get VM store for a Col Type) 
    --> |VM identifier| K(Init new retail VM)
    C -->|Institutional| G(Institutional VM store)
    --> |collateralBrand| N(Get VM store for a Col Type)
    --> |VM identifier| L(Init new retail VM)

However, before we come to any conclusion we must answer more questions. Preferably with diagrams. Here are some of those questions.

How is EC going to update parameters for retail and institutional VMs?

sequenceDiagram
    actor mem as Member
    participant ec as EC UI
    participant vs as vStorage
    participant sw as Member SmartWallet
    participant ch as EconCharter
    participant gv as VF Governor
    participant vf as VaultFactory
    mem ->> ec: Raise DebtLimit for ATOM-A
    ec ->> vs: Query vaultManager specifiers
    vs -->> ec: { collateralBrand, managerIdentifier }
    ec ->> ec: Build offerSpec
    ec ->> sw: Initiate election
    Note over ec, sw: offerSpec = {..., offerArgs = {..., path = {paramPath = {key = {collateralBrand, managerIdentifier}}}}}
    sw ->> ch: Forward offer
    ch ->> ch: Lookup relevant governor
    ch ->> gv: voteOnParamChanges(..., {...path})
    gv ->> vf: Lookup path
    vf -->> gv: ATOM-A paramManager
    gv ->> gv: Election result = Positive
    gv ->> gv: Update params
    gv -->> sw: Offer succesful
    sw -->> vs: Offer succesful
    vf ->> vs: Params updated
    ec ->> vs: Lookup params
    ec ->> ec: Display updated params

We expect this process to be the same for both retail and institutional managers as institutional manager will also have their own identifier in vstorage.

How is an outside user will know about the VM identifiers? How will the vstorage look like? What will be the form of the VM identifiers?

Here's a snapshot of how vstorage looks for a vault manager;

published
│__vaultFactory
│  │__managers
│     │__manager0
│        │__governance
│        │__metrics
│        │__quotes
│        │__vaults         
│     │__manager1
│        │   ...

I suggest we introduce a new child node under manager{manager-index} called metadata. This metadata child can have a data model similar to below;

Note that data shown below is just a thought and not the final data model

const metadata = {
  identifier,
  title: "ATOM-A",
  whitelist: ["agoric1....4w", "agoric1....2q", ...], // Means this is an institutional VM
}

So vstorage after the upgrade will look like below;

published
│__vaultFactory
│  │__managers
│     │__manager0
│        │__governance
│        │__metrics
│        │__quotes
│        │__vaults 
│        │__metadata ***         
│     │__manager1
│        │   ...

Form of VM Identifiers: String vs Handle? [WIP]

VM identifiers are intended to serve as the key in vmStore. Here are main parameters I use to decide;

See discussion in agoric-sdk => https://github.com/Agoric/agoric-sdk/discussions/9663

How is an ordinary user going to open a vault on a newly added retail VM?

sequenceDiagram
   participant sw as User SmartWallet
   participant vd as VaultDirector
   participant vm as VaultManager

   Note over sw: user selects one of the vaultManagers displayed on dapp-inter
   sw ->> vd: getCollateralManager(brandIn, managerIdentifier)

   vd ->> vd: retailVMsStore.get(brandIn)
   vd ->> vd: ${brandIn}Store.get(managerIndex)

   vd -->> sw : vaultManagerPublicFacet
   sw ->> vm : makeVaultInvitation()
   vm -->> sw : invitation
   sw ->> vm : makeVaultKit()
   vm -->> sw : vaultKit

How is an institutional user going to be granted access to an institutional VM?

sequenceDiagram
    participant vd as VaultDirector
    participant vf as VaultFactory
    participant hl as VM Holder
    participant sw as User SmartWallet

    vd ->> vd: addNewManager(collateralBrand, initialParamValues)
    vd ->> vf: makeVaultManagerKit()
    vf -->> vd: kit
    vd ->> hl: makeVmHolder(kit.self.getPublicFacet(), getIterationCount)
    hl ->> hl: currentIteration = getIterationCount()
    Note over hl: currentIteration enables revoking access
    hl -->> vd: holder
    Note over vd, hl: holder = {invitationMakers = {MakeVault = pf.makeVaultInvitation}, holder = { getQuotes, getCompoundedInterest, getPublicTopics }}
    vd ->> sw: holder

Using a holder pattern similar to vaultHolder.js here. The main reason using a holder instead of a raw invitationMakers object is that vaultManager's publicFacet does not only include methods for making new invitations but also has some utility read only methods like getQuotes. So in order not to leave those out a holder pattern is probably useful.

In addition to what's written above we can use a method like isActive to check current iteration against the holder's iteration count in order decide if this user is revoked or not. See owned method in vaultHolderi.

https://github.com/anilhelvaci/agoric-sdk/blob/5d7774e926712f9c60d11552f6638434ee831687/packages/inter-protocol/src/vaultFactory/vaultHolder.js#L63-L69

How is an institutional user going to open a vault on an institutional VM?

sequenceDiagram
    actor us as Institutional User
    participant ui as dapp-inter 
    participant sw as User SmartWallet
    participant ivm as Institutional VM

    us ->> ui: select institutional VM
    us ->> ui: enter vault parameters
    us ->> ui: click open vault
    ui ->> ui: build offer spec
    note over ui: {invitationSpec= {previousOffer= "{$address-manager-$managerIndex}"}, proposal, offerArgs}
    ui ->> sw: offer sent
    sw ->> sw: find continuing invitation for {$address-manager-$managerIndex}
    sw ->> sw: invitationMakers.makeVaultInvitation()
    sw ->> ivm: forward offer
    ivm ->> ivm: makeVatultKit()
    ivm -->> sw: vaultHolder
    sw -->> ui: success

What are the structural differences between retail and institutional VMs?

A Retail Vault Manager is a vm that its public facing methods (collateral facet) are accessible to anybody. Anyone wishing to open a vault in these VMs can do so.

An Institutional Vault Manager is a vm that only a set whitelisted account can access its public facing methods (collateral facet). EC is in charge of determining the whitelisted accounts. If manager{managerIndex} storage node's child node metadata has a non-empty whitelist array, this means that vault manager is for institutional purposes and only those accounts listed under metadata.whitelist can send transactions to interact with that VM.

Is this new way of storing VMs going to cause any changes how vault lifecycles are carried out?

As far as I can see, vault lifecycle is handled separately via methods of vaultHolder. As a result for an institutional user having vaults from both institutional and retails VMs, all those vaults show up under wallet.{address}.current.offerToPublicSubscriberPaths with their own offerIds as the key value. As a result an institutional user's smart wallet will have lifecycle methods for both institutional and retail vaults. Therefor, I don't expect any changes to the process as well.

Jorge-Lopes commented 2 weeks ago

New Approach to Retail VMs

Just to clarify if I understood your approach, the structure of VaultManagers stores will be similar to the following?

Retail or Institutional VMs Store Key (collateralBrand) Value (VM store)
stAtom stAtomStore
stOsmo stOsmoStore
stAtomStore Key (VM identifier) Value (VM)
manager101 vaultManager
manager102 vaultManager
Jorge-Lopes commented 2 weeks ago

How is an outside user will know about the VM identifiers? How will the vstorage look like?

The current structure of vStorage is:

published
│__vaultFactory
│  │__managers
│     │__manager0
│        │__vaults
│           │__vault0
│           │__vault1
│     │__manager1
│        │   ...

Lets assume that we wish to implement the minimum changes possible to keep back compatibility, but still be able to open and query a vaultManager that has the same collateral as other vaultManagers.

If we record on vstorage the Retail and Institutional VMs Store, from which we can fetch the manager index, we could keep the same vstorage structure by simply add a new step on the query process.

Diagram in progress ...

Jorge-Lopes commented 2 weeks ago

How is an institutional user going to be granted access to an institutional VM?

Assumptions:

sequenceDiagram

    actor I as Institution
    participant EC as EconCommittee
    participant ECC as EconCommitteeCharter
    participant VD as VaultDirector
    participant PS as PostalService

    I ->> EC: provide account addresses to be whitelisted
    I ->> EC: provide desired collateral and initialParamValues
    Note over I, EC: Should the communication above happen off-chain?
    EC ->> ECC: makeCharterMemberInvitation
    ECC -->> EC: invitationMakers
    note over EC: invitationMakerName: 'VoteOnApiCall'
    note over EC: invitationArgs: [VaultDirector, addNewManager, [collateralIssuer, initialParamValues]]
    EC ->> VD : executeOffer()

    create participant VM as VaultManager
    VD ->> VM: makeVaultManagerKit()
    VM -->> VD: VaultManagerKit
    VD ->> VD: Build invitationMaker for the institution
    Note over VD: { invitationMakers = { GetVmPF = kit.self.getPublicFacet } }
    loop Whitelisted addresses
        VD ->> PS: sendTo(addr, payment)
    end
    PS -->> I: Invitation
    I ->> I: Exercise invitation to fetch VaultManager publicFacet
    I ->> VM: makeVaultInvitation()
    VM -->> I: vaultKit

VaultManager PublicFacet (collateral interface):

  collateral: M.interface('collateral', {
        makeVaultInvitation: M.call().returns(M.promise()),
        getPublicTopics: M.call().returns(TopicsRecordShape),
        getQuotes: M.call().returns(NotifierShape),
        getCompoundedInterest: M.call().returns(RatioShape),
      }),
Jorge-Lopes commented 2 weeks ago

How is an ordinary user going to open a vault on a newly added retail VM?

How is an institutional user going to open a vault on an institutional VM?

The difference between this approach and the existing one is that the offer to retrieve the expected VaultManager has to provide, in addition to the brandIn, the managerIndex. If the vaultManager selected on the dapp-inter UI is an Institutional one, it will be also required to passed the parameter institutional as true. These additional parameters will be used to fetch the VaultManager from its respective durable storage.

sequenceDiagram

   participant sw as User SmartWallet
   participant vd as VaultDirector
   participant vm as VaultManager

    Note over sw: user selects one of the vaultManagers displayed on dapp-inter
    sw ->> vd: getCollateralManager(brandIn, managerIndex, institutional: false)

    alt intitutional === false
        vd ->> vd: retailVMsStore.get(brandIn)
        vd ->> vd: ${brandIn}Store.get(managerIndex)
    else institutional === true
        vd ->> vd: institutionalVMsStore.get(brandIn)
        vd ->> vd: ${brandIn}Store.get(managerIndex)
        note over vd: Not sure if next step is required considering that dapp-inter will only display whitelisted VMs
        vd ->> vd: validate if address is whitelisted
    end

    vd -->> sw : vaultManagerPublicFacet
    sw ->> vm : makeVaultInvitation()
    vm -->> sw : invitation
    Note over sw, vm: user exercises the invitation
    sw ->> vm : makeVaultKit()
    vm -->> sw : vaultKit
Jorge-Lopes commented 2 weeks ago

What are the structural differences between retail and institutional VMs?

In the collapsable section bellow, it is displayed the VaultManager structure and data currently present on Vstorage. This should be kept as is for both retail and institutional VMs since it exposes the necessary data for the inter-protocol logic and transparency.

Note: this assume as premisse that the institutional VMs will have the same level of transparency as the retail ones.


Current structure of a VaultManager ### Vstorage ``` published │__vaultFactory │ │__managers │ │__manager0 │ │__governance │ │__metrics │ │__quotes │ │__vaults ``` **manager0.governance:** ```json { "value": { "blockHeight": "14588605", "values": [ { "body": { "current": { "DebtLimit": { "type": "amount", "value": { "brand": "$0.Alleged: IST brand", "value": "+500000000000" } }, "InterestRate": { "type": "ratio", "value": { "denominator": { "brand": "$0", "value": "+10000" }, "numerator": { "brand": "$0", "value": "+75" } } }, "LiquidationMargin": { "type": "ratio", "value": { "denominator": { "brand": "$0", "value": "+10000" }, "numerator": { "brand": "$0", "value": "+15000" } } }, "LiquidationPadding": { "type": "ratio", "value": { "denominator": { "brand": "$0", "value": "+10000" }, "numerator": { "brand": "$0", "value": "+1000" } } }, "LiquidationPenalty": { "type": "ratio", "value": { "denominator": { "brand": "$0", "value": "+10000" }, "numerator": { "brand": "$0", "value": "+1000" } } }, "MintFee": { "type": "ratio", "value": { "denominator": { "brand": "$0", "value": "+10000" }, "numerator": { "brand": "$0", "value": "+50" } } } } } } ] } } ``` **manager0.metrics:** ```json { "value": { "blockHeight": "15731831", "values": [ { "body": { "liquidatingCollateral": { "brand": "$0.Alleged: ATOM brand", "value": "+0" }, "liquidatingDebt": { "brand": "$1.Alleged: IST brand", "value": "+0" }, "lockedQuote": null, "numActiveVaults": 21, "numLiquidatingVaults": 0, "numLiquidationsAborted": 0, "numLiquidationsCompleted": 21, "retainedCollateral": { "brand": "$0", "value": "+0" }, "totalCollateral": { "brand": "$0", "value": "+2372752124" }, "totalCollateralSold": { "brand": "$0", "value": "+79749677" }, "totalDebt": { "brand": "$1", "value": "+2852358601" }, "totalOverageReceived": { "brand": "$1", "value": "+0" }, "totalProceedsReceived": { "brand": "$1", "value": "+622939841" }, "totalShortfallReceived": { "brand": "$1", "value": "+25127984" } } } ] } } ``` **manager0.quotes:** ```json { "value": { "blockHeight": "15766049", "values": [ { "body": { "quoteAmount": { "brand": "$0.Alleged: quote brand", "value": [ { "amountIn": { "brand": "$1.Alleged: ATOM brand", "value": "+1000000" }, "amountOut": { "brand": "$2.Alleged: IST brand", "value": "+6106876" }, "timer": "$3.Alleged: timerService", "timestamp": { "absValue": "+1720433835", "timerBrand": "$4.Alleged: timerBrand" } } ] }, "quotePayment": "$5.Alleged: quote payment" }, } ] } } ``` **manager0.vaults.vault0** ```json { "value": { "blockHeight": "10445656", "values": [ { "body": { "debtSnapshot": { "debt": { "brand": "$0.Alleged: IST brand", "value": "+0" }, "interest": { "denominator": { "brand": "$0", "value": "+100" }, "numerator": { "brand": "$0", "value": "+100" } } }, "locked": { "brand": "$1.Alleged: ATOM brand", "value": "+0" }, "vaultState": "closed" } } ] } } ```

One of the main structural difference that we anticipate between retail and institutional VaultManagers is the addresses whitelist that defines the identifies the accounts that should be able to visualize the respective VaultManagers on the inter-protocol app.

As described above, one possible solution for is to add an additional child node under manager{manager-index} called metadata. If the whitelist attribute is set with a list of addresses, that would mean we are interacting with an institutional VaultManager.

const metadata = {
  identifier,
  title: "ATOM-A",
  whitelist: ["agoric1....4w", "agoric1....2q", ...], // Means this is an institutional VM
}

Other structural difference is the process required to create a new VaultManager. The retail VMs will continue to follow the current flow where a core-eval proposal is submitted and voted by the BLD stakers. Regarding the institutional VMs, they will be created via the EconComittee as it is described on this diagram https://github.com/anilhelvaci/agoric-sdk/issues/1#issuecomment-2208664281

otoole-brendan commented 1 week ago

The retail VMs will continue to follow the current flow where a core-eval proposal is submitted and voted by the BLD stakers. Regarding the institutional VMs, they will be created via the EconComittee as it is described on this diagram https://github.com/anilhelvaci/agoric-sdk/issues/1#issuecomment-2208664281

@Jorge-Lopes @anilhelvaci - Can you clarify this statement? If a collateral type has already been approved by the BLD stakers - then it's over to the EC to add additional vault types of that already supported collateral type. i.e. new vault types of already supported collateral type shouldn't need to be voted in by BLD stakers.

Similarly for those institutional vaults - if the collateral type has already been added via BLD stakers - i.e. ATOM then the EC should be able to create an institutional ATOM vault from the Econ Gov UI - rather than it needing to go back to BLD stakers. Does both of these flows aligns with what you had in mind?

Jorge-Lopes commented 1 week ago

@Jorge-Lopes @anilhelvaci - Can you clarify this statement? If a collateral type has already been approved by the BLD stakers - then it's over to the EC to add additional vault types of that already supported collateral type. i.e. new vault types of already supported collateral type shouldn't need to be voted in by BLD stakers.

Similarly for those institutional vaults - if the collateral type has already been added via BLD stakers - i.e. ATOM then the EC should be able to create an institutional ATOM vault from the Econ Gov UI - rather than it needing to go back to BLD stakers. Does both of these flows aligns with what you had in mind?

Yes, the flow you described aligns with our design. I apologize if the previous comment was unclear; it was a draft meant to initiate discussion.

We are currently preparing a new issue as Analysis Milestone: 2. The goal is to provide a clear description of the intended design, supported by necessary diagrams for comprehensive visualization.

This issue will take into account your responses from Analysis Milestone: 1, as well as the recent feedback you provided.