Reset bep3 swaps for zero height (#1228)

* copy over swap height reset from v016 upgrade

* remove unneeded old bep3 migrations

* hook migration cmd into app

* add bep3 migration integration test

* update old docs
This commit is contained in:
Ruaridh 2022-04-27 16:27:10 +01:00 committed by GitHub
parent a33a7b1166
commit fefcb48a80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 310 additions and 652 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/app/params"
kavaclient "github.com/kava-labs/kava/client"
"github.com/kava-labs/kava/migrate"
)
// NewRootCmd creates a new root command for the kava blockchain.
@ -79,8 +80,8 @@ func addSubCmds(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, de
genutilcli.InitCmd(app.ModuleBasics, defaultNodeHome),
),
genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, defaultNodeHome),
// migrate.MigrateGenesisCmd(),
// migrate.AssertInvariantsCmd(encodingConfig),
migrate.MigrateGenesisCmd(),
migrate.AssertInvariantsCmd(encodingConfig),
genutilcli.GenTxCmd(app.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, defaultNodeHome),
genutilcli.ValidateGenesisCmd(app.ModuleBasics),
AddGenesisAccountCmd(defaultNodeHome),

View File

@ -96,9 +96,8 @@ func AssertInvariantsCmd(config params.EncodingConfig) *cobra.Command {
func validateGenDoc(importGenesisFile string) (*tmtypes.GenesisDoc, error) {
genDoc, err := tmtypes.GenesisDocFromFile(importGenesisFile)
if err != nil {
return nil, fmt.Errorf("%s. Make sure that"+
" you have correctly migrated all Tendermint consensus params, please see the"+
" chain migration guide at https://docs.cosmos.network/master/migrations/chain-upgrade-guide-040.html for more info",
return nil, fmt.Errorf(
"%s. Make sure that you have correctly migrated all Tendermint consensus params.",
err.Error(),
)
}

View File

@ -6,10 +6,10 @@ migrated to the new structure of the newer versions.
There are two types of migration:
- **genesis migration** a script converts an exported genesis file from the old software to a new genesis file for the new software
- **live upgrade** a handler in the upgrade module converts data in the database itself from the old version to the new
- **in-place upgrade** a handler in the upgrade module converts data in the database itself from the old version to the new
Genesis migration starts a whole new blockchain (with new chain-id) for the new software version.
Live upgrade keeps the blockchain (and chain-id) the same for the new software version.
In-Place upgrade keeps the blockchain (and chain-id) the same for the new software version.
We only support migrations between mainnet kava releases.
We only support migrations from the previous mainnet kava version to the current. We don't support migrating between two old versions, use the old software version for this.
@ -25,16 +25,5 @@ On each release we can delete the previous releases migration and old GenesisSta
eg kava-3 migrates `auth.GenesisState` from kava-2 to `auth.GenesisState` from kava-3,
but for kava-4 we don't need to keep around kava-2's `auth.GenesisState` type.
This folder contains old types from several sdk modules because they needed custom migrations for kava-3.
The sdk version for kava-2 was a master commit 18de63.
Live Upgrade
The process is:
- submit upgrade proposal on old chain
- old chain halts
- people download new version and restart their validators
- on start the new upgrade handler runs
- use copypasted old keeper and types to read from db, convert to current types and write with current keeper
*/
package migrate

View File

@ -17,6 +17,8 @@ import (
v016auction "github.com/kava-labs/kava/x/auction/legacy/v0_16"
v017auction "github.com/kava-labs/kava/x/auction/legacy/v0_17"
auctiontypes "github.com/kava-labs/kava/x/auction/types"
v017bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_17"
bep3types "github.com/kava-labs/kava/x/bep3/types"
incentivetypes "github.com/kava-labs/kava/x/incentive/types"
savingstypes "github.com/kava-labs/kava/x/savings/types"
)
@ -88,4 +90,14 @@ func migrateAppState(appState genutiltypes.AppMap, clientCtx client.Context) {
// x/savings
savingsState := savingstypes.DefaultGenesisState()
appState[savingstypes.ModuleName] = codec.MustMarshalJSON(&savingsState)
// x/bep3
if appState[bep3types.ModuleName] != nil {
var v16GenState bep3types.GenesisState
codec.MustUnmarshalJSON(appState[bep3types.ModuleName], &v16GenState)
migratedState := v017bep3.Migrate(v16GenState)
appState[bep3types.ModuleName] = codec.MustMarshalJSON(migratedState)
}
}

View File

@ -1145,7 +1145,7 @@
"denom": "btcb"
}
],
"closed_block": "1",
"closed_block": "838115",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "838627",
@ -1164,7 +1164,7 @@
"denom": "bnb"
}
],
"closed_block": "1",
"closed_block": "1712118",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "1736797",
@ -1186,7 +1186,7 @@
"closed_block": "787122",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "1",
"expire_height": "811799",
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
"recipient": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
"recipient_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
@ -1205,7 +1205,7 @@
"closed_block": "787122",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "1",
"expire_height": "811799",
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
"recipient": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
"recipient_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
@ -1224,7 +1224,7 @@
"closed_block": "0",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "24687",
"expire_height": "1730589",
"random_number_hash": "A74EA1AB58D312FDF1E872D18583CACCF294E639DDA4F303939E9ADCEC081D93",
"recipient": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
"recipient_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
@ -1243,13 +1243,13 @@
"closed_block": "0",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "1",
"expire_height": "1740000",
"random_number_hash": "39E9ADCEC081D93A74EA1A83CACCF294E639DDA4F3039B58D312FDF1E872D185",
"recipient": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
"sender_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
"status": "SWAP_STATUS_EXPIRED",
"status": "SWAP_STATUS_OPEN",
"timestamp": "1641934114"
}
],

