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

Standalone line of credit #3991

Closed
JimLarson opened this issue Oct 23, 2021 · 23 comments
Closed

Standalone line of credit #3991

JimLarson opened this issue Oct 23, 2021 · 23 comments
Assignees
Labels
agd Agoric (Golang) Daemon cosmic-swingset package: cosmic-swingset enhancement New feature or request

Comments

@JimLarson
Copy link
Contributor

What is the Problem Being Solved?

We need a way to generate RUN in the mainnet0 timeframe. Some accounts will have BLD, and no fees will be charged yet.

Description of the Design

  • Tweak the existing x/lien module to serve as the source-of-truth for liens.
    • Add compare-and-swap fields in SetLien request.
  • Add a command for taking a line of credit in RUN against BLD in one's own account.
    • Required collateralization rate specified in module parameters, modifiable by governance.
    • Module must be able to mint RUN.
  • Same command can repay the LoC, lifting the lien.
    • Need to establish behavior when multiple LoCs are present with different collateralization rates.
  • Switches over to JS-controlled LoC system in mainnet1
    • Need to be able to disable standalone LoC creation
    • JS needs to be able to initialize LoC state from existing standalone LoCs.

Security Considerations

Must ensure conservation of liened BLD vs RUN.

Test Plan

Unit tests.

Manual testing on testnet:

  • cannot take nonzero LoC without BLD
  • produced RUN is transferrable
  • liened BLD is not
  • can change collateralization ratio
  • payoff works
@JimLarson JimLarson added the enhancement New feature or request label Oct 23, 2021
@JimLarson JimLarson self-assigned this Oct 23, 2021
@dckc dckc added this to the Mainnet: Phase 0 - Cosmos Launch milestone Oct 25, 2021
@dckc
Copy link
Member

dckc commented Oct 25, 2021

We need a way to generate RUN in the mainnet0 timeframe.

We do? This is news to me. In our Sep 8 community call, we (i.e. @rowgraus ) said the scope of mainnet 0 is:

  • Cosmos SDK Layer-1 Chain with staking and governance only; transfers not yet enabled
  • Distributing BLD tokens to validators, investors, and stakeholders

Did something change? Or did I misunderstand the Sep 8 scope?

@dckc dckc added the agd Agoric (Golang) Daemon label Oct 25, 2021
@JimLarson
Copy link
Contributor Author

Check with @dtribble as to whether this is a change to mainnet 0 scope, or just a feature that didn't get top billing.

@JimLarson
Copy link
Contributor Author

Input from @michaelfig: just use the same command as for creating a new periodic vesting account, but with a --merge flag to allow it to merge the schedule with an existing account.

I considered having it, at merge time, rolling up vesting events which had passed, but I think this adds complexity for no specific benefit.

@JimLarson
Copy link
Contributor Author

Also:

  • Use sdk.Dec type for conversion factor - don't need to coerce to an integer.
  • Be prepared to have initiative go back and forth between JS and this Cosmos-level system (but each time via a configuration change via governance) to give a way out in case there's a flaw in the JS level.
  • Refactor the lien vm.PortHandler to move the logic into keeper functions, leaving only the JSON conversion.
  • Create a new x/loc module to store the line-of-credit information, which will call the above x/lien keeper methods.
  • Later (but still soon) refactor x/lien to move controller-dependent logic into x/vlien so x/lien and x/loc can be moved to the ag0 repository and be useful within the Cosmos community.
    • Things will start in agoric-sdk for now.

@michaelfig
Copy link
Member

  • Use sdk.Dec type for conversion factor - don't need to coerce to an integer.

I'm thinking the conversion is from the "staking token denom" (never explicitly ubld) and there is a rate consisting of sdk.DecCoins so that the multiplication can be rounded down into an sdk.Coins (never assuming urun or just a single resulting denom).

This is more in line with how, say, the gas fees are handled.

@JimLarson
Copy link
Contributor Author

Notes from conversation with @rowgraus

  • If we charge interest on LoC, it will apply to all extant LoC's regardless of when they were initiated. The interest rate is not a property of the LoC, but of current conditions.
  • Because of overcollateralization of the LoC, it's reasonable to assume that the collateralization ratio will remain stable, perhaps for the entire duration of use of the "standalone" implementation.
  • The "refinance" semantics are sufficient and simple: to change one's LoC, it's equivalent to atomically paying off the old one and taking out a new one, with only the delta affecting the RUN balance and amount of liened BLD.
    • If the required collateral rate goes up, it will be necessary to "backfill" the existing RUN LoC by liening additional BLD before any BLD gets liened for new RUN. If there is not enough available BLD to backfill, the additional RUN may not be taken.
    • However, we don't want to block someone from repaying RUN (and thereby lowering interest they might be paying) even if they are over their credit limit, according to current collateral ratios. We've got to make an exception to the logic in this direction.
    • This leaves the system open to "gaming", where a user could get better LoC terms by transferring some BLD to a different account. However, due to the overcollateralization, we don't think this is a capital-efficient exploit.

