mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 07:45:18 +00:00
Add derivative denom and kava value methods to liquid (#1303)
* Add IsDerivativeDenom and GetKavaForDerivatives methods to liquid * Add test case containing 2 different bkava denoms * Add doc to GetKavaForDerivatives * Remove logging statements, use keeper logger * Fix nil err use * Return error from GetKavaForDerivatives * Re-add ParseLiquidStakingTokenDenom * Add ParseLiquidStakingTokenDenom tests * Use DenomSeparator instead of str Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com> Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
This commit is contained in:
parent
ceaed3f0e1
commit
ed116b24ba
@ -2,7 +2,6 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@ -94,7 +93,7 @@ func getCmdBurnDerivative() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
valAddr, err := parseLiquidStakingTokenDenom(amount.Denom)
|
||||
valAddr, err := types.ParseLiquidStakingTokenDenom(amount.Denom)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(types.ErrInvalidDenom, err.Error())
|
||||
}
|
||||
@ -107,16 +106,3 @@ func getCmdBurnDerivative() *cobra.Command {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// parseLiquidStakingTokenDenom extracts a validator address from a derivative denom.
|
||||
func parseLiquidStakingTokenDenom(denom string) (sdk.ValAddress, error) {
|
||||
elements := strings.Split(denom, types.DenomSeparator)
|
||||
if len(elements) != 2 {
|
||||
return nil, fmt.Errorf("cannot parse denom %s", denom)
|
||||
}
|
||||
addr, err := sdk.ValAddressFromBech32(elements[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
@ -97,6 +99,42 @@ 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
|
||||
}
|
||||
|
||||
// GetKavaForDerivatives returns the total amount of the provided derivatives
|
||||
// in Kava accounting for the specific share prices.
|
||||
func (k Keeper) GetKavaForDerivatives(ctx sdk.Context, coins sdk.Coins) (sdk.Int, error) {
|
||||
totalKava := sdk.ZeroInt()
|
||||
|
||||
for _, coin := range coins {
|
||||
valAddr, err := types.ParseLiquidStakingTokenDenom(coin.Denom)
|
||||
if err != nil {
|
||||
return sdk.Int{}, fmt.Errorf("invalid derivative denom: %w", err)
|
||||
}
|
||||
|
||||
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
|
||||
if !found {
|
||||
return sdk.Int{}, fmt.Errorf("invalid derivative denom %s: validator not found", coin.Denom)
|
||||
}
|
||||
|
||||
// bkava is 1:1 to delegation shares
|
||||
valTokens := validator.TokensFromSharesTruncated(coin.Amount.ToDec())
|
||||
totalKava = totalKava.Add(valTokens.TruncateInt())
|
||||
}
|
||||
|
||||
return totalKava, nil
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -314,3 +314,158 @@ func (suite *KeeperTestSuite) TestMintDerivative() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestIsDerivativeDenom() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
valAccAddr1, delegator, valAccAddr2 := addrs[0], addrs[1], addrs[2]
|
||||
valAddr1 := sdk.ValAddress(valAccAddr1)
|
||||
|
||||
// Validator addr that has **not** delegated anything
|
||||
valAddr2 := sdk.ValAddress(valAccAddr2)
|
||||
|
||||
initialBalance := i(1e9)
|
||||
vestedBalance := i(500e6)
|
||||
|
||||
suite.CreateAccountWithAddress(valAccAddr1, suite.NewBondCoins(initialBalance))
|
||||
suite.CreateVestingAccountWithAddress(delegator, suite.NewBondCoins(initialBalance), suite.NewBondCoins(vestedBalance))
|
||||
|
||||
suite.CreateNewUnbondedValidator(valAddr1, initialBalance)
|
||||
suite.CreateDelegation(valAddr1, delegator, initialBalance)
|
||||
staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
denom string
|
||||
wantIsDenom bool
|
||||
}{
|
||||
{
|
||||
name: "valid derivative denom",
|
||||
denom: suite.Keeper.GetLiquidStakingTokenDenom(valAddr1),
|
||||
wantIsDenom: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - undelegated validator addr",
|
||||
denom: suite.Keeper.GetLiquidStakingTokenDenom(valAddr2),
|
||||
wantIsDenom: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - invalid val addr",
|
||||
denom: "bkava-asdfasdf",
|
||||
wantIsDenom: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - ukava",
|
||||
denom: "ukava",
|
||||
wantIsDenom: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - plain bkava",
|
||||
denom: "bkava",
|
||||
wantIsDenom: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - bkava prefix",
|
||||
denom: "bkava-",
|
||||
wantIsDenom: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
isDenom := suite.Keeper.IsDerivativeDenom(suite.Ctx, tc.denom)
|
||||
|
||||
suite.Require().Equal(tc.wantIsDenom, isDenom)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetKavaForDerivatives() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
valAccAddr1, delegator, valAccAddr2, valAccAddr3 := addrs[0], addrs[1], addrs[2], addrs[3]
|
||||
valAddr1 := sdk.ValAddress(valAccAddr1)
|
||||
|
||||
// Validator addr that has **not** delegated anything
|
||||
valAddr2 := sdk.ValAddress(valAccAddr2)
|
||||
|
||||
valAddr3 := sdk.ValAddress(valAccAddr3)
|
||||
|
||||
initialBalance := i(1e9)
|
||||
vestedBalance := i(500e6)
|
||||
delegateAmount := i(100e6)
|
||||
|
||||
suite.CreateAccountWithAddress(valAccAddr1, suite.NewBondCoins(initialBalance))
|
||||
suite.CreateVestingAccountWithAddress(delegator, suite.NewBondCoins(initialBalance), suite.NewBondCoins(vestedBalance))
|
||||
|
||||
suite.CreateNewUnbondedValidator(valAddr1, initialBalance)
|
||||
suite.CreateDelegation(valAddr1, delegator, delegateAmount)
|
||||
|
||||
suite.CreateAccountWithAddress(valAccAddr3, suite.NewBondCoins(initialBalance))
|
||||
|
||||
suite.CreateNewUnbondedValidator(valAddr3, initialBalance)
|
||||
suite.CreateDelegation(valAddr3, delegator, delegateAmount)
|
||||
staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
|
||||
|
||||
suite.SlashValidator(valAddr3, d("0.05"))
|
||||
|
||||
_, err := suite.Keeper.MintDerivative(suite.Ctx, delegator, valAddr1, suite.NewBondCoin(delegateAmount))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
derivatives sdk.Coins
|
||||
wantKavaAmount sdk.Int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "valid derivative denom",
|
||||
derivatives: sdk.NewCoins(
|
||||
sdk.NewCoin(suite.Keeper.GetLiquidStakingTokenDenom(valAddr1), vestedBalance),
|
||||
),
|
||||
wantKavaAmount: vestedBalance,
|
||||
},
|
||||
{
|
||||
name: "valid - slashed validator",
|
||||
derivatives: sdk.NewCoins(
|
||||
sdk.NewCoin(suite.Keeper.GetLiquidStakingTokenDenom(valAddr3), vestedBalance),
|
||||
),
|
||||
// vestedBalance * 95%
|
||||
wantKavaAmount: vestedBalance.Mul(sdk.NewInt(95)).Quo(sdk.NewInt(100)),
|
||||
},
|
||||
{
|
||||
name: "valid - sum",
|
||||
derivatives: sdk.NewCoins(
|
||||
sdk.NewCoin(suite.Keeper.GetLiquidStakingTokenDenom(valAddr3), vestedBalance),
|
||||
sdk.NewCoin(suite.Keeper.GetLiquidStakingTokenDenom(valAddr1), vestedBalance),
|
||||
),
|
||||
// vestedBalance + (vestedBalance * 95%)
|
||||
wantKavaAmount: vestedBalance.Mul(sdk.NewInt(95)).Quo(sdk.NewInt(100)).Add(vestedBalance),
|
||||
},
|
||||
{
|
||||
name: "invalid - undelegated validator address denom",
|
||||
derivatives: sdk.NewCoins(
|
||||
sdk.NewCoin(suite.Keeper.GetLiquidStakingTokenDenom(valAddr2), vestedBalance),
|
||||
),
|
||||
err: fmt.Errorf("invalid derivative denom %s: validator not found", suite.Keeper.GetLiquidStakingTokenDenom(valAddr2)),
|
||||
},
|
||||
{
|
||||
name: "invalid - denom",
|
||||
derivatives: sdk.NewCoins(
|
||||
sdk.NewCoin("kava", vestedBalance),
|
||||
),
|
||||
err: fmt.Errorf("invalid derivative denom: cannot parse denom kava"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
kavaAmount, err := suite.Keeper.GetKavaForDerivatives(suite.Ctx, tc.derivatives)
|
||||
|
||||
if tc.err != nil {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(tc.wantKavaAmount, kavaAmount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
@ -24,3 +25,22 @@ const (
|
||||
func GetLiquidStakingTokenDenom(bondDenom string, valAddr sdk.ValAddress) string {
|
||||
return fmt.Sprintf("%s%s%s", bondDenom, DenomSeparator, valAddr.String())
|
||||
}
|
||||
|
||||
// ParseLiquidStakingTokenDenom extracts a validator address from a derivative denom.
|
||||
func ParseLiquidStakingTokenDenom(denom string) (sdk.ValAddress, error) {
|
||||
elements := strings.Split(denom, DenomSeparator)
|
||||
if len(elements) != 2 {
|
||||
return nil, fmt.Errorf("cannot parse denom %s", denom)
|
||||
}
|
||||
|
||||
if elements[0] != DefaultDerivativeDenom {
|
||||
return nil, fmt.Errorf("invalid denom prefix, expected %s, got %s", DefaultDerivativeDenom, elements[0])
|
||||
}
|
||||
|
||||
addr, err := sdk.ValAddressFromBech32(elements[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid denom validator address: %w", err)
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
56
x/liquid/types/key_test.go
Normal file
56
x/liquid/types/key_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/liquid/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseLiquidStakingTokenDenom(t *testing.T) {
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
giveDenom string
|
||||
wantAddress sdk.ValAddress
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid denom",
|
||||
giveDenom: "bkava-kavavaloper1ze7y9qwdddejmy7jlw4cymqqlt2wh05y6cpt5a",
|
||||
wantAddress: mustValAddressFromBech32("kavavaloper1ze7y9qwdddejmy7jlw4cymqqlt2wh05y6cpt5a"),
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid prefix",
|
||||
giveDenom: "ukava-kavavaloper1ze7y9qwdddejmy7jlw4cymqqlt2wh05y6cpt5a",
|
||||
wantAddress: mustValAddressFromBech32("kavavaloper1ze7y9qwdddejmy7jlw4cymqqlt2wh05y6cpt5a"),
|
||||
wantErr: fmt.Errorf("invalid denom prefix, expected %s, got %s", types.DefaultDerivativeDenom, "ukava"),
|
||||
},
|
||||
{
|
||||
name: "invalid validator address",
|
||||
giveDenom: "bkava-kavavaloper1ze7y9qw",
|
||||
wantAddress: sdk.ValAddress{},
|
||||
wantErr: fmt.Errorf("invalid denom validator address: decoding bech32 failed: invalid checksum"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
addr, err := types.ParseLiquidStakingTokenDenom(tt.giveDenom)
|
||||
|
||||
if tt.wantErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErr.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantAddress, addr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user