0g-chain/x/earn/keeper/vault_share.go
Derrick Lee b5e162a930
Update Earn vaults to use sdk.Dec shares (#1283)
* Change vault supply to shares

* Update deposit shares

* Use shares instead of supplied

* Update tests, fix share calculation

* Pass hard and savings keeper as pointer to earn keeper

* Update remaining failing test

* Add different share price test, fix comment for share price

* Add shares amount to events

* Additional share tests, use share to asset conversion for withdraw amount

* Update VaultTotalValue test

* Use sdk.Dec for vault shares instead of sdk.Int

* Add test for expensive 20:1 shares

* Update ConvertToShares comment for division, remove redundant test

* Add vault share tests
2022-09-12 09:23:26 -07:00

83 lines
2.8 KiB
Go

package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/earn/types"
)
// ConvertToShares converts a given amount of tokens to shares.
func (k *Keeper) ConvertToShares(ctx sdk.Context, assets sdk.Coin) (types.VaultShare, error) {
totalShares, found := k.GetVaultTotalShares(ctx, assets.Denom)
if !found {
// No shares issued yet, so shares are issued 1:1
return types.NewVaultShare(assets.Denom, assets.Amount.ToDec()), nil
}
totalValue, err := k.GetVaultTotalValue(ctx, assets.Denom)
if err != nil {
return types.VaultShare{}, err
}
if totalValue.Amount.IsZero() {
return types.VaultShare{}, fmt.Errorf("total value of vault is zero")
}
// sharePrice = totalValue / totalShares
// issuedShares = assets / sharePrice
// issuedShares = assets / (totalValue / totalShares)
// = assets * (totalShares / totalValue)
// = (assets * totalShares) / totalValue
//
// Multiply by reciprocal of sharePrice to avoid two divisions and limit
// rounding to one time. Per-share price is also not used as there is a loss
// of precision.
// Division is done at the last step as there is a slight amount that is
// rounded down.
// For example:
// 100 * 100 / 105 == 10000 / 105 == 95.238095238095238095
// 100 * (100 / 105) == 100 * 0.952380952380952380 == 95.238095238095238000
// rounded down and truncated ^ loss of precision ^
issuedShares := assets.Amount.ToDec().Mul(totalShares.Amount).QuoTruncate(totalValue.Amount.ToDec())
if issuedShares.IsZero() {
return types.VaultShare{}, fmt.Errorf("share count is zero")
}
return types.NewVaultShare(assets.Denom, issuedShares), nil
}
// ConvertToAssets converts a given amount of shares to tokens.
func (k *Keeper) ConvertToAssets(ctx sdk.Context, share types.VaultShare) (sdk.Coin, error) {
totalVaultShares, found := k.GetVaultTotalShares(ctx, share.Denom)
if !found {
return sdk.Coin{}, fmt.Errorf("vault for %s not found", share.Denom)
}
totalValue, err := k.GetVaultTotalValue(ctx, share.Denom)
if err != nil {
return sdk.Coin{}, err
}
// percentOwnership := accShares / totalVaultShares
// accValue := totalValue * percentOwnership
// accValue := totalValue * accShares / totalVaultShares
// Division must be last to avoid rounding errors and properly truncate.
value := totalValue.Amount.ToDec().Mul(share.Amount).QuoTruncate(totalVaultShares.Amount)
return sdk.NewCoin(share.Denom, value.TruncateInt()), nil
}
// ShareIsDust returns true if the share value is less than 1 coin
func (k *Keeper) ShareIsDust(ctx sdk.Context, share types.VaultShare) (bool, error) {
coin, err := k.ConvertToAssets(ctx, share)
if err != nil {
return false, err
}
// Truncated int, becomes zero if < 1
return coin.IsZero(), nil
}