Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 272 additions & 31 deletions tests/interchain/delegator/liquid_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package delegator_test

import (
"encoding/json"
"fmt"
"strconv"
"testing"
"time"

Expand All @@ -11,7 +13,8 @@ import (
"github.com/strangelove-ventures/interchaintest/v8/ibc"
"github.com/strangelove-ventures/interchaintest/v8/testutil"
"github.com/stretchr/testify/suite"
"golang.org/x/mod/semver"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"golang.org/x/sync/errgroup"
)

Expand All @@ -25,12 +28,19 @@ const (

type LSMSuite struct {
*chainsuite.Suite
Stride *chainsuite.Chain
ICAAddr string
LinkedChain *chainsuite.Chain
LSMWallets map[string]ibc.Wallet
ShareFactor sdkmath.Int
}

type ProposalJSON struct {
Messages []json.RawMessage `json:"messages"`
InitialDeposit string `json:"deposit"`
Title string `json:"title"`
Summary string `json:"summary"`
Metadata string `json:"metadata"`
}

func (s *LSMSuite) checkAMinusBEqualsX(a, b string, x sdkmath.Int) {
intA, err := chainsuite.StrToSDKInt(a)
s.Require().NoError(err)
Expand All @@ -48,8 +58,7 @@ func (s *LSMSuite) TestLSMHappyPath() {
liquid1Redeem = 20000000
)
providerWallet := s.Chain.ValidatorWallets[0]

strideWallet := s.Stride.ValidatorWallets[0]
ibcWallet := s.LinkedChain.ValidatorWallets[0]

var tokenizedDenom string
s.Run("Tokenize", func() {
Expand Down Expand Up @@ -121,9 +130,9 @@ func (s *LSMSuite) TestLSMHappyPath() {
var happyLiquid1Delegations1 string
var ibcDenom string

ibcChannelProvider, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, s.Stride)
ibcChannelProvider, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Chain, s.LinkedChain)
s.Require().NoError(err)
ibcChannelStride, err := s.Relayer.GetTransferChannel(s.GetContext(), s.Stride, s.Chain)
ibcChannel, err := s.Relayer.GetTransferChannel(s.GetContext(), s.LinkedChain, s.Chain)
s.Require().NoError(err)

s.Run("Transfer Tokens", func() {
Expand All @@ -141,11 +150,11 @@ func (s *LSMSuite) TestLSMHappyPath() {
_, err = s.Chain.SendIBCTransfer(s.GetContext(), ibcChannelProvider.ChannelID, s.LSMWallets[lsmLiquid1Moniker].FormattedAddress(), ibc.WalletAmount{
Amount: sdkmath.NewInt(ibcTransfer),
Denom: tokenizedDenom,
Address: strideWallet.Address,
Address: ibcWallet.Address,
}, ibc.TransferOptions{})
s.Require().NoError(err)
s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 5, s.Stride))
balances, err := s.Stride.BankQueryAllBalances(s.GetContext(), strideWallet.Address)
s.Require().NoError(testutil.WaitForBlocks(s.GetContext(), 5, s.LinkedChain))
balances, err := s.LinkedChain.BankQueryAllBalances(s.GetContext(), ibcWallet.Address)
s.Require().NoError(err)
for _, balance := range balances {
if balance.Amount.Int64() == ibcTransfer {
Expand All @@ -167,7 +176,7 @@ func (s *LSMSuite) TestLSMHappyPath() {
"--gas", "auto")
s.Require().NoError(err)

_, err = s.Stride.SendIBCTransfer(s.GetContext(), ibcChannelStride.ChannelID, strideWallet.Address, ibc.WalletAmount{
_, err = s.LinkedChain.SendIBCTransfer(s.GetContext(), ibcChannel.ChannelID, ibcWallet.Address, ibc.WalletAmount{
Amount: sdkmath.NewInt(ibcTransfer),
Denom: ibcDenom,
Address: s.LSMWallets[lsmLiquid3Moniker].FormattedAddress(),
Expand Down Expand Up @@ -272,6 +281,251 @@ func (s *LSMSuite) TestTokenizeVested() {
s.checkAMinusBEqualsX(sharesPostTokenize, sharesPreTokenize, sdkmath.NewInt(tokenizeAmount).Mul(s.ShareFactor))
}

func (s *LSMSuite) TestLSMParams() {
const (
delegation = 100000000
globalCap = 0.1
validatorCap = 0.05
)

providerWallet := s.Chain.ValidatorWallets[0]

liquidParams, err := s.Chain.QueryJSON(s.GetContext(), "params", "liquid", "params")
s.Require().NoError(err)

startingValidatorCap := gjson.Get(liquidParams.String(), "validator_liquid_staking_cap").String()
startingGlobalCap := gjson.Get(liquidParams.String(), "global_liquid_staking_cap").String()
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Starting validator liquid staking cap: %s", startingValidatorCap)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Starting global liquid staking cap: %s", startingGlobalCap)

s.Run("Update params", func() {

authority, err := s.Chain.GetGovernanceAddress(s.GetContext())
s.Require().NoError(err)

updatedParams, err := sjson.Set(liquidParams.String(), "global_liquid_staking_cap", fmt.Sprintf("%f", globalCap))
s.Require().NoError(err)
updatedParams, err = sjson.Set(updatedParams, "validator_liquid_staking_cap", fmt.Sprintf("%f", validatorCap))
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Updated params: %s", updatedParams)

paramChangeMessage := fmt.Sprintf(`{
"@type": "/gaia.liquid.v1beta1.MsgUpdateParams",
"authority": "%s",
"params": %s
}`, authority, updatedParams)

proposal := ProposalJSON{
Messages: []json.RawMessage{json.RawMessage(paramChangeMessage)},
InitialDeposit: "5000000uatom",
Title: "Liquid Param Change Proposal",
Summary: "Test Proposal",
Metadata: "ipfs://CID",
}

// Marshal to JSON
proposalBytes, err := json.MarshalIndent(proposal, "", " ")
s.Require().NoError(err)

// Get the home directory of the node
homeDir := s.Chain.GetNode().HomeDir()
proposalPath := homeDir + "/params-proposal.json"
// Write to file
err = s.Chain.GetNode().WriteFile(s.GetContext(), proposalBytes, "params-proposal.json")
s.Require().NoError(err)

// out, _, _ := s.Chain.GetNode().Exec(s.GetContext(), []string{"ls", "-l", string(homeDir)}, []string{homeDir})
// fmt.Println("Files in home dir:", string(out))
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), providerWallet.Address,
"gov", "submit-proposal", proposalPath)
s.Require().NoError(err)

lastProposalId, err := s.Chain.QueryJSON(s.GetContext(), "proposals.@reverse.0.id", "gov", "proposals")
s.Require().NoError(err)

// Pass proposal
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), providerWallet.Address,
"gov", "vote", lastProposalId.String(), "yes")
s.Require().NoError(err)
time.Sleep(80 * time.Second)

// Check proposal
proposalStatus, err := s.Chain.QueryJSON(s.GetContext(), "proposal", "gov", "proposal", lastProposalId.String())
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Proposal status: %s", proposalStatus)
// Check params
liquidParams, err = s.Chain.QueryJSON(s.GetContext(), "params", "liquid", "params")
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Current params: %s", liquidParams)
validatorLiquidCapParam := gjson.Get(liquidParams.String(), "validator_liquid_staking_cap")
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Validator liquid cap: %s", validatorLiquidCapParam)
globalLiquidCapParam := gjson.Get(liquidParams.String(), "global_liquid_staking_cap")
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Global liquid cap: %s", globalLiquidCapParam)
validatorLiquidCapFloat := validatorLiquidCapParam.Float()
globalLiquidCapFloat := globalLiquidCapParam.Float()
s.Require().Equal(validatorCap, validatorLiquidCapFloat)
s.Require().Equal(globalCap, globalLiquidCapFloat)

})

s.Run("Test liquid caps", func() {
_, err := s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "staking", "delegate", s.Chain.ValidatorWallets[1].ValoperAddress, fmt.Sprintf("%d%s", delegation, chainsuite.Uatom))
s.Require().NoError(err)
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "staking", "delegate", s.Chain.ValidatorWallets[2].ValoperAddress, fmt.Sprintf("%d%s", delegation, chainsuite.Uatom))
s.Require().NoError(err)

liquidParams, err := s.Chain.QueryJSON(s.GetContext(), "params", "liquid", "params")
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Starting params: %s", liquidParams)
validatorLiquidCapParam := gjson.Get(liquidParams.String(), "validator_liquid_staking_cap")
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Validator liquid cap: %s", validatorLiquidCapParam)
globalLiquidCapParam := gjson.Get(liquidParams.String(), "global_liquid_staking_cap")
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Global liquid cap: %s", globalLiquidCapParam)

val0Tokens, err := s.Chain.QueryJSON(s.GetContext(), "validator.tokens", "staking", "validator", s.Chain.ValidatorWallets[0].ValoperAddress)
s.Require().NoError(err)
val1Tokens, err := s.Chain.QueryJSON(s.GetContext(), "validator.tokens", "staking", "validator", s.Chain.ValidatorWallets[1].ValoperAddress)
s.Require().NoError(err)
val2Tokens, err := s.Chain.QueryJSON(s.GetContext(), "validator.tokens", "staking", "validator", s.Chain.ValidatorWallets[2].ValoperAddress)
s.Require().NoError(err)
totalBondedTokens, err := s.Chain.QueryJSON(s.GetContext(), "pool.bonded_tokens", "staking", "pool")
s.Require().NoError(err)
totalLiquidStaked, err := s.Chain.QueryJSON(s.GetContext(), "tokens", "liquid", "total-liquid-staked")
s.Require().NoError(err)
val0TokensFloat := val0Tokens.Float()
val1TokensFloat := val1Tokens.Float()
val2TokensFloat := val2Tokens.Float()
totalBondedTokensFloat := totalBondedTokens.Float()
totalLiquidStakedFloat := totalLiquidStaked.Float()

validatorCap := validatorLiquidCapParam.Float()
globalCap := globalLiquidCapParam.Float()

val0Cap := validatorCap * val0TokensFloat
val1Cap := validatorCap * val1TokensFloat
val2Cap := validatorCap * val2TokensFloat
globalCapShares := globalCap * totalBondedTokensFloat

val0LiquidShares, err := s.Chain.QueryJSON(s.GetContext(), "liquid_validator.liquid_shares", "liquid", "liquid-validator", s.Chain.ValidatorWallets[0].ValoperAddress)
s.Require().NoError(err)
val1LiquidShares, err := s.Chain.QueryJSON(s.GetContext(), "liquid_validator.liquid_shares", "liquid", "liquid-validator", s.Chain.ValidatorWallets[1].ValoperAddress)
s.Require().NoError(err)
val2LiquidShares, err := s.Chain.QueryJSON(s.GetContext(), "liquid_validator.liquid_shares", "liquid", "liquid-validator", s.Chain.ValidatorWallets[2].ValoperAddress)
s.Require().NoError(err)
val0LiquidSharesFloat := val0LiquidShares.Float()
val1LiquidSharesFloat := val1LiquidShares.Float()
val2LiquidSharesFloat := val2LiquidShares.Float()

val0CapAvailable := val0Cap - val0LiquidSharesFloat
val1CapAvailable := val1Cap - val1LiquidSharesFloat
val2CapAvailable := val2Cap - val2LiquidSharesFloat
globalCapSharesAvailable := globalCapShares - totalLiquidStakedFloat

chainsuite.GetLogger(s.GetContext()).Sugar().Infof("val0 cap: %f, available shares: %f", val0Cap, val0CapAvailable)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("val1 cap: %f, available shares: %f", val1Cap, val1CapAvailable)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Val2 cap: %f, available shares: %f", val2Cap, val2CapAvailable)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Global cap: %f, available shares: %f", globalCapShares, globalCapSharesAvailable)

// Validator 1 must have a lower cap than the global amount
s.Require().Less(val1CapAvailable, globalCapSharesAvailable)

// Try to tokenize more than the global cap
globalFailAmount := int(globalCapSharesAvailable + 1000000)
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "liquid", "tokenize-share",
s.Chain.ValidatorWallets[1].ValoperAddress, fmt.Sprintf("%d%s", globalFailAmount, s.Chain.Config().Denom), s.Chain.ValidatorWallets[0].Address)
s.Require().Error(err)

// Try to tokenize more than the validator cap
validatorFailAmount := int(val1CapAvailable + 1000000)
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "liquid", "tokenize-share",
s.Chain.ValidatorWallets[1].ValoperAddress, fmt.Sprintf("%d%s", validatorFailAmount, s.Chain.Config().Denom), s.Chain.ValidatorWallets[0].Address)
s.Require().Error(err)

// Tokenize less than the validator cap
validatorSuccessAmount := int(val1CapAvailable - 1000000)
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "liquid", "tokenize-share",
s.Chain.ValidatorWallets[1].ValoperAddress, fmt.Sprintf("%d%s", validatorSuccessAmount, s.Chain.Config().Denom), s.Chain.ValidatorWallets[0].Address)
s.Require().NoError(err)

tokenizedDenom, err := s.Chain.QueryJSON(s.GetContext(), "balances.@reverse.1.denom", "bank", "balances", s.Chain.ValidatorWallets[0].Address)
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Tokenized denom: %s", tokenizedDenom)
// Redeem tokenized amount
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "liquid", "redeem-tokens",
fmt.Sprintf("%d%s", validatorSuccessAmount, tokenizedDenom))
s.Require().NoError(err)

// Unbond
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "staking", "unbond", s.Chain.ValidatorWallets[1].ValoperAddress, fmt.Sprintf("%d%s", delegation, chainsuite.Uatom))
s.Require().NoError(err)
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), s.Chain.ValidatorWallets[0].Address, "staking", "unbond", s.Chain.ValidatorWallets[2].ValoperAddress, fmt.Sprintf("%d%s", delegation, chainsuite.Uatom))
s.Require().NoError(err)

})