@JimLarson
Copy link
Contributor Author

JimLarson commented Nov 1, 2021

Draft interface:

Golang keeper API:

GetLoC(ctx sdk.Context, addr sdk.AccAddress) (collateral, loan sdk.Coins)
SetLoC(ctx sdk.Context, addr sdk.AccAddress, oldCollateral, oldLoan, newCollateral, newLoan sdk.Coins) error

GetLoC() returns zero amounts if no LoC is present.

The SetLoC() call has several checks and effects:

  • The old amounts in SetLoC() must match the current LoC state.
  • In "standalone" mode, the new collateral/loan ratio must match the configured ratio. In JS-master mode, the given amounts are assumed to be correct.
  • If the loan amount is increasing, the new collateral must not be greater than the amount in the given account. However, if the loan is being paid down, it is okay to not have the new required collateral available: the account becomes "under water" from a lien perspective, but you can always pay down a loan.
  • The lien is set to the new collateral amount.
  • If the loan is increasing, the increment is minted and added to the account. If the loan is decreasing, the decrement is removed from the account and burned. There must be enough of the loan denomination available to do this.

The configuration will specify the size of loan that can be taken for one unit of collateral, specifying both denominations. In the expected case of a RUN LoC on BLD, this gives an amount of RUN per liened BLD, which should be more immediately legible than the inverse price of BLD.

Question: should we specialize the API to list loan and collateral to sdk.Coin? This would simplify the configuration and the JSON messages.

@JimLarson
Copy link
Contributor Author

Since the x/loc module needs to be able to mint and burn the loan coins when running in standalone mode, I'd prefer to have it also do so when commanded by JS, just for less confusion about which side has the authority and when.

@JimLarson
Copy link
Contributor Author

To adjust the standalone line of credit, I'm only requiring that the requestor specify the new loan amount, with the collateral calculated automatically from the configured collateralization ratio. There is no need to make the requestor do a computation that the chain needs to do anyhow.

I'm reconsidering whether to make the requestor specify the current line of credit details, originally thought to be a guard against conflicting, concurrent loan adjustment requests. But on further reflection, I don't think it's necessary. The parameter settings ought to resolve conflicts between JS- and standalone-initiated actions, and there isn't such protection around, e.g. bank transfer requests. The loan adjustment specifies the desired state, rather than the desired delta, which provides additional integrity. Lastly, the collateral amount might not be rounded, so specifying such a large number exactly is not at good user experience. Thoughts?

@michaelfig
Copy link
Member

To adjust the standalone line of credit, I'm only requiring that the requestor specify the new loan amount, with the collateral calculated automatically from the configured collateralization ratio. There is no need to make the requestor do a computation that the chain needs to do anyhow.

Does this accommodate if the user wants to overcollateralize the loan? Maybe have a talk with @dckc and @btulloh to see what they arrived on in their recent discussions.

As for specifying the desired loan state, that makes sense to me.

@michaelfig
Copy link
Member

Since the x/loc module needs to be able to mint and burn the loan coins when running in standalone mode, I'd prefer to have it also do so when commanded by JS, just for less confusion about which side has the authority and when.

That seems reasonable. Again, though, maybe involve @dckc to see how that lines up with the JS side.

@dckc
Copy link
Member

dckc commented Nov 22, 2021

Since the x/loc module needs to be able to mint and burn the loan coins when running in standalone mode, I'd prefer to have it also do so when commanded by JS, just for less confusion about which side has the authority and when.

That seems reasonable. Again, though, maybe involve @dckc to see how that lines up with the JS side.

I expect this is all fine, but I don't understand what "loan coins" means. I would appreciate a worked example. I can stand by for tests.

@JimLarson
Copy link
Contributor Author

Notes on today's conversation with @dckc and @rowgraus

  • LoC should have independent operations to set collateral and loan:
    • collateral is never seized - must be explicitly placed under lien
    • collateralization ratio determines maximum loan possible given current collateral
      • the natural units are RUN per BLD
    • changing the loan
      • loan can always be paid down, even if ending loan amount is higher than the maximum given the current collateralization ratio
      • increasing the loan requires recalculating the maximum with the current collateralization ratio
    • changing the collateral
      • new amount is all staked (bonded)
      • loan (if any) is at or under max with current ratio
    • keep current lien behavior - can go "under water" due to slashing, can unbond
  • Paying off the loan with rewards
    • getting rewards triggered by some manual action (or epoch?) as it is without LoC
    • as above, loan amount can always be reduced regardless of other factors
    • question is how much of the reward stream gets intercepted to pay down the loan
      • maybe all
      • maybe proportional to liened amount vs all staked BLD
  • Standalone LoC can skip some features
    • no need for global limit on total loans
    • no RUN rewards, hence no reward interception