View File

@ -465,7 +465,12 @@
},
"atomic_swaps": [
{
"amount": [{ "amount": "1999955998", "denom": "btcb" }],
"amount": [
{
"amount": "1999955998",
"denom": "btcb"
}
],
"closed_block": "1",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",
@ -479,7 +484,12 @@
"timestamp": "1636034914"
},
{
"amount": [{ "amount": "19000000000", "denom": "bnb" }],
"amount": [
{
"amount": "19000000000",
"denom": "bnb"
}
],
"closed_block": "1",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
@ -493,7 +503,12 @@
"timestamp": "1641976566"
},
{
"amount": [{ "amount": "999595462080", "denom": "busd" }],
"amount": [
{
"amount": "999595462080",
"denom": "busd"
}
],
"closed_block": "787122",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",
@ -507,7 +522,12 @@
"timestamp": "1635694492"
},
{
"amount": [{ "amount": "999595462080", "denom": "busd" }],
"amount": [
{
"amount": "999595462080",
"denom": "busd"
}
],
"closed_block": "787122",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
@ -521,7 +541,12 @@
"timestamp": "1635694492"
},
{
"amount": [{ "amount": "1000000", "denom": "btcb" }],
"amount": [
{
"amount": "1000000",
"denom": "btcb"
}
],
"closed_block": "0",
"cross_chain": true,
"direction": "SWAP_DIRECTION_OUTGOING",
@ -535,7 +560,12 @@
"timestamp": "1641934114"
},
{
"amount": [{ "amount": "1000000", "denom": "btcb" }],
"amount": [
{
"amount": "1000000",
"denom": "btcb"
}
],
"closed_block": "0",
"cross_chain": true,
"direction": "SWAP_DIRECTION_INCOMING",

View File

@ -1,187 +0,0 @@
package v0_15
import (
"encoding/json"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
)
const (
// ModuleName is the name of the module
ModuleName = "bep3"
)
// GenesisState - all bep3 state that must be provided at genesis
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
AtomicSwaps AtomicSwaps `json:"atomic_swaps" yaml:"atomic_swaps"`
Supplies AssetSupplies `json:"supplies" yaml:"supplies"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
}
// Params governance parameters for bep3 module
type Params struct {
AssetParams AssetParams `json:"asset_params" yaml:"asset_params"`
}
// AssetParams array of AssetParam
type AssetParams []AssetParam
// AssetParam parameters that must be specified for each bep3 asset
type AssetParam struct {
Denom string `json:"denom" yaml:"denom"` // name of the asset
CoinID int `json:"coin_id" yaml:"coin_id"` // SLIP-0044 registered coin type - see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
SupplyLimit SupplyLimit `json:"supply_limit" yaml:"supply_limit"` // asset supply limit
Active bool `json:"active" yaml:"active"` // denotes if asset is available or paused
DeputyAddress sdk.AccAddress `json:"deputy_address" yaml:"deputy_address"` // the address of the relayer process
FixedFee sdk.Int `json:"fixed_fee" yaml:"fixed_fee"` // the fixed fee charged by the relayer process for outgoing swaps
MinSwapAmount sdk.Int `json:"min_swap_amount" yaml:"min_swap_amount"` // Minimum swap amount
MaxSwapAmount sdk.Int `json:"max_swap_amount" yaml:"max_swap_amount"` // Maximum swap amount
MinBlockLock uint64 `json:"min_block_lock" yaml:"min_block_lock"` // Minimum swap block lock
MaxBlockLock uint64 `json:"max_block_lock" yaml:"max_block_lock"` // Maximum swap block lock
}
// SupplyLimit parameters that control the absolute and time-based limits for an assets's supply
type SupplyLimit struct {
Limit sdk.Int `json:"limit" yaml:"limit"` // the absolute supply limit for an asset
TimeLimited bool `json:"time_limited" yaml:"time_limited"` // boolean for if the supply is also limited by time
TimePeriod time.Duration `json:"time_period" yaml:"time_period"` // the duration for which the supply time limit applies
TimeBasedLimit sdk.Int `json:"time_based_limit" yaml:"time_based_limit"` // the supply limit for an asset for each time period
}
// AtomicSwaps is a slice of AtomicSwap
type AtomicSwaps []AtomicSwap
// AtomicSwap contains the information for an atomic swap
type AtomicSwap struct {
Amount sdk.Coins `json:"amount" yaml:"amount"`
RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
ExpireHeight uint64 `json:"expire_height" yaml:"expire_height"`
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
ClosedBlock int64 `json:"closed_block" yaml:"closed_block"`
Status SwapStatus `json:"status" yaml:"status"`
CrossChain bool `json:"cross_chain" yaml:"cross_chain"`
Direction SwapDirection `json:"direction" yaml:"direction"`
}
// SwapStatus is the status of an AtomicSwap
type SwapStatus byte
// swap statuses
const (
NULL SwapStatus = 0x00
Open SwapStatus = 0x01
Completed SwapStatus = 0x02
Expired SwapStatus = 0x03
)
// String returns the string representation of a SwapStatus
func (status SwapStatus) String() string {
switch status {
case Open:
return "Open"
case Completed:
return "Completed"
case Expired:
return "Expired"
default:
return "NULL"
}
}
// NewSwapStatusFromString converts string to SwapStatus type
func NewSwapStatusFromString(str string) SwapStatus {
switch str {
case "Open", "open":
return Open
case "Completed", "completed":
return Completed
case "Expired", "expired":
return Expired
default:
return NULL
}
}
// MarshalJSON marshals the SwapStatus
func (status SwapStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// UnmarshalJSON unmarshals the SwapStatus
func (status *SwapStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
*status = NewSwapStatusFromString(s)
return nil
}
// SwapDirection is the direction of an AtomicSwap
type SwapDirection byte
const (
INVALID SwapDirection = 0x00
Incoming SwapDirection = 0x01
Outgoing SwapDirection = 0x02
)
// String returns the string representation of a SwapDirection
func (direction SwapDirection) String() string {
switch direction {
case Incoming:
return "Incoming"
case Outgoing:
return "Outgoing"
default:
return "INVALID"
}
}
// NewSwapDirectionFromString converts string to SwapDirection type
func NewSwapDirectionFromString(str string) SwapDirection {
switch str {
case "Incoming", "incoming", "inc", "I", "i":
return Incoming
case "Outgoing", "outgoing", "out", "O", "o":
return Outgoing
default:
return INVALID
}
}
// MarshalJSON marshals the SwapDirection
func (direction SwapDirection) MarshalJSON() ([]byte, error) {
return json.Marshal(direction.String())
}
// UnmarshalJSON unmarshals the SwapDirection
func (direction *SwapDirection) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
*direction = NewSwapDirectionFromString(s)
return nil
}
// AssetSupplies is a slice of AssetSupply
type AssetSupplies []AssetSupply
// AssetSupply contains information about an asset's supply
type AssetSupply struct {
IncomingSupply sdk.Coin `json:"incoming_supply" yaml:"incoming_supply"`
OutgoingSupply sdk.Coin `json:"outgoing_supply" yaml:"outgoing_supply"`
CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"`
TimeLimitedCurrentSupply sdk.Coin `json:"time_limited_current_supply" yaml:"time_limited_current_supply"`
TimeElapsed time.Duration `json:"time_elapsed" yaml:"time_elapsed"`
}

View File

@ -1,142 +0,0 @@
package v0_16
import (
"fmt"
v015bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_15"
v016bep3 "github.com/kava-labs/kava/x/bep3/types"
)
func migrateAssetParam(param v015bep3.AssetParam) v016bep3.AssetParam {
return v016bep3.AssetParam{
Denom: param.Denom,
CoinID: int64(param.CoinID),
SupplyLimit: v016bep3.SupplyLimit{
Limit: param.SupplyLimit.Limit,
TimeLimited: param.SupplyLimit.TimeLimited,
TimePeriod: param.SupplyLimit.TimePeriod,
TimeBasedLimit: param.SupplyLimit.TimeBasedLimit,
},
Active: param.Active,
DeputyAddress: param.DeputyAddress,
FixedFee: param.FixedFee,
MinSwapAmount: param.MinSwapAmount,
MaxSwapAmount: param.MaxSwapAmount,
MinBlockLock: param.MinBlockLock,
MaxBlockLock: param.MaxBlockLock,
}
}
func migrateParams(v015params v015bep3.Params) v016bep3.Params {
assetParams := make(v016bep3.AssetParams, len(v015params.AssetParams))
for i, assetParam := range v015params.AssetParams {
assetParams[i] = migrateAssetParam(assetParam)
}
return v016bep3.Params{AssetParams: assetParams}
}
func migrateSwapStatus(status v015bep3.SwapStatus) v016bep3.SwapStatus {
switch status {
case v015bep3.NULL:
return v016bep3.SWAP_STATUS_UNSPECIFIED
case v015bep3.Open:
return v016bep3.SWAP_STATUS_OPEN
case v015bep3.Completed:
return v016bep3.SWAP_STATUS_COMPLETED
case v015bep3.Expired:
return v016bep3.SWAP_STATUS_EXPIRED
default:
panic(fmt.Errorf("'%d' is not a valid swap status", status))
}
}
func migrateSwapDirection(direction v015bep3.SwapDirection) v016bep3.SwapDirection {
switch direction {
case v015bep3.INVALID:
return v016bep3.SWAP_DIRECTION_UNSPECIFIED
case v015bep3.Incoming:
return v016bep3.SWAP_DIRECTION_INCOMING
case v015bep3.Outgoing:
return v016bep3.SWAP_DIRECTION_OUTGOING
default:
panic(fmt.Errorf("'%d' is not a valid swap direction", direction))
}
}
func migrateAtomicSwaps(oldSwaps v015bep3.AtomicSwaps) v016bep3.AtomicSwaps {
newSwaps := make(v016bep3.AtomicSwaps, len(oldSwaps))
for i, oldSwap := range oldSwaps {
swap := v016bep3.AtomicSwap{
Amount: oldSwap.Amount,
RandomNumberHash: oldSwap.RandomNumberHash,
ExpireHeight: oldSwap.ExpireHeight,
Timestamp: oldSwap.Timestamp,
Sender: oldSwap.Sender,
Recipient: oldSwap.Recipient,
SenderOtherChain: oldSwap.SenderOtherChain,
RecipientOtherChain: oldSwap.RecipientOtherChain,
ClosedBlock: oldSwap.ClosedBlock,
Status: migrateSwapStatus(oldSwap.Status),
CrossChain: oldSwap.CrossChain,
Direction: migrateSwapDirection(oldSwap.Direction),
}
swap = resetSwapForZeroHeight(swap)
newSwaps[i] = swap
}
return newSwaps
}
// resetSwapForZeroHeight updates swap expiry/close heights to work when the chain height is reset to zero.
func resetSwapForZeroHeight(swap v016bep3.AtomicSwap) v016bep3.AtomicSwap {
switch status := swap.Status; status {
case v016bep3.SWAP_STATUS_COMPLETED:
// Reset closed block to one so completed swaps are not held in long term storage too long.
swap.ClosedBlock = 1
case v016bep3.SWAP_STATUS_OPEN:
switch dir := swap.Direction; dir {
case v016bep3.SWAP_DIRECTION_INCOMING:
// Open incoming swaps can be expired safely. They haven't been claimed yet, so the outgoing swap on bnb will just timeout.
// The chain downtime cannot be accurately predicted, so it's easier to expire than to recalculate a correct expire height.
swap.ExpireHeight = 1
swap.Status = v016bep3.SWAP_STATUS_EXPIRED
case v016bep3.SWAP_DIRECTION_OUTGOING:
// Open outgoing swaps should be extended to allow enough time to claim after the chain launches.
// They cannot be expired as there could be an open/claimed bnb swap.
swap.ExpireHeight = 1 + 24686 // default timeout used when sending swaps from kava
case v016bep3.SWAP_DIRECTION_UNSPECIFIED:
default:
panic(fmt.Sprintf("unknown bep3 swap direction '%s'", dir))
}
case v016bep3.SWAP_STATUS_EXPIRED:
// Once a swap is marked expired the expire height is ignored. However reset to 1 to be sure.
swap.ExpireHeight = 1
case v016bep3.SWAP_STATUS_UNSPECIFIED:
default:
panic(fmt.Sprintf("unknown bep3 swap status '%s'", status))
}
return swap
}
func migrateSupplies(oldSupplies v015bep3.AssetSupplies) v016bep3.AssetSupplies {
newSupplies := make(v016bep3.AssetSupplies, len(oldSupplies))
for i, supply := range oldSupplies {
newSupplies[i] = v016bep3.AssetSupply{
IncomingSupply: supply.IncomingSupply,
OutgoingSupply: supply.OutgoingSupply,
CurrentSupply: supply.CurrentSupply,
TimeLimitedCurrentSupply: supply.TimeLimitedCurrentSupply,
TimeElapsed: supply.TimeElapsed,
}
}
return newSupplies
}
func Migrate(oldState v015bep3.GenesisState) *v016bep3.GenesisState {
return &v016bep3.GenesisState{
PreviousBlockTime: oldState.PreviousBlockTime,
Params: migrateParams(oldState.Params),
AtomicSwaps: migrateAtomicSwaps(oldState.AtomicSwaps),
Supplies: migrateSupplies(oldState.Supplies),
}
}

View File

@ -1,275 +0,0 @@
package v0_16
import (
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/bytes"
app "github.com/kava-labs/kava/app"
v015bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_15"
v016bep3 "github.com/kava-labs/kava/x/bep3/types"
)
type migrateTestSuite struct {
suite.Suite
addresses []sdk.AccAddress
v15genstate v015bep3.GenesisState
cdc codec.Codec
legacyCdc *codec.LegacyAmino
}
func (s *migrateTestSuite) SetupTest() {
app.SetSDKConfig()
s.v15genstate = v015bep3.GenesisState{
PreviousBlockTime: time.Date(2021, 4, 8, 15, 0, 0, 0, time.UTC),
Params: v015bep3.Params{},
Supplies: v015bep3.AssetSupplies{},
AtomicSwaps: v015bep3.AtomicSwaps{},
}
config := app.MakeEncodingConfig()
s.cdc = config.Marshaler
legacyCodec := codec.NewLegacyAmino()
s.legacyCdc = legacyCodec
_, accAddresses := app.GeneratePrivKeyAddressPairs(10)
s.addresses = accAddresses
}
func (s *migrateTestSuite) TestMigrate_JSON() {
// Migrate v15 bep3 to v16
file := filepath.Join("testdata", "v15-bep3.json")
data, err := ioutil.ReadFile(file)
s.Require().NoError(err)
err = s.legacyCdc.UnmarshalJSON(data, &s.v15genstate)
s.Require().NoError(err)
genstate := Migrate(s.v15genstate)
// Compare expect v16 bep3 json with migrated json
actual := s.cdc.MustMarshalJSON(genstate)
file = filepath.Join("testdata", "v16-bep3.json")
expected, err := ioutil.ReadFile(file)
s.Require().NoError(err)
s.Require().JSONEq(string(expected), string(actual))
}
func (s *migrateTestSuite) TestMigrate_Swaps() {
type oldSwap struct {
ExpireHeight uint64
CloseBlock int64
Status v015bep3.SwapStatus
Direction v015bep3.SwapDirection
}
type newSwap struct {
ExpireHeight uint64
CloseBlock int64
Status v016bep3.SwapStatus
Direction v016bep3.SwapDirection
}
testcases := []struct {
name string
oldSwap oldSwap
newSwap newSwap
}{
{
name: "invalid swap direction",
oldSwap: oldSwap{
Direction: v015bep3.INVALID,
},
newSwap: newSwap{
Direction: v016bep3.SWAP_DIRECTION_UNSPECIFIED,
},
},
{
name: "invalid swap status",
oldSwap: oldSwap{
Status: v015bep3.NULL,
},
newSwap: newSwap{
Status: v016bep3.SWAP_STATUS_UNSPECIFIED,
},
},
{
name: "incoming open swap",
oldSwap: oldSwap{
// expire and close not set in open swaps
Status: v015bep3.Open,
Direction: v015bep3.Incoming,
},
newSwap: newSwap{
ExpireHeight: 1,
Status: v016bep3.SWAP_STATUS_EXPIRED,
Direction: v016bep3.SWAP_DIRECTION_INCOMING,
},
},
{
name: "outgoing open swap",
oldSwap: oldSwap{
// expire and close not set in open swaps
Status: v015bep3.Open,
Direction: v015bep3.Outgoing,
},
newSwap: newSwap{
ExpireHeight: 24687,
Status: v016bep3.SWAP_STATUS_OPEN,
Direction: v016bep3.SWAP_DIRECTION_OUTGOING,
},
},
{
name: "completed swap",
oldSwap: oldSwap{
ExpireHeight: 1000,
CloseBlock: 900,
Status: v015bep3.Completed,
Direction: v015bep3.Incoming,
},
newSwap: newSwap{
ExpireHeight: 1000,
CloseBlock: 1,
Status: v016bep3.SWAP_STATUS_COMPLETED,
Direction: v016bep3.SWAP_DIRECTION_INCOMING,
},
},
{
name: "expired swap",
oldSwap: oldSwap{
ExpireHeight: 1000,
CloseBlock: 900,
Status: v015bep3.Expired,
Direction: v015bep3.Incoming,
},
newSwap: newSwap{
ExpireHeight: 1,
CloseBlock: 900,
Status: v016bep3.SWAP_STATUS_EXPIRED,
Direction: v016bep3.SWAP_DIRECTION_INCOMING,
},
},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
oldSwaps := v015bep3.AtomicSwaps{
{
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(12))),
RandomNumberHash: bytes.HexBytes{},
ExpireHeight: tc.oldSwap.ExpireHeight,
Timestamp: 1110,
Sender: s.addresses[0],
Recipient: s.addresses[1],
RecipientOtherChain: s.addresses[0].String(),
SenderOtherChain: s.addresses[1].String(),
ClosedBlock: tc.oldSwap.CloseBlock,
Status: tc.oldSwap.Status,
CrossChain: true,
Direction: tc.oldSwap.Direction,
},
}
expectedSwaps := v016bep3.AtomicSwaps{
{
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(12))),
RandomNumberHash: bytes.HexBytes{},
ExpireHeight: tc.newSwap.ExpireHeight,
Timestamp: 1110,
Sender: s.addresses[0],
Recipient: s.addresses[1],
RecipientOtherChain: s.addresses[0].String(),
SenderOtherChain: s.addresses[1].String(),
ClosedBlock: tc.newSwap.CloseBlock,
Status: tc.newSwap.Status,
CrossChain: true,
Direction: tc.newSwap.Direction,
},
}
s.v15genstate.AtomicSwaps = oldSwaps
genState := Migrate(s.v15genstate)
s.Require().Len(genState.AtomicSwaps, 1)
s.Equal(expectedSwaps, genState.AtomicSwaps)
})
}
}
func (s *migrateTestSuite) TestMigrate_Params() {
params := v015bep3.AssetParams{
{
Denom: "bnb",
CoinID: int(714),
SupplyLimit: v015bep3.SupplyLimit{
Limit: sdk.NewInt(350000000000000),
TimeLimited: false,
TimeBasedLimit: sdk.ZeroInt(),
TimePeriod: time.Hour,
},
Active: true,
DeputyAddress: s.addresses[0],
FixedFee: sdk.NewInt(1000),
MinSwapAmount: sdk.OneInt(),
MaxSwapAmount: sdk.NewInt(1000000000000),
MinBlockLock: 220,
MaxBlockLock: 770,
},
}
expectedParams := v016bep3.AssetParams{
{
Denom: "bnb",
CoinID: int64(714),
SupplyLimit: v016bep3.SupplyLimit{
Limit: sdk.NewInt(350000000000000),
TimeLimited: false,
TimeBasedLimit: sdk.ZeroInt(),
TimePeriod: time.Hour,
},
Active: true,
DeputyAddress: s.addresses[0],
FixedFee: sdk.NewInt(1000),
MinSwapAmount: sdk.OneInt(),
MaxSwapAmount: sdk.NewInt(1000000000000),
MinBlockLock: 220,
MaxBlockLock: 770,
},
}
s.v15genstate.Params = v015bep3.Params{AssetParams: params}
genState := Migrate(s.v15genstate)
s.Require().Len(genState.Params.AssetParams, 1)
s.Require().Equal(v016bep3.Params{AssetParams: expectedParams}, genState.Params)
}
func (s *migrateTestSuite) TestMigrate_Supplies() {
supplies := v015bep3.AssetSupplies{
{
IncomingSupply: sdk.NewInt64Coin("bnb", 1000),
OutgoingSupply: sdk.NewInt64Coin("bnb", 1001),
CurrentSupply: sdk.NewInt64Coin("bnb", 1002),
TimeLimitedCurrentSupply: sdk.NewInt64Coin("bnb", 1003),
TimeElapsed: time.Hour,
},
}
expectedSupplies := v016bep3.AssetSupplies{
{
IncomingSupply: sdk.NewInt64Coin("bnb", 1000),
OutgoingSupply: sdk.NewInt64Coin("bnb", 1001),
CurrentSupply: sdk.NewInt64Coin("bnb", 1002),
TimeLimitedCurrentSupply: sdk.NewInt64Coin("bnb", 1003),
TimeElapsed: time.Hour,
},
}
s.v15genstate.Supplies = supplies
genState := Migrate(s.v15genstate)
s.Require().Len(genState.Supplies, 1)
s.Require().Equal(expectedSupplies, genState.Supplies)
}
func TestMigrateTestSuite(t *testing.T) {
suite.Run(t, new(migrateTestSuite))
}

View File

@ -0,0 +1,57 @@
package v0_16
import (
"fmt"
"github.com/kava-labs/kava/x/bep3/types"
)
// resetSwapForZeroHeight updates swap expiry/close heights to work when the chain height is reset to zero.
func resetSwapForZeroHeight(swap types.AtomicSwap) types.AtomicSwap {
switch status := swap.Status; status {
case types.SWAP_STATUS_COMPLETED:
// Reset closed block to one so completed swaps are not held in long term storage too long.
swap.ClosedBlock = 1
case types.SWAP_STATUS_OPEN:
switch dir := swap.Direction; dir {
case types.SWAP_DIRECTION_INCOMING:
// Open incoming swaps can be expired safely. They haven't been claimed yet, so the outgoing swap on bnb will just timeout.
// The chain downtime cannot be accurately predicted, so it's easier to expire than to recalculate a correct expire height.
swap.ExpireHeight = 1
swap.Status = types.SWAP_STATUS_EXPIRED
case types.SWAP_DIRECTION_OUTGOING:
// Open outgoing swaps should be extended to allow enough time to claim after the chain launches.
// They cannot be expired as there could be an open/claimed bnb swap.
swap.ExpireHeight = 1 + 24686 // default timeout used when sending swaps from kava
case types.SWAP_DIRECTION_UNSPECIFIED:
default:
panic(fmt.Sprintf("unknown bep3 swap direction '%s'", dir))
}
case types.SWAP_STATUS_EXPIRED:
// Once a swap is marked expired the expire height is ignored. However reset to 1 to be sure.
swap.ExpireHeight = 1
case types.SWAP_STATUS_UNSPECIFIED:
default:
panic(fmt.Sprintf("unknown bep3 swap status '%s'", status))
}
return swap
}
func resetSwapsForZeroHeight(oldSwaps types.AtomicSwaps) types.AtomicSwaps {
newSwaps := make(types.AtomicSwaps, len(oldSwaps))
for i, oldSwap := range oldSwaps {
swap := resetSwapForZeroHeight(oldSwap)
newSwaps[i] = swap
}
return newSwaps
}
func Migrate(oldState types.GenesisState) *types.GenesisState {
return &types.GenesisState{
PreviousBlockTime: oldState.PreviousBlockTime,
Params: oldState.Params,
AtomicSwaps: resetSwapsForZeroHeight(oldState.AtomicSwaps),
Supplies: oldState.Supplies,
}
}

View File

@ -0,0 +1,174 @@
package v0_16
import (
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/bytes"
app "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/types"
)
type migrateTestSuite struct {
suite.Suite
addresses []sdk.AccAddress
v16genstate types.GenesisState
cdc codec.Codec
}
func (s *migrateTestSuite) SetupTest() {
app.SetSDKConfig()
s.v16genstate = types.GenesisState{
PreviousBlockTime: time.Date(2021, 4, 8, 15, 0, 0, 0, time.UTC),
Params: types.Params{},
Supplies: types.AssetSupplies{},
AtomicSwaps: types.AtomicSwaps{},
}
config := app.MakeEncodingConfig()
s.cdc = config.Marshaler
_, accAddresses := app.GeneratePrivKeyAddressPairs(10)
s.addresses = accAddresses
}
func (s *migrateTestSuite) TestMigrate_JSON() {
// Migrate v16 bep3 to v17
file := filepath.Join("testdata", "v16-bep3.json")
data, err := ioutil.ReadFile(file)
s.Require().NoError(err)
err = s.cdc.UnmarshalJSON(data, &s.v16genstate)
s.Require().NoError(err)
genstate := Migrate(s.v16genstate)
// Compare expect v16 bep3 json with migrated json
actual := s.cdc.MustMarshalJSON(genstate)
file = filepath.Join("testdata", "v17-bep3.json")
expected, err := ioutil.ReadFile(file)
s.Require().NoError(err)
s.Require().JSONEq(string(expected), string(actual))
}
func (s *migrateTestSuite) TestMigrate_Swaps() {
type swap struct {
ExpireHeight uint64
CloseBlock int64
Status types.SwapStatus
Direction types.SwapDirection
}
testcases := []struct {
name string
oldSwap swap
newSwap swap
}{
{
name: "incoming open swap",
oldSwap: swap{
// expire and close not set in open swaps
Status: types.SWAP_STATUS_OPEN,
Direction: types.SWAP_DIRECTION_INCOMING,
},
newSwap: swap{
ExpireHeight: 1,
Status: types.SWAP_STATUS_EXPIRED,
Direction: types.SWAP_DIRECTION_INCOMING,
},
},
{
name: "outgoing open swap",
oldSwap: swap{
// expire and close not set in open swaps
Status: types.SWAP_STATUS_OPEN,
Direction: types.SWAP_DIRECTION_OUTGOING,
},
newSwap: swap{
ExpireHeight: 24687,
Status: types.SWAP_STATUS_OPEN,
Direction: types.SWAP_DIRECTION_OUTGOING,
},
},
{
name: "completed swap",
oldSwap: swap{
ExpireHeight: 1000,
CloseBlock: 900,
Status: types.SWAP_STATUS_COMPLETED,
Direction: types.SWAP_DIRECTION_INCOMING,
},
newSwap: swap{
ExpireHeight: 1000,
CloseBlock: 1,
Status: types.SWAP_STATUS_COMPLETED,
Direction: types.SWAP_DIRECTION_INCOMING,
},
},
{
name: "expired swap",
oldSwap: swap{
ExpireHeight: 1000,
CloseBlock: 900,
Status: types.SWAP_STATUS_EXPIRED,
Direction: types.SWAP_DIRECTION_INCOMING,
},
newSwap: swap{
ExpireHeight: 1,
CloseBlock: 900,
Status: types.SWAP_STATUS_EXPIRED,
Direction: types.SWAP_DIRECTION_INCOMING,
},
},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
oldSwaps := types.AtomicSwaps{
{
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(12))),
RandomNumberHash: bytes.HexBytes{},
ExpireHeight: tc.oldSwap.ExpireHeight,
Timestamp: 1110,
Sender: s.addresses[0],
Recipient: s.addresses[1],
RecipientOtherChain: s.addresses[0].String(),
SenderOtherChain: s.addresses[1].String(),
ClosedBlock: tc.oldSwap.CloseBlock,
Status: tc.oldSwap.Status,
CrossChain: true,
Direction: tc.oldSwap.Direction,
},
}
expectedSwaps := types.AtomicSwaps{
{
Amount: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(12))),
RandomNumberHash: bytes.HexBytes{},
ExpireHeight: tc.newSwap.ExpireHeight,
Timestamp: 1110,
Sender: s.addresses[0],
Recipient: s.addresses[1],
RecipientOtherChain: s.addresses[0].String(),
SenderOtherChain: s.addresses[1].String(),
ClosedBlock: tc.newSwap.CloseBlock,
Status: tc.newSwap.Status,
CrossChain: true,
Direction: tc.newSwap.Direction,
},
}
s.v16genstate.AtomicSwaps = oldSwaps
genState := Migrate(s.v16genstate)
s.Require().Len(genState.AtomicSwaps, 1)
s.Equal(expectedSwaps, genState.AtomicSwaps)
})
}
}
func TestMigrateTestSuite(t *testing.T) {
suite.Run(t, new(migrateTestSuite))
}