s.Run("Restore params", func() {
authority, err := s.Chain.GetGovernanceAddress(s.GetContext())
s.Require().NoError(err)

updatedParams, err := sjson.Set(liquidParams.String(), "global_liquid_staking_cap", startingGlobalCap)
s.Require().NoError(err)
updatedParams, err = sjson.Set(updatedParams, "validator_liquid_staking_cap", startingValidatorCap)
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Updated params: %s", updatedParams)

paramChangeMessage := fmt.Sprintf(`{
"@type": "/gaia.liquid.v1beta1.MsgUpdateParams",
"authority": "%s",
"params": %s
}`, authority, updatedParams)

proposal := ProposalJSON{
Messages: []json.RawMessage{json.RawMessage(paramChangeMessage)},
InitialDeposit: "5000000uatom",
Title: "Liquid Param Restore Proposal",
Summary: "Test Proposal",
Metadata: "ipfs://CID",
}

// Marshal to JSON
proposalBytes, err := json.MarshalIndent(proposal, "", " ")
s.Require().NoError(err)

// Get the home directory of the node
homeDir := s.Chain.GetNode().HomeDir()
proposalPath := homeDir + "/restore-params-proposal.json"
// Write to file
err = s.Chain.GetNode().WriteFile(s.GetContext(), proposalBytes, "restore-params-proposal.json")
s.Require().NoError(err)

_, err = s.Chain.GetNode().ExecTx(s.GetContext(), providerWallet.Address,
"gov", "submit-proposal", proposalPath)
s.Require().NoError(err)

lastProposalId, err := s.Chain.QueryJSON(s.GetContext(), "proposals.@reverse.0.id", "gov", "proposals")
s.Require().NoError(err)
chainsuite.GetLogger(s.GetContext()).Sugar().Infof("Last Proposal ID: %s", lastProposalId)

// Pass proposal
_, err = s.Chain.GetNode().ExecTx(s.GetContext(), providerWallet.Address,
"gov", "vote", lastProposalId.String(), "yes")
s.Require().NoError(err)
time.Sleep(80 * time.Second)

// Check params
liquidParams, err = s.Chain.QueryJSON(s.GetContext(), "params", "liquid", "params")
s.Require().NoError(err)
validatorLiquidCapParam := gjson.Get(liquidParams.String(), "validator_liquid_staking_cap").Float()
globalLiquidCapParam := gjson.Get(liquidParams.String(), "global_liquid_staking_cap").Float()
startingValidatorCapFloat, err := strconv.ParseFloat(startingValidatorCap, 64)
s.Require().NoError(err)
startingGlobalCapFloat, err := strconv.ParseFloat(startingGlobalCap, 64)
s.Require().NoError(err)
s.Require().Equal(startingValidatorCapFloat, validatorLiquidCapParam)
s.Require().Equal(startingGlobalCapFloat, globalLiquidCapParam)
})
}