@JimLarson
Copy link
Contributor Author

Question on the desired UI: do we want to support setting collateral and obtaining a loan as a single action? Or do we always want to do this as separate actions? @rowgraus

@JimLarson
Copy link
Contributor Author

I reviewed the treasury vault management UI. It's not an exact match, as we're intentionally allowing the loan to be less than the maximum allowed by the collateral. Here's my proposal for the commands and queries for the standalone LoC:

  • Query to get the collateralization ratio
    • agd query loc ratio
    • Gives result in maximum loan-per-collateral, with the loan denom. The collateral denom is implicitly the staking token.
    • We'll add other commands to query other params, but this is currently the only one.
  • Query to get the current LoC state
    • agd query loc get <myaddr>
    • Returns
      • collateral
      • loan
      • max possible loan, given the collateral (if in standalone mode)
    • Works in standalone or JS-master mode
  • Transaction to set collateral and loan
    • agd tx loc set <myaddr> <collateral> <loan>
    • loan defaults to zero if omitted
  • Transaction to set loan only
    • agd tx loc setloan <myaddr> <loan>

@dckc
Copy link
Member

dckc commented Nov 30, 2021

The name set for taking out a loan seems awkward.

I don't have a suggested alternative, though.

@JimLarson
Copy link
Contributor Author

I agree that the bare set is odd, but it makes more sense in the context of the whole command line. I considered setloc but loc setloc seemed like unnecessary stuttering. I also considered setcollateral to adjust only the collateral, but I got feedback that it was better to be able to adjust both at once.

Another alternative to the above is to make commands for deltas instead of just setting the new state, e.g. incr-collateral, repay-loan, etc. The current commands are idempotent, though. I'm not sure how important that is.

@dckc
Copy link
Member

dckc commented Nov 30, 2021

That all makes sense. I look forward to hearing from @rowgraus .

@rowgraus
Copy link

rowgraus commented Dec 1, 2021

Apologies for delay.

On idempotency - not the best person to answer. Could imagine delays in blockchain processing causing users to make errors (e.g., sending too many transactions) which idempotency would help with.

On the set command - presumably I can reduce my collateral/loan amounts there as well? And if I'm attempting to reduce the collateral it checks against the maximum loan per collateral?

If so to the above this all looks good to me

@JimLarson
Copy link
Contributor Author

Yes to all - both transactions allow the loan amount to be adjusted up or down, to come from or go to zero, and the system will check that the loan is compatible with the amount of collateral. The set command also allows the collateral to go up or down, and to come from or go to zero.

@JimLarson
Copy link
Contributor Author

Details on checking a change to LoC.

Balance-independent checks, considering only new vs old loan and collateral:

  • From the new collateral and the current ratio, compute max_loan.
  • If we're increasing the loan or decreasing the collateral, the new loan must not be greater than max_loan.
    • Otherwise, if loan is non-increasing and collateral is non-decreasing, we can be over max_loan. This is possible after the collateralization ratio is changed.

Balance-dependent checks:

  • If increasing collateral or increasing the loan, must have the new collateral available as staked tokens (already implemented in x/lien).
  • If decreasing loan, must have the decrement available in the account to burn.
    • A separate operation will decrement the loan balance from intercepted rewards which won't do this check.

The composition of legal commands is also legal, which explains the behavior of, e.g. increasing loan while decreasing collateral in a single operation.

@JimLarson
Copy link
Contributor Author

Configuration

The standalone code uses a single config parameter: maximum RUN loan per BLD. @dckc has been using two parameters: a base BLD price in RUN, and a dimensionless overcollateralization ratio, e.g. 200% means that we need to lien twice as much BLD as we'd need to sell in order to obtain the desired amount of RUN.

Either way works, but we should be consistent for terminology, docs, common tests, etc. - @rowgraus should weigh in on which he'd like to see.

Division of Labor between JS and Go

Since Go needs to be able to handle all checks and actions to run in standalone mode, it makes sense for JS to rely on these checks, at least initially. The JS will have to validate the checks related to the collateralization ratio (new loan doesn't exceed max loan for the given collateral when increasing loan or decreasing collateral), since Go will no longer know what the ratio is, but Go will handle the checks related to the balance, and the actions of adjusting the lien or minting and burning RUN.

While we're still reserving the option of reverting to standalone mode, we'll still want the Go layer to be the authoritative store of LoC state. But once we're ready to run only in JS mode, JS can import the LoC state and then use the x/lien module directly for enforcing liens.

@ivanlei
Copy link
Contributor

ivanlei commented Oct 6, 2023

this is no longer plan of record

@ivanlei ivanlei closed this as not planned Won't fix, can't repro, duplicate, stale Oct 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
agd Agoric (Golang) Daemon cosmic-swingset package: cosmic-swingset enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants