0g-chain/x/liquid/keeper/derivative.go
drklee3 f757d7ab15
feat: upgrade to Cosmos v0.46 (#1477)
* Update cosmos-sdk to v0.45.10-kava

* Add RegisterNodeService to app

* Update cosmos proto files

* Update cosmos proto files

* Use tagged v0.45.10-kava-v0.19-0.21 cosmos version

* update x/auth/legacy to x/auth/migrations

* Delete rest packages and registration

* Remove rest from proposal handlers

* Remove legacy types referencing removed sdk types

* Remove legacy tx broadcast handler

* Update incentive staking hooks to return error

* Remove grpc replace directive, use new grpc version

* Fix storetypes import

* Update tally_handler with updated gov types

* Delete legacy types

* Use new gov default config

* Update RegisterTendermintService params

Signed-off-by: drklee3 <derrick@dlee.dev>

* Replace sdk.StoreKey with storetypes.StoreKey

* Replace sdk.Int#ToDec with sdk.NewDecFromInt

* Replace sdk.NewUintFromBigInt with sdkmath.NewUintFromBigInt

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update most intances of govtypes to govv1beta1

* Unpack coin slice for Coins#Sub and Coins#SafeSub

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update committee gov codec registration

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update migrate utils period_vesting Coins#Sub

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update Coin#Sub in community proposal handler

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update Coin#Sub, FundModuleAccount/FundAccount in banktestutil

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update community, earn, kavadist proposal gov registration

* Update evm cli client EthSecp256k1Type check

* AccAddressFromHex to AccAddressFromHexUnsafe

* Add mint DefaultInflationCalculationFn to earn test

* Update use of removed staking.NewHandler

* Rename FlagIAVLFastNode -> FlagDisableIAVLFastNode

* cmd: Update new snapshot app option

Signed-off-by: drklee3 <derrick@dlee.dev>

* cmd: Add tendermint default config, use cosmos rpc status command

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update ethermint import path

github.com/tharsis/ethermint -> github.com/evmos/ethermint

* Upgrade ibc-go to v6

* Update proto dependencies

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update Tally handler test with new gov types

* Update helpers.GenTx -> helpers.GenSignedMockTx

* Update evmkeeper.NewKeeper params

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update ante authz, tests

* Add feemarket transient key, pass subspaces to evm/feemarket keepers

* Update new ante decorators

* Add new addModuleInitFlags to server commands

* Pass codec to keyring.New in genaccounts

* Pass codec to client keys add

* Add SendCoins to evmutil bank_keeper

* Use github.com/cosmos/iavl@v0.19.5

* Add ante HandlerOptions

* Add unimplemented SendCoins to evmutil bank keeper

Ethermint x/evm does not use this method

* Update init-new-chain script to disable post-london blocks

* Modify test genesis states to append 1 validator

* Update tally handler test to use string values

* Prevent querying balance for empty sdk.AccAddress in auction bidding test

* Set default bond denom to ukava

* Remove overwritten bank genesis total supply in committee proposal test

Signed-off-by: drklee3 <derrick@dlee.dev>

* Use ukava for testing staked balance

* Disable minting in community proposal handler test

Previously stake denom is used, which resulted in 0 minted coins

* Update hard APYToSPY test expected value

Increased iterations in sdk.ApproxRoot, updated closer to real value

* Fix NewDecCoinsFromCoins bug in incentive collectDerivativeStakingRewards

* Allow bkava earn incentive test values to match within small margin for rounding

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update invalid denom in issuance message coin validation

Colons are now valid in denoms

Signed-off-by: drklee3 <derrick@dlee.dev>

* Remove genesis validator in incentive delegation tests

* Update pricefeed market test for invalid denom

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update incentive delegator rewards test without genesis validator

Signed-off-by: drklee3 <derrick@dlee.dev>

* Add validator to export test

* Clear bank state in minting tests

Signed-off-by: drklee3 <derrick@dlee.dev>

* Remove validator for no stake tally test

Signed-off-by: drklee3 <derrick@dlee.dev>

* Clear incentive state before InitGenesis in incentive genesis export test

* Update swagger

Signed-off-by: drklee3 <derrick@dlee.dev>

* Update ethermint version to match replaced version

* Remove legacy swagger

* Add NewEthEmitEventDecorator

* Remove redundant func for AddModuleInitFlags

* Remove unused addBankBalanceForAddress func

* Add SetIAVLLazyLoading option to app cmd

* Use legacy.RegisterAminoMsg for committee msg concrete registration

* Remove unnecessary Amino field

* Add evm_util bankkeeper SendCoins comment

* Update test method ResetBankState to DeleteGenesisValidatorCoins to be more clear

* Validate incentive params.RewardsPerSecond to be non-zero

* Validate swap pools to disallow colons in token denoms

* Register all legacy amino types on gov modulecdc

* Remove redundant Comittee interface registration

* Pin goleveldb to v1.0.1-0.20210819022825-2ae1ddf74ef7

Causes failed to load state at height errors

* Update ethermint to new pinned version with minGasPrices parse error fix

* Update cosmos fork dependcy commit to include reverted account constructor patch

* Update Cosmos v0.46.11 and cometbft v0.34.27

* Bump minimum go version to 1.19

* Update tendermint proto

* Update internal testnet genesis

* Move NewCanTransferDecorator before NewEthGasConsumeDecorator

* Add hard borrow store tests (#1514)

* add store tests for Borrow type

* refactor Deposit tests to match

* Fix old bep3 tests (#1515)

* Update Ethermint to 1b17445 to fix duplicate proto registration

* Add custom status command to use snake_case and stdout

* Add SetInflation helper

* Reduce ambiguity with evm CanSignEthTx error

* Remove init genesis validator claim in test

* Add disabled evmante.NewMinGasPriceDecorator with x/feemarket note

* chore: use tagged versions for Cosmos and Ethermint forks

* update kvtool & increase wait for ibc transfer test

---------

Signed-off-by: drklee3 <derrick@dlee.dev>
Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
Co-authored-by: Robert Pirtle <astropirtle@gmail.com>
2023-04-03 20:08:45 -04:00

198 lines
7.7 KiB
Go

package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/liquid/types"
)
// MintDerivative removes a user's staking delegation and mints them equivalent staking derivative coins.
//
// The input staking token amount is used to calculate shares in the user's delegation, which are transferred to a delegation owned by the module.
// Derivative coins are them minted and transferred to the user.
func (k Keeper) MintDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Coin, error) {
bondDenom := k.stakingKeeper.BondDenom(ctx)
if amount.Denom != bondDenom {
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom, "expected %s", bondDenom)
}
derivativeAmount, shares, err := k.CalculateDerivativeSharesFromTokens(ctx, delegatorAddr, valAddr, amount.Amount)
if err != nil {
return sdk.Coin{}, err
}
// Fetching the module account will create it if it doesn't exist.
// This is necessary as otherwise TransferDelegation will create a normal account.
modAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
if _, err := k.TransferDelegation(ctx, valAddr, delegatorAddr, modAcc.GetAddress(), shares); err != nil {
return sdk.Coin{}, err
}
liquidTokenDenom := k.GetLiquidStakingTokenDenom(valAddr)
liquidToken := sdk.NewCoin(liquidTokenDenom, derivativeAmount)
if err = k.mintCoins(ctx, delegatorAddr, sdk.NewCoins(liquidToken)); err != nil {
return sdk.Coin{}, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeMintDerivative,
sdk.NewAttribute(types.AttributeKeyDelegator, delegatorAddr.String()),
sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, liquidToken.String()),
sdk.NewAttribute(types.AttributeKeySharesTransferred, shares.String()),
),
)
return liquidToken, nil
}
// CalculateDerivativeSharesFromTokens converts a staking token amount into its equivalent delegation shares, and staking derivative amount.
// This combines the code for calculating the shares to be transferred, and the derivative coins to be minted.
func (k Keeper) CalculateDerivativeSharesFromTokens(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, tokens sdk.Int) (sdk.Int, sdk.Dec, error) {
if !tokens.IsPositive() {
return sdk.Int{}, sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "token amount must be positive")
}
shares, err := k.stakingKeeper.ValidateUnbondAmount(ctx, delegator, validator, tokens)
if err != nil {
return sdk.Int{}, sdk.Dec{}, err
}
return shares.TruncateInt(), shares, nil
}
// BurnDerivative burns an user's staking derivative coins and returns them an equivalent staking delegation.
//
// The derivative coins are burned, and an equivalent number of shares in the module's staking delegation are transferred back to the user.
func (k Keeper) BurnDerivative(ctx sdk.Context, delegatorAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) (sdk.Dec, error) {
if amount.Denom != k.GetLiquidStakingTokenDenom(valAddr) {
return sdk.Dec{}, sdkerrors.Wrap(types.ErrInvalidDenom, "derivative denom does not match validator")
}
if err := k.burnCoins(ctx, delegatorAddr, sdk.NewCoins(amount)); err != nil {
return sdk.Dec{}, err
}
modAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
shares := sdk.NewDecFromInt(amount.Amount)
receivedShares, err := k.TransferDelegation(ctx, valAddr, modAcc.GetAddress(), delegatorAddr, shares)
if err != nil {
return sdk.Dec{}, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBurnDerivative,
sdk.NewAttribute(types.AttributeKeyDelegator, delegatorAddr.String()),
sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
sdk.NewAttribute(types.AttributeKeySharesTransferred, shares.String()),
),
)
return receivedShares, nil
}
func (k Keeper) GetLiquidStakingTokenDenom(valAddr sdk.ValAddress) string {
return types.GetLiquidStakingTokenDenom(k.derivativeDenom, valAddr)
}
// IsDerivativeDenom returns true if the denom is a valid derivative denom and
// corresponds to a valid validator.
func (k Keeper) IsDerivativeDenom(ctx sdk.Context, denom string) bool {
valAddr, err := types.ParseLiquidStakingTokenDenom(denom)
if err != nil {
return false
}
_, found := k.stakingKeeper.GetValidator(ctx, valAddr)
return found
}
// GetStakedTokensForDerivatives returns the total value of the provided derivatives
// in staked tokens, accounting for the specific share prices.
func (k Keeper) GetStakedTokensForDerivatives(ctx sdk.Context, coins sdk.Coins) (sdk.Coin, error) {
total := sdk.ZeroInt()
for _, coin := range coins {
valAddr, err := types.ParseLiquidStakingTokenDenom(coin.Denom)
if err != nil {
return sdk.Coin{}, fmt.Errorf("invalid derivative denom: %w", err)
}
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Coin{}, fmt.Errorf("invalid derivative denom %s: validator not found", coin.Denom)
}
// bkava is 1:1 to delegation shares
valTokens := validator.TokensFromSharesTruncated(sdk.NewDecFromInt(coin.Amount))
total = total.Add(valTokens.TruncateInt())
}
totalCoin := sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), total)
return totalCoin, nil
}
// GetTotalDerivativeValue returns the total sum value of all derivative coins
// for all validators denominated by the bond token (ukava).
func (k Keeper) GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error) {
bkavaCoins := sdk.NewCoins()
k.bankKeeper.IterateTotalSupply(ctx, func(c sdk.Coin) bool {
if k.IsDerivativeDenom(ctx, c.Denom) {
bkavaCoins = bkavaCoins.Add(c)
}
return false
})
return k.GetStakedTokensForDerivatives(ctx, bkavaCoins)
}
// GetDerivativeValue returns the total underlying value of the provided
// derivative denominated by the bond token (ukava).
func (k Keeper) GetDerivativeValue(ctx sdk.Context, denom string) (sdk.Coin, error) {
return k.GetStakedTokensForDerivatives(ctx, sdk.NewCoins(k.bankKeeper.GetSupply(ctx, denom)))
}
func (k Keeper) mintCoins(ctx sdk.Context, receiver sdk.AccAddress, amount sdk.Coins) error {
if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, amount); err != nil {
return err
}
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, receiver, amount); err != nil {
return err
}
return nil
}
func (k Keeper) burnCoins(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) error {
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, amount); err != nil {
return err
}
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleAccountName, amount); err != nil {
return err
}
return nil
}
// DerivativeFromTokens calculates the approximate amount of derivative coins that would be minted for a given amount of staking tokens.
func (k Keeper) DerivativeFromTokens(ctx sdk.Context, valAddr sdk.ValAddress, tokens sdk.Coin) (sdk.Coin, error) {
bondDenom := k.stakingKeeper.BondDenom(ctx)
if tokens.Denom != bondDenom {
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom, "'%s' does not match staking denom '%s'", tokens.Denom, bondDenom)
}
// Use GetModuleAddress instead of GetModuleAccount to avoid creating a module account if it doesn't exist.
modAddress := k.accountKeeper.GetModuleAddress(types.ModuleAccountName)
derivative, _, err := k.CalculateDerivativeSharesFromTokens(ctx, modAddress, valAddr, tokens.Amount)
if err != nil {
return sdk.Coin{}, err
}
liquidTokenDenom := k.GetLiquidStakingTokenDenom(valAddr)
liquidToken := sdk.NewCoin(liquidTokenDenom, derivative)
return liquidToken, nil
}