func (s *LSMSuite) setupLSMWallets() {
names := []string{lsmBondingMoniker, lsmLiquid1Moniker, lsmLiquid2Moniker, lsmLiquid3Moniker, lsmOwnerMoniker}
wallets := make(map[string]ibc.Wallet)
Expand Down Expand Up @@ -299,38 +553,25 @@ func (s *LSMSuite) setupLSMWallets() {

func (s *LSMSuite) SetupSuite() {
s.Suite.SetupSuite()
// This is slightly broken while stride is still in the process of being upgraded, so skip if
// going from v21 -> v21
if semver.Major(s.Env.OldGaiaImageVersion) == s.Env.UpgradeName && s.Env.UpgradeName == "v21" {
s.T().Skip("Skipping LSM when going from v21 -> v21")
}
stride, err := s.Chain.AddConsumerChain(s.GetContext(), s.Relayer, chainsuite.ConsumerConfig{
ChainName: "stride",
Version: chainsuite.StrideVersion,
Denom: chainsuite.StrideDenom,
TopN: 100,
ShouldCopyProviderKey: []bool{true},
})
s.Require().NoError(err)
s.Stride = stride
err = s.Chain.CheckCCV(s.GetContext(), stride, s.Relayer, 1_000_000, 0, 1)
secondChain, err := s.Chain.AddLinkedChain(s.GetContext(), s.T(), s.Relayer, chainsuite.DefaultChainSpec(s.Env))
s.Require().NoError(err)
s.LinkedChain = secondChain

icaAddr, err := stride.SetupICAAccount(s.GetContext(), s.Chain, s.Relayer, stride.ValidatorWallets[0].Address, 0, 1_000_000_000)
s.Require().NoError(err)
s.ICAAddr = icaAddr
shareFactor, ok := sdkmath.NewIntFromString("1000000000000000000")
s.Require().True(ok)
s.ShareFactor = shareFactor

s.setupLSMWallets()
s.UpgradeChain()
}

func TestLSM(t *testing.T) {
s := &LSMSuite{
Suite: chainsuite.NewSuite(chainsuite.SuiteConfig{
CreateRelayer: true,
CreateRelayer: true,
UpgradeOnSetup: true,
ChainSpec: &interchaintest.ChainSpec{
NumValidators: &chainsuite.SixValidators,
},
}),
}
suite.Run(t, s)
Expand Down
Loading