View File

@ -9,14 +9,14 @@
],
"closed_block": "838115",
"cross_chain": true,
"direction": "Incoming",
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "838627",
"random_number_hash": "6F1CF8F2E13A0C0F0A359F54E47E4E265D766B8E006D2F00BDF994ABDEF1E9E4",
"recipient": "kava1fl2hs6y9vz986g5v52pdan9ga923n9mn5cxxkw",
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
"sender_other_chain": "bnb19k9wuv2j7c7ck8tmc7kav0r0cnt3esmkrpf25x",
"status": "Completed",
"status": "SWAP_STATUS_COMPLETED",
"timestamp": "1636034914"
},
{
@ -28,14 +28,14 @@
],
"closed_block": "1712118",
"cross_chain": true,
"direction": "Outgoing",
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "1736797",
"random_number_hash": "280EB832A37F2265CC82F3957CE603AAD57BAD7038B876A1F28953AFA29FA1C3",
"recipient": "kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6",
"recipient_other_chain": "bnb18nsgj50zvc4uq93w4j0ltz5gaxhwv7aq4qnq0p",
"sender": "kava1zw6gg4ztvly7zf25pa33mclav3spvj3ympxxna",
"sender_other_chain": "bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn",
"status": "Completed",
"status": "SWAP_STATUS_COMPLETED",
"timestamp": "1641976566"
},
{
@ -47,14 +47,14 @@
],
"closed_block": "787122",
"cross_chain": true,
"direction": "Incoming",
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "811799",
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
"recipient": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
"recipient_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
"sender": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
"sender_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
"status": "Expired",
"status": "SWAP_STATUS_EXPIRED",
"timestamp": "1635694492"
},
{
@ -66,14 +66,14 @@
],
"closed_block": "787122",
"cross_chain": true,
"direction": "Outgoing",
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "811799",
"random_number_hash": "BFB7CC82DA0E0C8556AC37843F5AB136B9A7A066054368F5948944282B414D83",
"recipient": "kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu",
"recipient_other_chain": "bnb1vl3wn4x8kqajg2j9wxa5y5amgzdxchutkxr6at",
"sender": "kava1eufgf0w9d7hf5mgtek4zr2upkxag9stmzx6unl",
"sender_other_chain": "bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8",
"status": "Expired",
"status": "SWAP_STATUS_EXPIRED",
"timestamp": "1635694492"
},
{
@ -85,14 +85,14 @@
],
"closed_block": "0",
"cross_chain": true,
"direction": "Outgoing",
"direction": "SWAP_DIRECTION_OUTGOING",
"expire_height": "1730589",
"random_number_hash": "A74EA1AB58D312FDF1E872D18583CACCF294E639DDA4F303939E9ADCEC081D93",
"recipient": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
"recipient_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
"sender": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
"sender_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
"status": "Open",
"status": "SWAP_STATUS_OPEN",
"timestamp": "1641934114"
},
{
@ -104,14 +104,14 @@
],
"closed_block": "0",
"cross_chain": true,
"direction": "Incoming",
"direction": "SWAP_DIRECTION_INCOMING",
"expire_height": "1740000",
"random_number_hash": "39E9ADCEC081D93A74EA1A83CACCF294E639DDA4F3039B58D312FDF1E872D185",
"recipient": "kava1d2u28azje7rhqyjtxc2ex8q0cxxpw7dfm7ltq5",
"recipient_other_chain": "bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr",
"sender": "kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc",
"sender_other_chain": "bnb1lhk5ndlgf5wz55t8k35cqj6h9l3m4l5ek2w7q6",
"status": "Open",
"status": "SWAP_STATUS_OPEN",
"timestamp": "1641934114"
}
],
@ -131,7 +131,7 @@
"limit": "100000000000",
"time_based_limit": "0",
"time_limited": false,
"time_period": "0"
"time_period": "0s"
}
},
{
@ -148,7 +148,7 @@
"limit": "2000000000000000",
"time_based_limit": "0",
"time_limited": false,
"time_period": "0"
"time_period": "0s"
}
},
{
@ -165,7 +165,7 @@
"limit": "100000000000000",
"time_based_limit": "0",
"time_limited": false,
"time_period": "0"
"time_period": "0s"
}
},
{
@ -182,7 +182,7 @@
"limit": "2000000000000000",
"time_based_limit": "0",
"time_limited": false,
"time_period": "0"
"time_period": "0s"
}
}
]
@ -202,7 +202,7 @@
"amount": "0",
"denom": "bnb"
},
"time_elapsed": "0",
"time_elapsed": "0s",
"time_limited_current_supply": {
"amount": "0",
"denom": "bnb"