mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 23:35:19 +00:00
Add rate limiting to bep3 assets (#623)
* feat: use only module account for bep3 txs * wip: add time-based supply limits * add tests and sims * update genesis tests * fix migration, committee tests * update migrations * fix: set previous block time in begin block * update store decoder * add additional bep3 params to committee * revert incorrect rebase changes * add migration test * address review comments
This commit is contained in:
parent
c0006ca8eb
commit
5fc85f10a6
@ -97,7 +97,7 @@ var (
|
||||
cdp.ModuleName: {supply.Minter, supply.Burner},
|
||||
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
||||
cdp.SavingsRateMacc: {supply.Minter},
|
||||
bep3.ModuleName: nil,
|
||||
bep3.ModuleName: {supply.Minter, supply.Burner},
|
||||
kavadist.ModuleName: {supply.Minter},
|
||||
issuance.ModuleAccountName: {supply.Minter, supply.Burner},
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package v0_11
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
v0_11bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_11"
|
||||
@ -10,6 +12,7 @@ import (
|
||||
// MigrateBep3 migrates from a v0.9 (or v0.10) bep3 genesis state to a v0.11 bep3 genesis state
|
||||
func MigrateBep3(oldGenState v0_9bep3.GenesisState) v0_11bep3.GenesisState {
|
||||
var assetParams v0_11bep3.AssetParams
|
||||
var assetSupplies v0_11bep3.AssetSupplies
|
||||
v0_9Params := oldGenState.Params
|
||||
|
||||
for _, asset := range v0_9Params.SupportedAssets {
|
||||
@ -19,28 +22,22 @@ func MigrateBep3(oldGenState v0_9bep3.GenesisState) v0_11bep3.GenesisState {
|
||||
CoinID: asset.CoinID,
|
||||
DeputyAddress: v0_9Params.BnbDeputyAddress,
|
||||
FixedFee: v0_9Params.BnbDeputyFixedFee,
|
||||
MinSwapAmount: v0_9Params.MinAmount,
|
||||
MinSwapAmount: sdk.OneInt(), // set min swap to one - prevents accounts that hold zero bnb from creating spam txs
|
||||
MaxSwapAmount: v0_9Params.MaxAmount,
|
||||
MinBlockLock: v0_9Params.MinBlockLock,
|
||||
MaxBlockLock: v0_9Params.MaxBlockLock,
|
||||
SupplyLimit: v0_11bep3.AssetSupply{
|
||||
SupplyLimit: sdk.NewCoin(asset.Denom, sdk.ZeroInt()),
|
||||
CurrentSupply: sdk.NewCoin(asset.Denom, sdk.ZeroInt()),
|
||||
IncomingSupply: sdk.NewCoin(asset.Denom, sdk.ZeroInt()),
|
||||
OutgoingSupply: sdk.NewCoin(asset.Denom, sdk.ZeroInt()),
|
||||
SupplyLimit: v0_11bep3.SupplyLimit{
|
||||
Limit: asset.Limit,
|
||||
TimeLimited: false,
|
||||
TimePeriod: time.Duration(0),
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
},
|
||||
}
|
||||
assetParams = append(assetParams, v10AssetParam)
|
||||
}
|
||||
for _, supply := range oldGenState.AssetSupplies {
|
||||
for _, asset := range assetParams {
|
||||
if asset.Denom == supply.Denom {
|
||||
asset.SupplyLimit.SupplyLimit = supply.SupplyLimit
|
||||
asset.SupplyLimit.CurrentSupply = supply.CurrentSupply
|
||||
asset.SupplyLimit.IncomingSupply = supply.IncomingSupply
|
||||
asset.SupplyLimit.OutgoingSupply = supply.OutgoingSupply
|
||||
}
|
||||
}
|
||||
newSupply := v0_11bep3.NewAssetSupply(supply.IncomingSupply, supply.OutgoingSupply, supply.CurrentSupply, sdk.NewCoin(supply.CurrentSupply.Denom, sdk.ZeroInt()), time.Duration(0))
|
||||
assetSupplies = append(assetSupplies, newSupply)
|
||||
}
|
||||
var swaps v0_11bep3.AtomicSwaps
|
||||
for _, oldSwap := range oldGenState.AtomicSwaps {
|
||||
@ -63,6 +60,8 @@ func MigrateBep3(oldGenState v0_9bep3.GenesisState) v0_11bep3.GenesisState {
|
||||
return v0_11bep3.GenesisState{
|
||||
Params: v0_11bep3.Params{
|
||||
AssetParams: assetParams},
|
||||
AtomicSwaps: swaps,
|
||||
AtomicSwaps: swaps,
|
||||
Supplies: assetSupplies,
|
||||
PreviousBlockTime: v0_11bep3.DefaultPreviousBlockTime,
|
||||
}
|
||||
}
|
||||
|
37
migrate/v0_11/migrate_test.go
Normal file
37
migrate/v0_11/migrate_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package v0_11
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
app.SetBip44CoinType(config)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestMigrateBep3(t *testing.T) {
|
||||
bz, err := ioutil.ReadFile(filepath.Join("testdata", "bep3-v09.json"))
|
||||
require.NoError(t, err)
|
||||
var oldGenState v0_9bep3.GenesisState
|
||||
cdc := app.MakeCodec()
|
||||
require.NotPanics(t, func() {
|
||||
cdc.MustUnmarshalJSON(bz, &oldGenState)
|
||||
})
|
||||
|
||||
newGenState := MigrateBep3(oldGenState)
|
||||
err = newGenState.Validate()
|
||||
require.NoError(t, err)
|
||||
}
|
13265
migrate/v0_11/testdata/bep3-v09.json
vendored
Normal file
13265
migrate/v0_11/testdata/bep3-v09.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import (
|
||||
// BeginBlocker on every block expires outdated atomic swaps and removes closed
|
||||
// swap from long term storage (default storage time of 1 week)
|
||||
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||
k.UpdateTimeBasedSupplyLimits(ctx)
|
||||
k.UpdateExpiredAtomicSwaps(ctx)
|
||||
k.DeleteClosedAtomicSwapsFromLongtermStorage(ctx)
|
||||
}
|
||||
|
@ -120,6 +120,7 @@ var (
|
||||
DefaultMaxAmount = types.DefaultMaxAmount
|
||||
DefaultMinBlockLock = types.DefaultMinBlockLock
|
||||
DefaultMaxBlockLock = types.DefaultMaxBlockLock
|
||||
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
|
||||
)
|
||||
|
||||
type (
|
||||
@ -141,6 +142,7 @@ type (
|
||||
AtomicSwaps = types.AtomicSwaps
|
||||
SwapStatus = types.SwapStatus
|
||||
SwapDirection = types.SwapDirection
|
||||
SupplyLimit = types.SupplyLimit
|
||||
AugmentedAtomicSwap = types.AugmentedAtomicSwap
|
||||
AugmentedAtomicSwaps = types.AugmentedAtomicSwaps
|
||||
)
|
||||
|
@ -20,6 +20,8 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper
|
||||
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
|
||||
}
|
||||
|
||||
keeper.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
|
||||
|
||||
keeper.SetParams(ctx, gs.Params)
|
||||
for _, supply := range gs.Supplies {
|
||||
keeper.SetAssetSupply(ctx, supply, supply.GetDenom())
|
||||
@ -91,17 +93,17 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if supply.CurrentSupply.Amount.GT(limit) {
|
||||
panic(fmt.Sprintf("asset's current supply %s is over the supply limit %s", supply.CurrentSupply, limit))
|
||||
if supply.CurrentSupply.Amount.GT(limit.Limit) {
|
||||
panic(fmt.Sprintf("asset's current supply %s is over the supply limit %s", supply.CurrentSupply, limit.Limit))
|
||||
}
|
||||
if supply.IncomingSupply.Amount.GT(limit) {
|
||||
panic(fmt.Sprintf("asset's incoming supply %s is over the supply limit %s", supply.IncomingSupply, limit))
|
||||
if supply.IncomingSupply.Amount.GT(limit.Limit) {
|
||||
panic(fmt.Sprintf("asset's incoming supply %s is over the supply limit %s", supply.IncomingSupply, limit.Limit))
|
||||
}
|
||||
if supply.IncomingSupply.Amount.Add(supply.CurrentSupply.Amount).GT(limit) {
|
||||
panic(fmt.Sprintf("asset's incoming supply + current supply %s is over the supply limit %s", supply.IncomingSupply.Add(supply.CurrentSupply), limit))
|
||||
if supply.IncomingSupply.Amount.Add(supply.CurrentSupply.Amount).GT(limit.Limit) {
|
||||
panic(fmt.Sprintf("asset's incoming supply + current supply %s is over the supply limit %s", supply.IncomingSupply.Add(supply.CurrentSupply), limit.Limit))
|
||||
}
|
||||
if supply.OutgoingSupply.Amount.GT(limit) {
|
||||
panic(fmt.Sprintf("asset's outgoing supply %s is over the supply limit %s", supply.OutgoingSupply, limit))
|
||||
if supply.OutgoingSupply.Amount.GT(limit.Limit) {
|
||||
panic(fmt.Sprintf("asset's outgoing supply %s is over the supply limit %s", supply.OutgoingSupply, limit.Limit))
|
||||
}
|
||||
|
||||
}
|
||||
@ -112,5 +114,9 @@ func ExportGenesis(ctx sdk.Context, k Keeper) (data GenesisState) {
|
||||
params := k.GetParams(ctx)
|
||||
swaps := k.GetAllAtomicSwaps(ctx)
|
||||
supplies := k.GetAllAssetSupplies(ctx)
|
||||
return NewGenesisState(params, swaps, supplies)
|
||||
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||
if !found {
|
||||
previousBlockTime = DefaultPreviousBlockTime
|
||||
}
|
||||
return NewGenesisState(params, swaps, supplies, previousBlockTime)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
bep3.AssetSupply{
|
||||
IncomingSupply: c("bnb", 0),
|
||||
OutgoingSupply: c("bnb", 0),
|
||||
CurrentSupply: c("bnb", assetParam.SupplyLimit.Add(i(1)).Int64()),
|
||||
CurrentSupply: c("bnb", assetParam.SupplyLimit.Limit.Add(i(1)).Int64()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
|
||||
@ -109,7 +109,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
gs := baseGenState(suite.addrs[0])
|
||||
// Set up overlimit amount
|
||||
assetParam, _ := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||
overLimitAmount := assetParam.SupplyLimit.Add(i(1))
|
||||
overLimitAmount := assetParam.SupplyLimit.Limit.Add(i(1))
|
||||
|
||||
// Set up an atomic swap with amount equal to the currently asset supply
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
@ -124,7 +124,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
// Set up asset supply with overlimit current supply
|
||||
gs.Supplies = bep3.AssetSupplies{
|
||||
bep3.AssetSupply{
|
||||
IncomingSupply: c("bnb", assetParam.SupplyLimit.Add(i(1)).Int64()),
|
||||
IncomingSupply: c("bnb", assetParam.SupplyLimit.Limit.Add(i(1)).Int64()),
|
||||
OutgoingSupply: c("bnb", 0),
|
||||
CurrentSupply: c("bnb", 0),
|
||||
},
|
||||
@ -139,7 +139,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
gs := baseGenState(suite.addrs[0])
|
||||
// Set up overlimit amount
|
||||
assetParam, _ := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||
halfLimit := assetParam.SupplyLimit.Int64() / 2
|
||||
halfLimit := assetParam.SupplyLimit.Limit.Int64() / 2
|
||||
overHalfLimit := halfLimit + 1
|
||||
|
||||
// Set up an atomic swap with amount equal to the currently asset supply
|
||||
@ -170,7 +170,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
gs := baseGenState(suite.addrs[0])
|
||||
// Set up overlimit amount
|
||||
assetParam, _ := suite.keeper.GetAsset(suite.ctx, "bnb")
|
||||
overLimitAmount := assetParam.SupplyLimit.Add(i(1))
|
||||
overLimitAmount := assetParam.SupplyLimit.Limit.Add(i(1))
|
||||
|
||||
// Set up an atomic swap with amount equal to the currently asset supply
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
@ -187,7 +187,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
bep3.AssetSupply{
|
||||
IncomingSupply: c("bnb", 0),
|
||||
OutgoingSupply: c("bnb", 0),
|
||||
CurrentSupply: c("bnb", assetParam.SupplyLimit.Add(i(1)).Int64()),
|
||||
CurrentSupply: c("bnb", assetParam.SupplyLimit.Limit.Add(i(1)).Int64()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
|
||||
@ -266,7 +266,7 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
name: "negative supported asset limit",
|
||||
genState: func() app.GenesisState {
|
||||
gs := baseGenState(suite.addrs[0])
|
||||
gs.Params.AssetParams[0].SupplyLimit = i(-100)
|
||||
gs.Params.AssetParams[0].SupplyLimit.Limit = i(-100)
|
||||
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
|
||||
},
|
||||
expectPass: false,
|
||||
|
@ -40,9 +40,14 @@ func baseGenState(deputy sdk.AccAddress) bep3.GenesisState {
|
||||
Params: bep3.Params{
|
||||
AssetParams: bep3.AssetParams{
|
||||
bep3.AssetParam{
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: sdk.NewInt(350000000000000),
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: bep3.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -52,9 +57,14 @@ func baseGenState(deputy sdk.AccAddress) bep3.GenesisState {
|
||||
MaxBlockLock: bep3.DefaultMaxBlockLock,
|
||||
},
|
||||
bep3.AssetParam{
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: sdk.NewInt(100000000000),
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: bep3.SupplyLimit{
|
||||
Limit: sdk.NewInt(100000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -70,13 +80,18 @@ func baseGenState(deputy sdk.AccAddress) bep3.GenesisState {
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
time.Duration(0),
|
||||
),
|
||||
bep3.NewAssetSupply(
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
time.Duration(0),
|
||||
),
|
||||
},
|
||||
PreviousBlockTime: bep3.DefaultPreviousBlockTime,
|
||||
}
|
||||
return bep3Genesis
|
||||
}
|
||||
@ -92,7 +107,7 @@ func loadSwapAndSupply(addr sdk.AccAddress, index int) (bep3.AtomicSwap, bep3.As
|
||||
TestRecipientOtherChain, 1, bep3.Open, true, bep3.Incoming)
|
||||
|
||||
supply := bep3.NewAssetSupply(coin, c(coin.Denom, 0),
|
||||
c(coin.Denom, 0))
|
||||
c(coin.Denom, 0), c(coin.Denom, 0), time.Duration(0))
|
||||
|
||||
return swap, supply
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
@ -18,13 +20,21 @@ func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
supplyLimit := sdk.NewCoin(coin.Denom, limit)
|
||||
supplyLimit := sdk.NewCoin(coin.Denom, limit.Limit)
|
||||
|
||||
// Resulting current supply must be under asset's limit
|
||||
if supplyLimit.IsLT(supply.CurrentSupply.Add(coin)) {
|
||||
return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, supply.CurrentSupply, supplyLimit)
|
||||
}
|
||||
|
||||
if limit.TimeLimited {
|
||||
timeBasedSupplyLimit := sdk.NewCoin(coin.Denom, limit.TimeBasedLimit)
|
||||
if timeBasedSupplyLimit.IsLT(supply.TimeLimitedCurrentSupply.Add(coin)) {
|
||||
return sdkerrors.Wrapf(types.ErrExceedsTimeBasedSupplyLimit, "increase %s, current time-based asset supply %s, limit %s", coin, supply.TimeLimitedCurrentSupply, timeBasedSupplyLimit)
|
||||
}
|
||||
supply.TimeLimitedCurrentSupply = supply.TimeLimitedCurrentSupply.Add(coin)
|
||||
}
|
||||
|
||||
supply.CurrentSupply = supply.CurrentSupply.Add(coin)
|
||||
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||
return nil
|
||||
@ -62,11 +72,19 @@ func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
supplyLimit := sdk.NewCoin(coin.Denom, limit)
|
||||
supplyLimit := sdk.NewCoin(coin.Denom, limit.Limit)
|
||||
if supplyLimit.IsLT(totalSupply.Add(coin)) {
|
||||
return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, totalSupply, supplyLimit)
|
||||
}
|
||||
|
||||
if limit.TimeLimited {
|
||||
timeLimitedTotalSupply := supply.TimeLimitedCurrentSupply.Add(supply.IncomingSupply)
|
||||
timeBasedSupplyLimit := sdk.NewCoin(coin.Denom, limit.TimeBasedLimit)
|
||||
if timeBasedSupplyLimit.IsLT(timeLimitedTotalSupply.Add(coin)) {
|
||||
return sdkerrors.Wrapf(types.ErrExceedsTimeBasedSupplyLimit, "increase %s, time-based asset supply %s, limit %s", coin, supply.TimeLimitedCurrentSupply, timeBasedSupplyLimit)
|
||||
}
|
||||
}
|
||||
|
||||
supply.IncomingSupply = supply.IncomingSupply.Add(coin)
|
||||
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||
return nil
|
||||
@ -125,3 +143,42 @@ func (k Keeper) DecrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) err
|
||||
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNewAssetSupply creates a new AssetSupply in the store for the input denom
|
||||
func (k Keeper) CreateNewAssetSupply(ctx sdk.Context, denom string) types.AssetSupply {
|
||||
supply := types.NewAssetSupply(
|
||||
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()),
|
||||
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()), time.Duration(0))
|
||||
k.SetAssetSupply(ctx, supply, denom)
|
||||
return supply
|
||||
}
|
||||
|
||||
// UpdateTimeBasedSupplyLimits updates the time based supply for each asset, resetting it if the current time window has elapsed.
|
||||
func (k Keeper) UpdateTimeBasedSupplyLimits(ctx sdk.Context) {
|
||||
assets, found := k.GetAssets(ctx)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||
if !found {
|
||||
previousBlockTime = ctx.BlockTime()
|
||||
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
||||
}
|
||||
timeElapsed := ctx.BlockTime().Sub(previousBlockTime)
|
||||
for _, asset := range assets {
|
||||
supply, found := k.GetAssetSupply(ctx, asset.Denom)
|
||||
// if a new asset has been added by governance, create a new asset supply for it in the store
|
||||
if !found {
|
||||
supply = k.CreateNewAssetSupply(ctx, asset.Denom)
|
||||
}
|
||||
newTimeElapsed := supply.TimeElapsed + timeElapsed
|
||||
if asset.SupplyLimit.TimeLimited && newTimeElapsed < asset.SupplyLimit.TimePeriod {
|
||||
supply.TimeElapsed = newTimeElapsed
|
||||
} else {
|
||||
supply.TimeElapsed = time.Duration(0)
|
||||
supply.TimeLimitedCurrentSupply = sdk.NewCoin(asset.Denom, sdk.ZeroInt())
|
||||
}
|
||||
k.SetAssetSupply(ctx, supply, asset.Denom)
|
||||
}
|
||||
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -37,15 +39,17 @@ func (suite *AssetTestSuite) SetupTest() {
|
||||
|
||||
keeper := tApp.GetBep3Keeper()
|
||||
params := keeper.GetParams(ctx)
|
||||
params.AssetParams[0].SupplyLimit = sdk.NewInt(50)
|
||||
params.AssetParams[0].SupplyLimit.Limit = sdk.NewInt(50)
|
||||
params.AssetParams[1].SupplyLimit.Limit = sdk.NewInt(100)
|
||||
params.AssetParams[1].SupplyLimit.TimeBasedLimit = sdk.NewInt(15)
|
||||
keeper.SetParams(ctx, params)
|
||||
// Set asset supply with standard value for testing
|
||||
supply := types.AssetSupply{
|
||||
IncomingSupply: c("bnb", 5),
|
||||
OutgoingSupply: c("bnb", 5),
|
||||
CurrentSupply: c("bnb", 40),
|
||||
}
|
||||
keeper.SetAssetSupply(ctx, supply, supply.GetDenom())
|
||||
supply := types.NewAssetSupply(c("bnb", 5), c("bnb", 5), c("bnb", 40), c("bnb", 0), time.Duration(0))
|
||||
keeper.SetAssetSupply(ctx, supply, supply.IncomingSupply.Denom)
|
||||
|
||||
supply = types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0))
|
||||
keeper.SetAssetSupply(ctx, supply, supply.IncomingSupply.Denom)
|
||||
keeper.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
@ -112,6 +116,64 @@ func (suite *AssetTestSuite) TestIncrementCurrentAssetSupply() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AssetTestSuite) TestIncrementTimeLimitedCurrentAssetSupply() {
|
||||
type args struct {
|
||||
coin sdk.Coin
|
||||
expectedSupply types.AssetSupply
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}{
|
||||
{
|
||||
"normal",
|
||||
args{
|
||||
coin: c("inc", 5),
|
||||
expectedSupply: types.AssetSupply{
|
||||
IncomingSupply: c("inc", 10),
|
||||
OutgoingSupply: c("inc", 5),
|
||||
CurrentSupply: c("inc", 10),
|
||||
TimeLimitedCurrentSupply: c("inc", 5),
|
||||
TimeElapsed: time.Duration(0)},
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"over limit",
|
||||
args{
|
||||
coin: c("inc", 16),
|
||||
expectedSupply: types.AssetSupply{},
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "asset supply over limit for current time period",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.SetupTest()
|
||||
suite.Run(tc.name, func() {
|
||||
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coin)
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
supply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AssetTestSuite) TestDecrementCurrentAssetSupply() {
|
||||
type args struct {
|
||||
coin sdk.Coin
|
||||
@ -229,6 +291,64 @@ func (suite *AssetTestSuite) TestIncrementIncomingAssetSupply() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AssetTestSuite) TestIncrementTimeLimitedIncomingAssetSupply() {
|
||||
type args struct {
|
||||
coin sdk.Coin
|
||||
expectedSupply types.AssetSupply
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}{
|
||||
{
|
||||
"normal",
|
||||
args{
|
||||
coin: c("inc", 5),
|
||||
expectedSupply: types.AssetSupply{
|
||||
IncomingSupply: c("inc", 15),
|
||||
OutgoingSupply: c("inc", 5),
|
||||
CurrentSupply: c("inc", 5),
|
||||
TimeLimitedCurrentSupply: c("inc", 0),
|
||||
TimeElapsed: time.Duration(0)},
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"over limit",
|
||||
args{
|
||||
coin: c("inc", 6),
|
||||
expectedSupply: types.AssetSupply{},
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "asset supply over limit for current time period",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.SetupTest()
|
||||
suite.Run(tc.name, func() {
|
||||
err := suite.keeper.IncrementIncomingAssetSupply(suite.ctx, tc.args.coin)
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
supply, _ := suite.keeper.GetAssetSupply(suite.ctx, tc.args.coin.Denom)
|
||||
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AssetTestSuite) TestDecrementIncomingAssetSupply() {
|
||||
type args struct {
|
||||
coin sdk.Coin
|
||||
@ -405,6 +525,181 @@ func (suite *AssetTestSuite) TestDecrementOutgoingAssetSupply() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AssetTestSuite) TestUpdateTimeBasedSupplyLimits() {
|
||||
type args struct {
|
||||
asset string
|
||||
duration time.Duration
|
||||
expectedSupply types.AssetSupply
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPanic bool
|
||||
contains string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}{
|
||||
{
|
||||
"rate-limited increment time",
|
||||
args{
|
||||
asset: "inc",
|
||||
duration: time.Second,
|
||||
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Second),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rate-limited increment time half",
|
||||
args{
|
||||
asset: "inc",
|
||||
duration: time.Minute * 30,
|
||||
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Minute*30),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rate-limited period change",
|
||||
args{
|
||||
asset: "inc",
|
||||
duration: time.Hour + time.Second,
|
||||
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rate-limited period change exact",
|
||||
args{
|
||||
asset: "inc",
|
||||
duration: time.Hour,
|
||||
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rate-limited period change big",
|
||||
args{
|
||||
asset: "inc",
|
||||
duration: time.Hour * 4,
|
||||
expectedSupply: types.NewAssetSupply(c("inc", 10), c("inc", 5), c("inc", 5), c("inc", 0), time.Duration(0)),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"non rate-limited increment time",
|
||||
args{
|
||||
asset: "bnb",
|
||||
duration: time.Second,
|
||||
expectedSupply: types.NewAssetSupply(c("bnb", 5), c("bnb", 5), c("bnb", 40), c("bnb", 0), time.Duration(0)),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"new asset increment time",
|
||||
args{
|
||||
asset: "lol",
|
||||
duration: time.Second,
|
||||
expectedSupply: types.NewAssetSupply(c("lol", 0), c("lol", 0), c("lol", 0), c("lol", 0), time.Second),
|
||||
},
|
||||
errArgs{
|
||||
expectPanic: false,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.SetupTest()
|
||||
suite.Run(tc.name, func() {
|
||||
deputy, _ := sdk.AccAddressFromBech32(TestDeputy)
|
||||
newParams := types.Params{
|
||||
AssetParams: types.AssetParams{
|
||||
types.AssetParam{
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
MinSwapAmount: sdk.OneInt(),
|
||||
MaxSwapAmount: sdk.NewInt(1000000000000),
|
||||
MinBlockLock: types.DefaultMinBlockLock,
|
||||
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||
},
|
||||
types.AssetParam{
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: sdk.NewInt(100),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(10),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: false,
|
||||
DeputyAddress: deputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
MinSwapAmount: sdk.OneInt(),
|
||||
MaxSwapAmount: sdk.NewInt(1000000000000),
|
||||
MinBlockLock: types.DefaultMinBlockLock,
|
||||
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||
},
|
||||
types.AssetParam{
|
||||
Denom: "lol",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: sdk.NewInt(100),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(10),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: false,
|
||||
DeputyAddress: deputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
MinSwapAmount: sdk.OneInt(),
|
||||
MaxSwapAmount: sdk.NewInt(1000000000000),
|
||||
MinBlockLock: types.DefaultMinBlockLock,
|
||||
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.keeper.SetParams(suite.ctx, newParams)
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(tc.args.duration))
|
||||
suite.NotPanics(
|
||||
func() {
|
||||
suite.keeper.UpdateTimeBasedSupplyLimits(suite.ctx)
|
||||
},
|
||||
)
|
||||
if !tc.errArgs.expectPanic {
|
||||
supply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.asset)
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(tc.args.expectedSupply, supply)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(AssetTestSuite))
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/bep3"
|
||||
"github.com/kava-labs/kava/x/bep3/types"
|
||||
)
|
||||
|
||||
@ -32,12 +31,17 @@ func ts(minOffset int) int64 { return tmtime.Now().Add(time.Durat
|
||||
|
||||
func NewBep3GenStateMulti(deputyAddress sdk.AccAddress) app.GenesisState {
|
||||
bep3Genesis := types.GenesisState{
|
||||
Params: bep3.Params{
|
||||
Params: types.Params{
|
||||
AssetParams: types.AssetParams{
|
||||
types.AssetParam{
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: sdk.NewInt(350000000000000),
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputyAddress,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -47,33 +51,43 @@ func NewBep3GenStateMulti(deputyAddress sdk.AccAddress) app.GenesisState {
|
||||
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||
},
|
||||
types.AssetParam{
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: sdk.NewInt(100),
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: sdk.NewInt(100000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: false,
|
||||
DeputyAddress: deputyAddress,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
MinSwapAmount: sdk.OneInt(),
|
||||
MaxSwapAmount: sdk.NewInt(1000000000000),
|
||||
MaxSwapAmount: sdk.NewInt(100000000000),
|
||||
MinBlockLock: types.DefaultMinBlockLock,
|
||||
MaxBlockLock: types.DefaultMaxBlockLock,
|
||||
},
|
||||
},
|
||||
},
|
||||
Supplies: bep3.AssetSupplies{
|
||||
bep3.NewAssetSupply(
|
||||
Supplies: types.AssetSupplies{
|
||||
types.NewAssetSupply(
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
sdk.NewCoin("bnb", sdk.ZeroInt()),
|
||||
time.Duration(0),
|
||||
),
|
||||
bep3.NewAssetSupply(
|
||||
types.NewAssetSupply(
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
sdk.NewCoin("inc", sdk.ZeroInt()),
|
||||
time.Duration(0),
|
||||
),
|
||||
},
|
||||
PreviousBlockTime: types.DefaultPreviousBlockTime,
|
||||
}
|
||||
return app.GenesisState{bep3.ModuleName: bep3.ModuleCdc.MustMarshalJSON(bep3Genesis)}
|
||||
return app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(bep3Genesis)}
|
||||
}
|
||||
|
||||
func atomicSwaps(ctx sdk.Context, count int) types.AtomicSwaps {
|
||||
@ -112,5 +126,5 @@ func assetSupplies(count int) types.AssetSupplies {
|
||||
}
|
||||
|
||||
func assetSupply(denom string) types.AssetSupply {
|
||||
return types.NewAssetSupply(c(denom, 0), c(denom, 0), c(denom, 0))
|
||||
return types.NewAssetSupply(c(denom, 0), c(denom, 0), c(denom, 0), c(denom, 0), time.Duration(0))
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
@ -222,3 +223,20 @@ func (k Keeper) GetAllAssetSupplies(ctx sdk.Context) (supplies types.AssetSuppli
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetPreviousBlockTime get the blocktime for the previous block
|
||||
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||
b := store.Get([]byte{})
|
||||
if b == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &blockTime)
|
||||
return blockTime, true
|
||||
}
|
||||
|
||||
// SetPreviousBlockTime set the time of the previous block
|
||||
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(blockTime))
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ func (suite *KeeperTestSuite) TestIterateAtomicSwapsLongtermStorage() {
|
||||
func (suite *KeeperTestSuite) TestGetSetAssetSupply() {
|
||||
denom := "bnb"
|
||||
// Put asset supply in store
|
||||
assetSupply := types.NewAssetSupply(c(denom, 0), c(denom, 0), c(denom, 50000))
|
||||
assetSupply := types.NewAssetSupply(c(denom, 0), c(denom, 0), c(denom, 50000), c(denom, 0), time.Duration(0))
|
||||
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, denom)
|
||||
|
||||
// Check asset in store
|
||||
@ -327,9 +327,9 @@ func (suite *KeeperTestSuite) TestGetSetAssetSupply() {
|
||||
func (suite *KeeperTestSuite) TestGetAllAssetSupplies() {
|
||||
|
||||
// Put asset supply in store
|
||||
assetSupply := types.NewAssetSupply(c("bnb", 0), c("bnb", 0), c("bnb", 50000))
|
||||
assetSupply := types.NewAssetSupply(c("bnb", 0), c("bnb", 0), c("bnb", 50000), c("bnb", 0), time.Duration(0))
|
||||
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, "bnb")
|
||||
assetSupply = types.NewAssetSupply(c("inc", 0), c("inc", 0), c("inc", 50000))
|
||||
assetSupply = types.NewAssetSupply(c("inc", 0), c("inc", 0), c("inc", 50000), c("inc", 0), time.Duration(0))
|
||||
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, "inc")
|
||||
|
||||
supplies := suite.keeper.GetAllAssetSupplies(suite.ctx)
|
||||
|
@ -132,10 +132,10 @@ func (k Keeper) ValidateLiveAsset(ctx sdk.Context, coin sdk.Coin) error {
|
||||
}
|
||||
|
||||
// GetSupplyLimit returns the supply limit for the input denom
|
||||
func (k Keeper) GetSupplyLimit(ctx sdk.Context, denom string) (sdk.Int, error) {
|
||||
func (k Keeper) GetSupplyLimit(ctx sdk.Context, denom string) (types.SupplyLimit, error) {
|
||||
asset, err := k.GetAsset(ctx, denom)
|
||||
if err != nil {
|
||||
return sdk.Int{}, err
|
||||
return types.SupplyLimit{}, err
|
||||
}
|
||||
return asset.SupplyLimit, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -102,7 +103,7 @@ func (suite *QuerierTestSuite) TestQueryAssetSupply() {
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &supply))
|
||||
|
||||
expectedSupply := types.NewAssetSupply(c(denom, 1000),
|
||||
c(denom, 0), c(denom, 0))
|
||||
c(denom, 0), c(denom, 0), c(denom, 0), time.Duration(0))
|
||||
suite.Equal(supply, expectedSupply)
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times
|
||||
err = k.IncrementIncomingAssetSupply(ctx, amount[0])
|
||||
case types.Outgoing:
|
||||
|
||||
// Outoing swaps must have a height span within the accepted range
|
||||
// Outgoing swaps must have a height span within the accepted range
|
||||
if heightSpan < asset.MinBlockLock || heightSpan > asset.MaxBlockLock {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidHeightSpan, "height span %d outside range [%d, %d]", heightSpan, asset.MinBlockLock, asset.MaxBlockLock)
|
||||
}
|
||||
@ -88,6 +88,11 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times
|
||||
return sdkerrors.Wrap(types.ErrInsufficientAmount, amount[0].String())
|
||||
}
|
||||
err = k.IncrementOutgoingAssetSupply(ctx, amount[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Transfer coins to module - only needed for outgoing swaps
|
||||
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount)
|
||||
default:
|
||||
err = fmt.Errorf("invalid swap direction: %s", direction.String())
|
||||
}
|
||||
@ -95,12 +100,6 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times
|
||||
return err
|
||||
}
|
||||
|
||||
// Transfer coins to module
|
||||
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the details of the swap
|
||||
expireHeight := uint64(ctx.BlockHeight()) + heightSpan
|
||||
atomicSwap := types.NewAtomicSwap(amount, randomNumberHash, expireHeight, timestamp, sender,
|
||||
@ -161,6 +160,16 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// incoming case - coins should be MINTED, then sent to user
|
||||
err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, atomicSwap.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Send intended recipient coins
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case types.Outgoing:
|
||||
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||
if err != nil {
|
||||
@ -170,16 +179,15 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// outgoing case - coins should be burned
|
||||
err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, atomicSwap.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
|
||||
}
|
||||
|
||||
// Send intended recipient coins
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Complete swap
|
||||
atomicSwap.Status = types.Completed
|
||||
atomicSwap.ClosedBlock = ctx.BlockHeight()
|
||||
@ -221,6 +229,11 @@ func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []
|
||||
err = k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||
case types.Outgoing:
|
||||
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Refund coins to original swap sender for outgoing swaps
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount)
|
||||
default:
|
||||
err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
|
||||
}
|
||||
@ -229,12 +242,6 @@ func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []
|
||||
return err
|
||||
}
|
||||
|
||||
// Refund coins to original swap sender
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Complete swap
|
||||
atomicSwap.Status = types.Completed
|
||||
atomicSwap.ClosedBlock = ctx.BlockHeight()
|
||||
|
@ -34,8 +34,10 @@ type AtomicSwapTestSuite struct {
|
||||
}
|
||||
|
||||
const (
|
||||
STARING_BNB_BALANCE = int64(3000000000000)
|
||||
BNB_DENOM = "bnb"
|
||||
STARING_BNB_BALANCE = int64(3000000000000)
|
||||
BNB_DENOM = "bnb"
|
||||
OTHER_DENOM = "inc"
|
||||
STARING_OTHER_BALANCE = int64(3000000000000)
|
||||
)
|
||||
|
||||
func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||
@ -49,7 +51,7 @@ func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||
// Create and load 20 accounts with bnb tokens
|
||||
coins := []sdk.Coins{}
|
||||
for i := 0; i < 20; i++ {
|
||||
coins = append(coins, cs(c(BNB_DENOM, STARING_BNB_BALANCE)))
|
||||
coins = append(coins, cs(c(BNB_DENOM, STARING_BNB_BALANCE), c(OTHER_DENOM, STARING_OTHER_BALANCE)))
|
||||
}
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(20)
|
||||
deputy := addrs[0]
|
||||
@ -58,11 +60,16 @@ func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||
// Initialize genesis state
|
||||
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(deputy))
|
||||
|
||||
keeper := tApp.GetBep3Keeper()
|
||||
params := keeper.GetParams(ctx)
|
||||
params.AssetParams[1].Active = true
|
||||
keeper.SetParams(ctx, params)
|
||||
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.deputy = deputy
|
||||
suite.addrs = addrs
|
||||
suite.keeper = suite.app.GetBep3Keeper()
|
||||
suite.keeper = keeper
|
||||
|
||||
// Load a random module account to test blacklisting
|
||||
i := 0
|
||||
@ -138,6 +145,42 @@ func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() {
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"incoming swap rate limited",
|
||||
currentTmTime.Add(time.Minute * 10),
|
||||
args{
|
||||
randomNumberHash: suite.randomNumberHashes[12],
|
||||
timestamp: suite.timestamps[12],
|
||||
heightSpan: types.DefaultMinBlockLock,
|
||||
sender: suite.deputy,
|
||||
recipient: suite.addrs[1],
|
||||
senderOtherChain: TestSenderOtherChain,
|
||||
recipientOtherChain: TestRecipientOtherChain,
|
||||
coins: cs(c("inc", 50000000000)),
|
||||
crossChain: true,
|
||||
direction: types.Incoming,
|
||||
},
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"incoming swap over rate limit",
|
||||
currentTmTime.Add(time.Minute * 10),
|
||||
args{
|
||||
randomNumberHash: suite.randomNumberHashes[13],
|
||||
timestamp: suite.timestamps[13],
|
||||
heightSpan: types.DefaultMinBlockLock,
|
||||
sender: suite.deputy,
|
||||
recipient: suite.addrs[1],
|
||||
senderOtherChain: TestSenderOtherChain,
|
||||
recipientOtherChain: TestRecipientOtherChain,
|
||||
coins: cs(c("inc", 50000000001)),
|
||||
crossChain: true,
|
||||
direction: types.Incoming,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"outgoing swap",
|
||||
currentTmTime,
|
||||
@ -396,14 +439,13 @@ func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() {
|
||||
if tc.expectPass {
|
||||
suite.NoError(err)
|
||||
|
||||
// Check coins moved
|
||||
suite.Equal(senderBalancePre.Sub(tc.args.coins[0].Amount), senderBalancePost)
|
||||
|
||||
// Check incoming/outgoing asset supply increased
|
||||
switch tc.args.direction {
|
||||
case types.Incoming:
|
||||
suite.Equal(assetSupplyPre.IncomingSupply.Add(tc.args.coins[0]), assetSupplyPost.IncomingSupply)
|
||||
case types.Outgoing:
|
||||
// Check coins moved
|
||||
suite.Equal(senderBalancePre.Sub(tc.args.coins[0].Amount), senderBalancePost)
|
||||
suite.Equal(assetSupplyPre.OutgoingSupply.Add(tc.args.coins[0]), assetSupplyPost.OutgoingSupply)
|
||||
default:
|
||||
suite.Fail("should not have invalid direction")
|
||||
@ -460,8 +502,10 @@ func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() {
|
||||
|
||||
func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
suite.SetupTest()
|
||||
currentTmTime := tmtime.Now()
|
||||
invalidRandomNumber, _ := types.GenerateSecureRandomNumber()
|
||||
type args struct {
|
||||
coins sdk.Coins
|
||||
swapID []byte
|
||||
randomNumber []byte
|
||||
direction types.SwapDirection
|
||||
@ -476,6 +520,18 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
"normal incoming swap",
|
||||
suite.ctx,
|
||||
args{
|
||||
coins: cs(c(BNB_DENOM, 50000)),
|
||||
swapID: []byte{},
|
||||
randomNumber: []byte{},
|
||||
direction: types.Incoming,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"normal incoming swap rate-limited",
|
||||
suite.ctx.WithBlockTime(currentTmTime.Add(time.Minute * 10)),
|
||||
args{
|
||||
coins: cs(c(OTHER_DENOM, 50000)),
|
||||
swapID: []byte{},
|
||||
randomNumber: []byte{},
|
||||
direction: types.Incoming,
|
||||
@ -486,6 +542,7 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
"normal outgoing swap",
|
||||
suite.ctx,
|
||||
args{
|
||||
coins: cs(c(BNB_DENOM, 50000)),
|
||||
swapID: []byte{},
|
||||
randomNumber: []byte{},
|
||||
direction: types.Outgoing,
|
||||
@ -496,6 +553,7 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
"invalid random number",
|
||||
suite.ctx,
|
||||
args{
|
||||
coins: cs(c(BNB_DENOM, 50000)),
|
||||
swapID: []byte{},
|
||||
randomNumber: invalidRandomNumber[:],
|
||||
direction: types.Incoming,
|
||||
@ -506,6 +564,7 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
"wrong swap ID",
|
||||
suite.ctx,
|
||||
args{
|
||||
coins: cs(c(BNB_DENOM, 50000)),
|
||||
swapID: types.CalculateSwapID(suite.randomNumberHashes[3], suite.addrs[6], TestRecipientOtherChain),
|
||||
randomNumber: []byte{},
|
||||
direction: types.Outgoing,
|
||||
@ -516,6 +575,7 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
"past expiration",
|
||||
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 2000),
|
||||
args{
|
||||
coins: cs(c(BNB_DENOM, 50000)),
|
||||
swapID: []byte{},
|
||||
randomNumber: []byte{},
|
||||
direction: types.Incoming,
|
||||
@ -528,21 +588,20 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
suite.GenerateSwapDetails()
|
||||
suite.Run(tc.name, func() {
|
||||
expectedRecipient := suite.addrs[5]
|
||||
expectedClaimAmount := cs(c(BNB_DENOM, 50000))
|
||||
sender := suite.deputy
|
||||
|
||||
// Set sender to other and increment current asset supply for outgoing swap
|
||||
if tc.args.direction == types.Outgoing {
|
||||
sender = suite.addrs[6]
|
||||
expectedRecipient = suite.deputy
|
||||
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, expectedClaimAmount[0])
|
||||
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coins[0])
|
||||
suite.Nil(err)
|
||||
}
|
||||
|
||||
// Create atomic swap
|
||||
err := suite.keeper.CreateAtomicSwap(suite.ctx, suite.randomNumberHashes[i], suite.timestamps[i],
|
||||
types.DefaultMinBlockLock, sender, expectedRecipient, TestSenderOtherChain, TestRecipientOtherChain,
|
||||
expectedClaimAmount, true)
|
||||
tc.args.coins, true)
|
||||
suite.NoError(err)
|
||||
|
||||
realSwapID := types.CalculateSwapID(suite.randomNumberHashes[i], sender, TestSenderOtherChain)
|
||||
@ -569,40 +628,40 @@ func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
|
||||
// Load expected recipient's account prior to claim attempt
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
expectedRecipientAccPre := ak.GetAccount(tc.claimCtx, expectedRecipient)
|
||||
expectedRecipientBalancePre := expectedRecipientAccPre.GetCoins().AmountOf(expectedClaimAmount[0].Denom)
|
||||
expectedRecipientBalancePre := expectedRecipientAccPre.GetCoins().AmountOf(tc.args.coins[0].Denom)
|
||||
// Load asset supplies prior to claim attempt
|
||||
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.claimCtx, expectedClaimAmount[0].Denom)
|
||||
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.claimCtx, tc.args.coins[0].Denom)
|
||||
|
||||
// Attempt to claim atomic swap
|
||||
err = suite.keeper.ClaimAtomicSwap(tc.claimCtx, expectedRecipient, claimSwapID, claimRandomNumber)
|
||||
|
||||
// Load expected recipient's account after the claim attempt
|
||||
expectedRecipientAccPost := ak.GetAccount(tc.claimCtx, expectedRecipient)
|
||||
expectedRecipientBalancePost := expectedRecipientAccPost.GetCoins().AmountOf(expectedClaimAmount[0].Denom)
|
||||
expectedRecipientBalancePost := expectedRecipientAccPost.GetCoins().AmountOf(tc.args.coins[0].Denom)
|
||||
// Load asset supplies after the claim attempt
|
||||
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.claimCtx, expectedClaimAmount[0].Denom)
|
||||
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.claimCtx, tc.args.coins[0].Denom)
|
||||
|
||||
if tc.expectPass {
|
||||
suite.NoError(err)
|
||||
// Check coins moved
|
||||
suite.Equal(expectedRecipientBalancePre.Add(expectedClaimAmount[0].Amount), expectedRecipientBalancePost)
|
||||
|
||||
// Check asset supply changes
|
||||
switch tc.args.direction {
|
||||
case types.Incoming:
|
||||
// Check coins moved
|
||||
suite.Equal(expectedRecipientBalancePre.Add(tc.args.coins[0].Amount), expectedRecipientBalancePost)
|
||||
// Check incoming supply decreased
|
||||
suite.True(assetSupplyPre.IncomingSupply.Amount.Sub(expectedClaimAmount[0].Amount).Equal(assetSupplyPost.IncomingSupply.Amount))
|
||||
suite.True(assetSupplyPre.IncomingSupply.Amount.Sub(tc.args.coins[0].Amount).Equal(assetSupplyPost.IncomingSupply.Amount))
|
||||
// Check current supply increased
|
||||
suite.Equal(assetSupplyPre.CurrentSupply.Add(expectedClaimAmount[0]), assetSupplyPost.CurrentSupply)
|
||||
suite.Equal(assetSupplyPre.CurrentSupply.Add(tc.args.coins[0]), assetSupplyPost.CurrentSupply)
|
||||
// Check outgoing supply not changed
|
||||
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||
case types.Outgoing:
|
||||
// Check incoming supply not changed
|
||||
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||
// Check current supply decreased
|
||||
suite.Equal(assetSupplyPre.CurrentSupply.Sub(expectedClaimAmount[0]), assetSupplyPost.CurrentSupply)
|
||||
suite.Equal(assetSupplyPre.CurrentSupply.Sub(tc.args.coins[0]), assetSupplyPost.CurrentSupply)
|
||||
// Check outgoing supply decreased
|
||||
suite.True(assetSupplyPre.OutgoingSupply.Sub(expectedClaimAmount[0]).IsEqual(assetSupplyPost.OutgoingSupply))
|
||||
suite.True(assetSupplyPre.OutgoingSupply.Sub(tc.args.coins[0]).IsEqual(assetSupplyPost.OutgoingSupply))
|
||||
default:
|
||||
suite.Fail("should not have invalid direction")
|
||||
}
|
||||
@ -732,8 +791,6 @@ func (suite *AtomicSwapTestSuite) TestRefundAtomicSwap() {
|
||||
|
||||
if tc.expectPass {
|
||||
suite.NoError(err)
|
||||
// Check coins moved
|
||||
suite.Equal(originalSenderBalancePre.Add(expectedRefundAmount[0].Amount), originalSenderBalancePost)
|
||||
|
||||
// Check asset supply changes
|
||||
switch tc.args.direction {
|
||||
@ -744,6 +801,8 @@ func (suite *AtomicSwapTestSuite) TestRefundAtomicSwap() {
|
||||
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
|
||||
case types.Outgoing:
|
||||
// Check coins moved
|
||||
suite.Equal(originalSenderBalancePre.Add(expectedRefundAmount[0].Amount), originalSenderBalancePost)
|
||||
// Check incoming, current supply not changed
|
||||
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
|
||||
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
|
||||
|
@ -1,14 +1,65 @@
|
||||
package v0_11
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
var (
|
||||
AddrByteCount = 20
|
||||
RandomNumberHashLength = 32
|
||||
RandomNumberLength = 32
|
||||
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
)
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// Validate validates genesis inputs. It returns error if validation of any input fails.
|
||||
func (gs GenesisState) Validate() error {
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids := map[string]bool{}
|
||||
for _, swap := range gs.AtomicSwaps {
|
||||
if ids[hex.EncodeToString(swap.GetSwapID())] {
|
||||
return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(swap.GetSwapID()))
|
||||
}
|
||||
|
||||
if err := swap.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids[hex.EncodeToString(swap.GetSwapID())] = true
|
||||
}
|
||||
|
||||
supplyDenoms := map[string]bool{}
|
||||
for _, supply := range gs.Supplies {
|
||||
if err := supply.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if supplyDenoms[supply.GetDenom()] {
|
||||
return fmt.Errorf("found duplicate denom in asset supplies %s", supply.GetDenom())
|
||||
}
|
||||
supplyDenoms[supply.GetDenom()] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Params governance parameters for the bep3 module
|
||||
@ -16,11 +67,83 @@ type Params struct {
|
||||
AssetParams AssetParams `json:"asset_params" yaml:"asset_params"`
|
||||
}
|
||||
|
||||
// Validate ensure that params have valid values
|
||||
func (p Params) Validate() error {
|
||||
return validateAssetParams(p.AssetParams)
|
||||
}
|
||||
|
||||
func validateAssetParams(i interface{}) error {
|
||||
assetParams, ok := i.(AssetParams)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
coinDenoms := make(map[string]bool)
|
||||
for _, asset := range assetParams {
|
||||
if err := sdk.ValidateDenom(asset.Denom); err != nil {
|
||||
return fmt.Errorf(fmt.Sprintf("asset denom invalid: %s", asset.Denom))
|
||||
}
|
||||
|
||||
if asset.CoinID < 0 {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s coin id must be a non negative integer", asset.Denom))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.Limit.IsNegative() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply limit: %s", asset.Denom, asset.SupplyLimit.Limit))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.TimeBasedLimit.IsNegative() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply time limit: %s", asset.Denom, asset.SupplyLimit.TimeBasedLimit))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.TimeBasedLimit.GT(asset.SupplyLimit.Limit) {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s cannot have supply time limit > supply limit: %s>%s", asset.Denom, asset.SupplyLimit.TimeBasedLimit, asset.SupplyLimit.Limit))
|
||||
}
|
||||
|
||||
_, found := coinDenoms[asset.Denom]
|
||||
if found {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom))
|
||||
}
|
||||
|
||||
coinDenoms[asset.Denom] = true
|
||||
|
||||
if asset.DeputyAddress.Empty() {
|
||||
return fmt.Errorf("deputy address cannot be empty for %s", asset.Denom)
|
||||
}
|
||||
|
||||
if len(asset.DeputyAddress.Bytes()) != sdk.AddrLen {
|
||||
return fmt.Errorf("%s deputy address invalid bytes length got %d, want %d", asset.Denom, len(asset.DeputyAddress.Bytes()), sdk.AddrLen)
|
||||
}
|
||||
|
||||
if asset.FixedFee.IsNegative() {
|
||||
return fmt.Errorf("asset %s cannot have a negative fixed fee %s", asset.Denom, asset.FixedFee)
|
||||
}
|
||||
|
||||
if asset.MinBlockLock > asset.MaxBlockLock {
|
||||
return fmt.Errorf("asset %s has minimum block lock > maximum block lock %d > %d", asset.Denom, asset.MinBlockLock, asset.MaxBlockLock)
|
||||
}
|
||||
|
||||
if !asset.MinSwapAmount.IsPositive() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive minimum swap amount, got %s", asset.Denom, asset.MinSwapAmount))
|
||||
}
|
||||
|
||||
if !asset.MaxSwapAmount.IsPositive() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive maximum swap amount, got %s", asset.Denom, asset.MaxSwapAmount))
|
||||
}
|
||||
|
||||
if asset.MinSwapAmount.GT(asset.MaxSwapAmount) {
|
||||
return fmt.Errorf("asset %s has minimum swap amount > maximum swap amount %s > %s", asset.Denom, asset.MinSwapAmount, asset.MaxSwapAmount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 AssetSupply `json:"supply_limit" yaml:"supply_limit"` // asset supply limit
|
||||
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:"incoming_swap_fixed_fee" yaml:"incoming_swap_fixed_fee"` // the fixed fee charged by the relayer process for incoming swaps
|
||||
@ -30,17 +153,93 @@ type AssetParam struct {
|
||||
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
|
||||
}
|
||||
|
||||
// AssetParams array of AssetParam
|
||||
type AssetParams []AssetParam
|
||||
|
||||
// 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"`
|
||||
SupplyLimit sdk.Coin `json:"supply_limit" yaml:"supply_limit"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// NewAssetSupply initializes a new AssetSupply
|
||||
func NewAssetSupply(incomingSupply, outgoingSupply, currentSupply, timeLimitedSupply sdk.Coin, timeElapsed time.Duration) AssetSupply {
|
||||
return AssetSupply{
|
||||
IncomingSupply: incomingSupply,
|
||||
OutgoingSupply: outgoingSupply,
|
||||
CurrentSupply: currentSupply,
|
||||
TimeLimitedCurrentSupply: timeLimitedSupply,
|
||||
TimeElapsed: timeElapsed,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs a basic validation of an asset supply fields.
|
||||
func (a AssetSupply) Validate() error {
|
||||
if !a.IncomingSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "incoming supply %s", a.IncomingSupply)
|
||||
}
|
||||
if !a.OutgoingSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "outgoing supply %s", a.OutgoingSupply)
|
||||
}
|
||||
if !a.CurrentSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "current supply %s", a.CurrentSupply)
|
||||
}
|
||||
if !a.TimeLimitedCurrentSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "time-limited current supply %s", a.CurrentSupply)
|
||||
}
|
||||
denom := a.CurrentSupply.Denom
|
||||
if (a.IncomingSupply.Denom != denom) ||
|
||||
(a.OutgoingSupply.Denom != denom) ||
|
||||
(a.TimeLimitedCurrentSupply.Denom != denom) {
|
||||
return fmt.Errorf("asset supply denoms do not match %s %s %s %s", a.CurrentSupply.Denom, a.IncomingSupply.Denom, a.OutgoingSupply.Denom, a.TimeLimitedCurrentSupply.Denom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal returns if two asset supplies are equal
|
||||
func (a AssetSupply) Equal(b AssetSupply) bool {
|
||||
if a.GetDenom() != b.GetDenom() {
|
||||
return false
|
||||
}
|
||||
return (a.IncomingSupply.IsEqual(b.IncomingSupply) &&
|
||||
a.CurrentSupply.IsEqual(b.CurrentSupply) &&
|
||||
a.OutgoingSupply.IsEqual(b.OutgoingSupply) &&
|
||||
a.TimeLimitedCurrentSupply.IsEqual(b.TimeLimitedCurrentSupply) &&
|
||||
a.TimeElapsed == b.TimeElapsed)
|
||||
}
|
||||
|
||||
// String implements stringer
|
||||
func (a AssetSupply) String() string {
|
||||
return fmt.Sprintf(`
|
||||
asset supply:
|
||||
Incoming supply: %s
|
||||
Outgoing supply: %s
|
||||
Current supply: %s
|
||||
Time-limited current cupply: %s
|
||||
Time elapsed: %s
|
||||
`,
|
||||
a.IncomingSupply, a.OutgoingSupply, a.CurrentSupply, a.TimeLimitedCurrentSupply, a.TimeElapsed)
|
||||
}
|
||||
|
||||
// GetDenom getter method for the denom of the asset supply
|
||||
func (a AssetSupply) GetDenom() string {
|
||||
return a.CurrentSupply.Denom
|
||||
}
|
||||
|
||||
// AssetSupplies is a slice of AssetSupply
|
||||
type AssetSupplies []AssetSupply
|
||||
|
||||
// AtomicSwap contains the information for an atomic swap
|
||||
type AtomicSwap struct {
|
||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||
@ -57,6 +256,73 @@ type AtomicSwap struct {
|
||||
Direction SwapDirection `json:"direction" yaml:"direction"`
|
||||
}
|
||||
|
||||
// CalculateSwapID calculates the hash of a RandomNumberHash, sdk.AccAddress, and string
|
||||
func CalculateSwapID(randomNumberHash []byte, sender sdk.AccAddress, senderOtherChain string) []byte {
|
||||
senderOtherChain = strings.ToLower(senderOtherChain)
|
||||
data := randomNumberHash
|
||||
data = append(data, sender.Bytes()...)
|
||||
data = append(data, []byte(senderOtherChain)...)
|
||||
return tmhash.Sum(data)
|
||||
}
|
||||
|
||||
// GetSwapID calculates the ID of an atomic swap
|
||||
func (a AtomicSwap) GetSwapID() tmbytes.HexBytes {
|
||||
return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain)
|
||||
}
|
||||
|
||||
// GetCoins returns the swap's amount as sdk.Coins
|
||||
func (a AtomicSwap) GetCoins() sdk.Coins {
|
||||
return sdk.NewCoins(a.Amount...)
|
||||
}
|
||||
|
||||
// Validate performs a basic validation of an atomic swap fields.
|
||||
func (a AtomicSwap) Validate() error {
|
||||
if !a.Amount.IsValid() {
|
||||
return fmt.Errorf("invalid amount: %s", a.Amount)
|
||||
}
|
||||
if !a.Amount.IsAllPositive() {
|
||||
return fmt.Errorf("the swapped out coin must be positive: %s", a.Amount)
|
||||
}
|
||||
if len(a.RandomNumberHash) != RandomNumberHashLength {
|
||||
return fmt.Errorf("the length of random number hash should be %d", RandomNumberHashLength)
|
||||
}
|
||||
if a.ExpireHeight == 0 {
|
||||
return errors.New("expire height cannot be 0")
|
||||
}
|
||||
if a.Timestamp == 0 {
|
||||
return errors.New("timestamp cannot be 0")
|
||||
}
|
||||
if a.Sender.Empty() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender cannot be empty")
|
||||
}
|
||||
if a.Recipient.Empty() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient cannot be empty")
|
||||
}
|
||||
if len(a.Sender) != AddrByteCount {
|
||||
return fmt.Errorf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Sender))
|
||||
}
|
||||
if len(a.Recipient) != AddrByteCount {
|
||||
return fmt.Errorf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Recipient))
|
||||
}
|
||||
// NOTE: These adresses may not have a bech32 prefix.
|
||||
if strings.TrimSpace(a.SenderOtherChain) == "" {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender other chain cannot be blank")
|
||||
}
|
||||
if strings.TrimSpace(a.RecipientOtherChain) == "" {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient other chain cannot be blank")
|
||||
}
|
||||
if a.Status == Completed && a.ClosedBlock == 0 {
|
||||
return errors.New("closed block cannot be 0")
|
||||
}
|
||||
if a.Status == NULL || a.Status > 3 {
|
||||
return errors.New("invalid swap status")
|
||||
}
|
||||
if a.Direction == INVALID || a.Direction > 2 {
|
||||
return errors.New("invalid swap direction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AtomicSwaps is a slice of AtomicSwap
|
||||
type AtomicSwaps []AtomicSwap
|
||||
|
||||
@ -71,6 +337,50 @@ const (
|
||||
Expired SwapStatus = 0x03
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
// SwapDirection is the direction of an AtomicSwap
|
||||
type SwapDirection byte
|
||||
|
||||
@ -79,3 +389,52 @@ const (
|
||||
Incoming SwapDirection = 0x01
|
||||
Outgoing SwapDirection = 0x02
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsValid returns true if the swap direction is valid and false otherwise.
|
||||
func (direction SwapDirection) IsValid() bool {
|
||||
if direction == Incoming ||
|
||||
direction == Outgoing {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package v0_9
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||
)
|
||||
@ -141,6 +143,50 @@ const (
|
||||
Expired SwapStatus = 0x03
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
// SwapDirection is the direction of an AtomicSwap
|
||||
type SwapDirection byte
|
||||
|
||||
@ -149,3 +195,52 @@ const (
|
||||
Incoming SwapDirection = 0x01
|
||||
Outgoing SwapDirection = 0x02
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsValid returns true if the swap direction is valid and false otherwise.
|
||||
func (direction SwapDirection) IsValid() bool {
|
||||
if direction == Incoming ||
|
||||
direction == Outgoing {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package simulation
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
@ -31,6 +32,11 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &supplyA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &supplyB)
|
||||
return fmt.Sprintf("%s\n%s", supplyA, supplyB)
|
||||
case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey):
|
||||
var timeA, timeB time.Time
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)
|
||||
return fmt.Sprintf("%s\n%s", timeA, timeB)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
||||
|
@ -3,6 +3,7 @@ package simulation
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -22,12 +23,13 @@ func makeTestCodec() (cdc *codec.Codec) {
|
||||
return
|
||||
}
|
||||
|
||||
func TestDecodeDistributionStore(t *testing.T) {
|
||||
func TestDecodeBep3Store(t *testing.T) {
|
||||
cdc := makeTestCodec()
|
||||
prevBlockTime := time.Now().UTC()
|
||||
|
||||
oneCoin := sdk.NewCoin("coin", sdk.OneInt())
|
||||
swap := types.NewAtomicSwap(sdk.Coins{oneCoin}, nil, 10, 100, nil, nil, "otherChainSender", "otherChainRec", 200, types.Completed, true, types.Outgoing)
|
||||
supply := types.AssetSupply{IncomingSupply: oneCoin, OutgoingSupply: oneCoin, CurrentSupply: oneCoin}
|
||||
supply := types.AssetSupply{IncomingSupply: oneCoin, OutgoingSupply: oneCoin, CurrentSupply: oneCoin, TimeLimitedCurrentSupply: oneCoin, TimeElapsed: time.Duration(0)}
|
||||
bz := tmbytes.HexBytes([]byte{1, 2})
|
||||
|
||||
kvPairs := kv.Pairs{
|
||||
@ -35,6 +37,7 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
kv.Pair{Key: types.AssetSupplyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)},
|
||||
kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz},
|
||||
kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz},
|
||||
kv.Pair{Key: types.PreviousBlockTimeKey, Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)},
|
||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||
}
|
||||
|
||||
@ -46,6 +49,7 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
{"AssetSupply", fmt.Sprintf("%v\n%v", supply, supply)},
|
||||
{"AtomicSwapByBlock", fmt.Sprintf("%s\n%s", bz, bz)},
|
||||
{"AtomicSwapLongtermStorage", fmt.Sprintf("%s\n%s", bz, bz)},
|
||||
{"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)},
|
||||
{"other", ""},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -61,7 +62,7 @@ func GenSupplyLimit(r *rand.Rand, max int) sdk.Int {
|
||||
func GenAssetSupply(r *rand.Rand, denom string) types.AssetSupply {
|
||||
return types.NewAssetSupply(
|
||||
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()),
|
||||
sdk.NewCoin(denom, sdk.ZeroInt()))
|
||||
sdk.NewCoin(denom, sdk.ZeroInt()), sdk.NewCoin(denom, sdk.ZeroInt()), time.Duration(0))
|
||||
}
|
||||
|
||||
// GenMinBlockLock randomized MinBlockLock
|
||||
@ -97,10 +98,22 @@ func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam {
|
||||
|
||||
minSwapAmount := GenMinSwapAmount(r)
|
||||
minBlockLock := GenMinBlockLock(r)
|
||||
timeLimited := r.Float32() < 0.5
|
||||
timeBasedLimit := sdk.ZeroInt()
|
||||
if timeLimited {
|
||||
// set time-based limit to between 10 and 25% of the total limit
|
||||
min := int(limit.Quo(sdk.NewInt(10)).Int64())
|
||||
max := int(limit.Quo(sdk.NewInt(4)).Int64())
|
||||
timeBasedLimit = sdk.NewInt(int64(simulation.RandIntBetween(r, min, max)))
|
||||
}
|
||||
return types.AssetParam{
|
||||
Denom: denom,
|
||||
CoinID: int(coinID.Int64()),
|
||||
SupplyLimit: limit,
|
||||
Denom: denom,
|
||||
CoinID: int(coinID.Int64()),
|
||||
SupplyLimit: types.SupplyLimit{
|
||||
Limit: limit,
|
||||
TimeLimited: timeLimited,
|
||||
TimePeriod: time.Hour * 24,
|
||||
TimeBasedLimit: timeBasedLimit},
|
||||
Active: true,
|
||||
DeputyAddress: GenRandBnbDeputy(r).Address,
|
||||
FixedFee: GenRandFixedFee(r),
|
||||
@ -145,7 +158,8 @@ func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState
|
||||
Params: types.Params{
|
||||
AssetParams: supportedAssets,
|
||||
},
|
||||
Supplies: supplies,
|
||||
Supplies: supplies,
|
||||
PreviousBlockTime: types.DefaultPreviousBlockTime,
|
||||
}
|
||||
|
||||
return bep3Genesis
|
||||
@ -161,7 +175,7 @@ func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.Genesi
|
||||
if !found {
|
||||
panic("deputy address not found in available accounts")
|
||||
}
|
||||
assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.SupplyLimit))
|
||||
assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.SupplyLimit.Limit))
|
||||
if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin...)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -127,15 +127,27 @@ func SimulateMsgCreateAtomicSwap(ak types.AccountKeeper, k keeper.Keeper) simula
|
||||
|
||||
// Get maximum valid amount
|
||||
maximumAmount := senderAcc.SpendableCoins(ctx.BlockTime()).Sub(fees).AmountOf(asset.Denom)
|
||||
assetSupply, foundAssetSupply := k.GetAssetSupply(ctx, asset.Denom)
|
||||
if !foundAssetSupply {
|
||||
return noOpMsg, nil, fmt.Errorf("no asset supply found for %s", asset.Denom)
|
||||
}
|
||||
// The maximum amount for outgoing swaps is limited by the asset's current supply
|
||||
if recipient.Address.Equals(asset.DeputyAddress) {
|
||||
assetSupply, foundAssetSupply := k.GetAssetSupply(ctx, asset.Denom)
|
||||
if !foundAssetSupply {
|
||||
return noOpMsg, nil, fmt.Errorf("no asset supply found for %s", asset.Denom)
|
||||
}
|
||||
|
||||
if maximumAmount.GT(assetSupply.CurrentSupply.Amount.Sub(assetSupply.OutgoingSupply.Amount)) {
|
||||
maximumAmount = assetSupply.CurrentSupply.Amount.Sub(assetSupply.OutgoingSupply.Amount)
|
||||
}
|
||||
} else {
|
||||
// the maximum amount for incoming swaps in limited by the asset's incoming supply + current supply (rate-limited if applicable) + swap amount being less than the supply limit
|
||||
var currentRemainingSupply sdk.Int
|
||||
if asset.SupplyLimit.TimeLimited {
|
||||
currentRemainingSupply = asset.SupplyLimit.Limit.Sub(assetSupply.IncomingSupply.Amount).Sub(assetSupply.TimeLimitedCurrentSupply.Amount)
|
||||
} else {
|
||||
currentRemainingSupply = asset.SupplyLimit.Limit.Sub(assetSupply.IncomingSupply.Amount).Sub(assetSupply.CurrentSupply.Amount)
|
||||
}
|
||||
if currentRemainingSupply.LT(maximumAmount) {
|
||||
maximumAmount = currentRemainingSupply
|
||||
}
|
||||
}
|
||||
|
||||
// The maximum amount for all swaps is limited by the total max limit
|
||||
@ -147,7 +159,7 @@ func SimulateMsgCreateAtomicSwap(ak types.AccountKeeper, k keeper.Keeper) simula
|
||||
amount := maximumAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000))))
|
||||
minAmountPlusFee := asset.MinSwapAmount.Add(asset.FixedFee)
|
||||
if amount.LT(minAmountPlusFee) {
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (account funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil
|
||||
}
|
||||
coins := sdk.NewCoins(sdk.NewCoin(asset.Denom, amount))
|
||||
|
||||
@ -242,7 +254,7 @@ func operationClaimAtomicSwap(ak types.AccountKeeper, k keeper.Keeper, swapID []
|
||||
if !found {
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (could not claim - asset supply not found %s)", swap.Amount[0].Denom), "", false, nil), nil, nil
|
||||
}
|
||||
if asset.SupplyLimit.LT(supply.CurrentSupply.Amount.Add(swap.Amount[0].Amount)) {
|
||||
if asset.SupplyLimit.Limit.LT(supply.CurrentSupply.Amount.Add(swap.Amount[0].Amount)) {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, nil
|
||||
}
|
||||
|
||||
|
@ -43,4 +43,6 @@ var (
|
||||
ErrInvalidAmount = sdkerrors.Register(ModuleName, 18, "amount is outside acceptable range")
|
||||
// ErrInvalidSwapAccount error for when a swap involves an invalid account
|
||||
ErrInvalidSwapAccount = sdkerrors.Register(ModuleName, 19, "atomic swap has invalid account")
|
||||
// ErrExceedsTimeBasedSupplyLimit error for when the proposed supply increase would put the supply above limit for the current time period
|
||||
ErrExceedsTimeBasedSupplyLimit = sdkerrors.Register(ModuleName, 20, "asset supply over limit for current time period")
|
||||
)
|
||||
|
@ -13,6 +13,8 @@ type SupplyKeeper interface {
|
||||
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// AccountKeeper defines the expected account keeper (noalias)
|
||||
|
@ -4,21 +4,24 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// NewGenesisState creates a new GenesisState object
|
||||
func NewGenesisState(params Params, swaps AtomicSwaps, supplies AssetSupplies) GenesisState {
|
||||
func NewGenesisState(params Params, swaps AtomicSwaps, supplies AssetSupplies, previousBlockTime time.Time) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
AtomicSwaps: swaps,
|
||||
Supplies: supplies,
|
||||
Params: params,
|
||||
AtomicSwaps: swaps,
|
||||
Supplies: supplies,
|
||||
PreviousBlockTime: previousBlockTime,
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +31,7 @@ func DefaultGenesisState() GenesisState {
|
||||
DefaultParams(),
|
||||
AtomicSwaps{},
|
||||
AssetSupplies{},
|
||||
DefaultPreviousBlockTime,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -24,13 +25,15 @@ func (suite *GenesisTestSuite) SetupTest() {
|
||||
coin := sdk.NewCoin("kava", sdk.OneInt())
|
||||
suite.swaps = atomicSwaps(10)
|
||||
|
||||
supply := types.NewAssetSupply(coin, coin, coin)
|
||||
supply := types.NewAssetSupply(coin, coin, coin, coin, time.Duration(0))
|
||||
suite.supplies = types.AssetSupplies{supply}
|
||||
}
|
||||
|
||||
func (suite *GenesisTestSuite) TestValidate() {
|
||||
type args struct {
|
||||
swaps types.AtomicSwaps
|
||||
swaps types.AtomicSwaps
|
||||
supplies types.AssetSupplies
|
||||
previousBlockTime time.Time
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -40,31 +43,60 @@ func (suite *GenesisTestSuite) TestValidate() {
|
||||
{
|
||||
"default",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{},
|
||||
swaps: types.AtomicSwaps{},
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"with swaps",
|
||||
args{
|
||||
swaps: suite.swaps,
|
||||
swaps: suite.swaps,
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"with supplies",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{},
|
||||
supplies: suite.supplies,
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid supply",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{},
|
||||
supplies: types.AssetSupplies{types.AssetSupply{IncomingSupply: sdk.Coin{"Invalid", sdk.ZeroInt()}}},
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplicate swaps",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{suite.swaps[2], suite.swaps[2]},
|
||||
swaps: types.AtomicSwaps{suite.swaps[2], suite.swaps[2]},
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid swap",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{types.AtomicSwap{Amount: sdk.Coins{sdk.Coin{Denom: "Invalid Denom", Amount: sdk.NewInt(-1)}}}},
|
||||
swaps: types.AtomicSwaps{types.AtomicSwap{Amount: sdk.Coins{sdk.Coin{Denom: "Invalid Denom", Amount: sdk.NewInt(-1)}}}},
|
||||
previousBlockTime: types.DefaultPreviousBlockTime,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"blocktime not set",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -73,7 +105,7 @@ func (suite *GenesisTestSuite) TestValidate() {
|
||||
if tc.name == "default" {
|
||||
gs = types.DefaultGenesisState()
|
||||
} else {
|
||||
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, suite.supplies)
|
||||
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, tc.args.supplies, tc.args.previousBlockTime)
|
||||
}
|
||||
|
||||
err := gs.Validate()
|
||||
|
@ -30,6 +30,7 @@ var (
|
||||
AtomicSwapByBlockPrefix = []byte{0x01} // prefix for keys of the AtomicSwapsByBlock index
|
||||
AtomicSwapLongtermStoragePrefix = []byte{0x02} // prefix for keys of the AtomicSwapLongtermStorage index
|
||||
AssetSupplyPrefix = []byte{0x03}
|
||||
PreviousBlockTimeKey = []byte{0x04}
|
||||
)
|
||||
|
||||
// GetAtomicSwapByHeightKey is used by the AtomicSwapByBlock index and AtomicSwapLongtermStorage index
|
||||
|
@ -2,9 +2,16 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
bech32MainPrefix = "kava"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
@ -16,6 +23,7 @@ var (
|
||||
DefaultMaxAmount sdk.Int = sdk.NewInt(1000000000000) // 10,000 BNB
|
||||
DefaultMinBlockLock uint64 = 220
|
||||
DefaultMaxBlockLock uint64 = 270
|
||||
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
)
|
||||
|
||||
// Params governance parameters for bep3 module
|
||||
@ -47,7 +55,7 @@ func DefaultParams() Params {
|
||||
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 sdk.Int `json:"supply_limit" yaml:"supply_limit"` // asset supply limit
|
||||
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
|
||||
@ -59,7 +67,7 @@ type AssetParam struct {
|
||||
|
||||
// NewAssetParam returns a new AssetParam
|
||||
func NewAssetParam(
|
||||
denom string, coinID int, limit sdk.Int, active bool,
|
||||
denom string, coinID int, limit SupplyLimit, active bool,
|
||||
deputyAddr sdk.AccAddress, fixedFee sdk.Int, minSwapAmount sdk.Int,
|
||||
maxSwapAmount sdk.Int, minBlockLock uint64, maxBlockLock uint64,
|
||||
) AssetParam {
|
||||
@ -106,6 +114,28 @@ func (aps AssetParams) String() string {
|
||||
return out
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (sl SupplyLimit) String() string {
|
||||
return fmt.Sprintf(`%s
|
||||
%t
|
||||
%s
|
||||
%s
|
||||
`, sl.Limit, sl.TimeLimited, sl.TimePeriod, sl.TimeBasedLimit)
|
||||
}
|
||||
|
||||
// Equals returns true if two supply limits are equal
|
||||
func (sl SupplyLimit) Equals(sl2 SupplyLimit) bool {
|
||||
return sl.Limit.Equal(sl2.Limit) && sl.TimeLimited == sl2.TimeLimited && sl.TimePeriod == sl2.TimePeriod && sl.TimeBasedLimit.Equal(sl2.TimeBasedLimit)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
func ParamKeyTable() params.KeyTable {
|
||||
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||
@ -134,20 +164,28 @@ func validateAssetParams(i interface{}) error {
|
||||
coinDenoms := make(map[string]bool)
|
||||
for _, asset := range assetParams {
|
||||
if err := sdk.ValidateDenom(asset.Denom); err != nil {
|
||||
return fmt.Errorf("asset denom invalid: %s", asset.Denom)
|
||||
return fmt.Errorf(fmt.Sprintf("asset denom invalid: %s", asset.Denom))
|
||||
}
|
||||
|
||||
if asset.CoinID < 0 {
|
||||
return fmt.Errorf("asset %s coin id must be a non negative integer", asset.Denom)
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s coin id must be a non negative integer", asset.Denom))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.IsNegative() {
|
||||
return fmt.Errorf("asset %s has invalid (negative) supply limit: %s", asset.Denom, asset.SupplyLimit)
|
||||
if asset.SupplyLimit.Limit.IsNegative() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply limit: %s", asset.Denom, asset.SupplyLimit.Limit))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.TimeBasedLimit.IsNegative() {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply time limit: %s", asset.Denom, asset.SupplyLimit.TimeBasedLimit))
|
||||
}
|
||||
|
||||
if asset.SupplyLimit.TimeBasedLimit.GT(asset.SupplyLimit.Limit) {
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s cannot have supply time limit > supply limit: %s>%s", asset.Denom, asset.SupplyLimit.TimeBasedLimit, asset.SupplyLimit.Limit))
|
||||
}
|
||||
|
||||
_, found := coinDenoms[asset.Denom]
|
||||
if found {
|
||||
return fmt.Errorf("asset %s cannot have duplicate denom", asset.Denom)
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom))
|
||||
}
|
||||
|
||||
coinDenoms[asset.Denom] = true
|
||||
@ -169,11 +207,11 @@ func validateAssetParams(i interface{}) error {
|
||||
}
|
||||
|
||||
if !asset.MinSwapAmount.IsPositive() {
|
||||
return fmt.Errorf("asset %s must have a positive minimum swap amount, got %s", asset.Denom, asset.MinSwapAmount)
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive minimum swap amount, got %s", asset.Denom, asset.MinSwapAmount))
|
||||
}
|
||||
|
||||
if !asset.MaxSwapAmount.IsPositive() {
|
||||
return fmt.Errorf("asset %s must have a positive maximum swap amount, got %s", asset.Denom, asset.MaxSwapAmount)
|
||||
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive maximum swap amount, got %s", asset.Denom, asset.MaxSwapAmount))
|
||||
}
|
||||
|
||||
if asset.MinSwapAmount.GT(asset.MaxSwapAmount) {
|
||||
|
@ -2,6 +2,7 @@ package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -14,7 +15,7 @@ import (
|
||||
type ParamsTestSuite struct {
|
||||
suite.Suite
|
||||
addr sdk.AccAddress
|
||||
supply []sdk.Int
|
||||
supply []types.SupplyLimit
|
||||
}
|
||||
|
||||
func (suite *ParamsTestSuite) SetupTest() {
|
||||
@ -22,7 +23,19 @@ func (suite *ParamsTestSuite) SetupTest() {
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
suite.addr = addrs[0]
|
||||
suite.supply = append(suite.supply, sdk.NewInt(10000000000000), sdk.NewInt(10000000000000))
|
||||
supply1 := types.SupplyLimit{
|
||||
Limit: sdk.NewInt(10000000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
}
|
||||
supply2 := types.SupplyLimit{
|
||||
Limit: sdk.NewInt(10000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(100000000000),
|
||||
TimePeriod: time.Hour * 24,
|
||||
}
|
||||
suite.supply = append(suite.supply, supply1, supply2)
|
||||
return
|
||||
}
|
||||
|
||||
@ -57,6 +70,17 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
expectPass: true,
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "valid single asset time limited",
|
||||
args: args{
|
||||
assetParams: types.AssetParams{types.NewAssetParam(
|
||||
"bnb", 714, suite.supply[1], true,
|
||||
suite.addr, sdk.NewInt(1000), sdk.NewInt(100000000), sdk.NewInt(100000000000),
|
||||
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||
},
|
||||
expectPass: true,
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "valid multi asset",
|
||||
args: args{
|
||||
@ -166,13 +190,38 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
args: args{
|
||||
assetParams: types.AssetParams{types.NewAssetParam(
|
||||
"bnb", 714,
|
||||
sdk.NewInt(-10000000000000), true,
|
||||
types.SupplyLimit{sdk.NewInt(-10000000000000), false, time.Hour, sdk.ZeroInt()}, true,
|
||||
suite.addr, sdk.NewInt(1000), sdk.NewInt(100000000), sdk.NewInt(100000000000),
|
||||
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||
},
|
||||
expectPass: false,
|
||||
expectedErr: "invalid (negative) supply limit",
|
||||
},
|
||||
{
|
||||
name: "negative asset time limit",
|
||||
args: args{
|
||||
assetParams: types.AssetParams{types.NewAssetParam(
|
||||
"bnb", 714,
|
||||
types.SupplyLimit{sdk.NewInt(10000000000000), false, time.Hour, sdk.NewInt(-10000000000000)}, true,
|
||||
suite.addr, sdk.NewInt(1000), sdk.NewInt(100000000), sdk.NewInt(100000000000),
|
||||
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||
},
|
||||
expectPass: false,
|
||||
expectedErr: "invalid (negative) supply time limit",
|
||||
},
|
||||
{
|
||||
name: "asset time limit greater than overall limit",
|
||||
args: args{
|
||||
assetParams: types.AssetParams{types.NewAssetParam(
|
||||
"bnb", 714,
|
||||
types.SupplyLimit{sdk.NewInt(10000000000000), true, time.Hour, sdk.NewInt(100000000000000)},
|
||||
true,
|
||||
suite.addr, sdk.NewInt(1000), sdk.NewInt(100000000), sdk.NewInt(100000000000),
|
||||
types.DefaultMinBlockLock, types.DefaultMaxBlockLock)},
|
||||
},
|
||||
expectPass: false,
|
||||
expectedErr: "supply time limit > supply limit",
|
||||
},
|
||||
{
|
||||
name: "duplicate denom",
|
||||
args: args{
|
||||
@ -201,7 +250,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
suite.Require().Error(err, tc.name)
|
||||
suite.Require().Contains(err.Error(), tc.expectedErr)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
@ -9,17 +10,21 @@ import (
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// NewAssetSupply initializes a new AssetSupply
|
||||
func NewAssetSupply(incomingSupply, outgoingSupply, currentSupply sdk.Coin) AssetSupply {
|
||||
func NewAssetSupply(incomingSupply, outgoingSupply, currentSupply, timeLimitedSupply sdk.Coin, timeElapsed time.Duration) AssetSupply {
|
||||
return AssetSupply{
|
||||
IncomingSupply: incomingSupply,
|
||||
OutgoingSupply: outgoingSupply,
|
||||
CurrentSupply: currentSupply,
|
||||
IncomingSupply: incomingSupply,
|
||||
OutgoingSupply: outgoingSupply,
|
||||
CurrentSupply: currentSupply,
|
||||
TimeLimitedCurrentSupply: timeLimitedSupply,
|
||||
TimeElapsed: timeElapsed,
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,19 +39,28 @@ func (a AssetSupply) Validate() error {
|
||||
if !a.CurrentSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "current supply %s", a.CurrentSupply)
|
||||
}
|
||||
if !a.TimeLimitedCurrentSupply.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "time-limited current supply %s", a.CurrentSupply)
|
||||
}
|
||||
denom := a.CurrentSupply.Denom
|
||||
if (a.IncomingSupply.Denom != denom) ||
|
||||
(a.OutgoingSupply.Denom != denom) {
|
||||
return fmt.Errorf("asset supply denoms do not match %s %s %s", a.CurrentSupply.Denom, a.IncomingSupply.Denom, a.OutgoingSupply.Denom)
|
||||
(a.OutgoingSupply.Denom != denom) ||
|
||||
(a.TimeLimitedCurrentSupply.Denom != denom) {
|
||||
return fmt.Errorf("asset supply denoms do not match %s %s %s %s", a.CurrentSupply.Denom, a.IncomingSupply.Denom, a.OutgoingSupply.Denom, a.TimeLimitedCurrentSupply.Denom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal returns if two asset supplies are equal
|
||||
func (a AssetSupply) Equal(b AssetSupply) bool {
|
||||
if a.GetDenom() != b.GetDenom() {
|
||||
return false
|
||||
}
|
||||
return (a.IncomingSupply.IsEqual(b.IncomingSupply) &&
|
||||
a.CurrentSupply.IsEqual(b.CurrentSupply) &&
|
||||
a.OutgoingSupply.IsEqual(b.OutgoingSupply))
|
||||
a.OutgoingSupply.IsEqual(b.OutgoingSupply) &&
|
||||
a.TimeLimitedCurrentSupply.IsEqual(b.TimeLimitedCurrentSupply) &&
|
||||
a.TimeElapsed == b.TimeElapsed)
|
||||
}
|
||||
|
||||
// String implements stringer
|
||||
@ -56,8 +70,10 @@ func (a AssetSupply) String() string {
|
||||
Incoming supply: %s
|
||||
Outgoing supply: %s
|
||||
Current supply: %s
|
||||
Time-limited current cupply: %s
|
||||
Time elapsed: %s
|
||||
`,
|
||||
a.IncomingSupply, a.OutgoingSupply, a.CurrentSupply)
|
||||
a.IncomingSupply, a.OutgoingSupply, a.CurrentSupply, a.TimeLimitedCurrentSupply, a.TimeElapsed)
|
||||
}
|
||||
|
||||
// GetDenom getter method for the denom of the asset supply
|
||||
|
@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -17,7 +18,7 @@ func TestAssetSupplyValidate(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
msg: "valid asset",
|
||||
asset: NewAssetSupply(coin, coin, coin),
|
||||
asset: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
expPass: true,
|
||||
},
|
||||
{
|
||||
@ -42,6 +43,27 @@ func TestAssetSupplyValidate(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid time limitedcurrent supply",
|
||||
AssetSupply{
|
||||
IncomingSupply: coin,
|
||||
OutgoingSupply: coin,
|
||||
CurrentSupply: coin,
|
||||
TimeLimitedCurrentSupply: invalidCoin,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"non matching denoms",
|
||||
AssetSupply{
|
||||
IncomingSupply: coin,
|
||||
OutgoingSupply: coin,
|
||||
CurrentSupply: coin,
|
||||
TimeLimitedCurrentSupply: sdk.NewCoin("lol", sdk.ZeroInt()),
|
||||
TimeElapsed: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -53,3 +75,47 @@ func TestAssetSupplyValidate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetSupplyEquality(t *testing.T) {
|
||||
coin := sdk.NewCoin("test", sdk.OneInt())
|
||||
coin2 := sdk.NewCoin("other", sdk.OneInt())
|
||||
testCases := []struct {
|
||||
name string
|
||||
asset1 AssetSupply
|
||||
asset2 AssetSupply
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "equal",
|
||||
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
asset2: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
expPass: true,
|
||||
},
|
||||
{
|
||||
name: "not equal duration",
|
||||
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
asset2: NewAssetSupply(coin, coin, coin, coin, time.Duration(1)),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "not equal coin amount",
|
||||
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
asset2: NewAssetSupply(sdk.NewCoin("test", sdk.ZeroInt()), coin, coin, coin, time.Duration(1)),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "not equal coin denom",
|
||||
asset1: NewAssetSupply(coin, coin, coin, coin, time.Duration(0)),
|
||||
asset2: NewAssetSupply(coin2, coin2, coin2, coin2, time.Duration(1)),
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if tc.expPass {
|
||||
require.True(t, tc.asset1.Equal(tc.asset2), tc.name)
|
||||
} else {
|
||||
require.False(t, tc.asset1.Equal(tc.asset2), tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -83,9 +84,14 @@ func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
|
||||
// bep3 Asset Params
|
||||
testAPs := bep3types.AssetParams{
|
||||
bep3types.AssetParam{
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: sdk.NewInt(350000000000000),
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: false,
|
||||
TimeBasedLimit: sdk.ZeroInt(),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: testDeputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -95,9 +101,14 @@ func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
|
||||
MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||
},
|
||||
bep3types.AssetParam{
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: sdk.NewInt(100),
|
||||
Denom: "inc",
|
||||
CoinID: 9999,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(100000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: false,
|
||||
DeputyAddress: testDeputy,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
@ -58,7 +59,7 @@ func RandomizedGenState(simState *module.SimulationState) {
|
||||
[]types.Proposal{},
|
||||
[]types.Vote{},
|
||||
)
|
||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, []byte{})
|
||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState))
|
||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
@ -159,9 +161,14 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
deputyAddress := sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1")))
|
||||
testAPs := bep3types.AssetParams{
|
||||
bep3types.AssetParam{
|
||||
Denom: "btc",
|
||||
CoinID: 0,
|
||||
SupplyLimit: sdk.NewInt(100),
|
||||
Denom: "btc",
|
||||
CoinID: 0,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(100),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: false,
|
||||
DeputyAddress: deputyAddress,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -171,9 +178,14 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||
},
|
||||
bep3types.AssetParam{
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: sdk.NewInt(350000000000000),
|
||||
Denom: "bnb",
|
||||
CoinID: 714,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputyAddress,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -183,9 +195,14 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
MaxBlockLock: bep3types.DefaultMaxBlockLock,
|
||||
},
|
||||
bep3types.AssetParam{
|
||||
Denom: "xrp",
|
||||
CoinID: 414,
|
||||
SupplyLimit: sdk.NewInt(350000000000000),
|
||||
Denom: "xrp",
|
||||
CoinID: 414,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: deputyAddress,
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -200,10 +217,12 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
updatedTestAPs[1] = testAPs[0]
|
||||
updatedTestAPs[2] = testAPs[2]
|
||||
|
||||
updatedTestAPs[0].SupplyLimit = i(1000) // btc
|
||||
updatedTestAPs[1].Active = false // bnb
|
||||
updatedTestAPs[2].SupplyLimit = i(1000) // xrp
|
||||
updatedTestAPs[2].Active = false // xrp
|
||||
updatedTestAPs[0].SupplyLimit.Limit = i(1000) // btc
|
||||
updatedTestAPs[1].Active = false // bnb
|
||||
updatedTestAPs[2].SupplyLimit.Limit = i(1000) // xrp
|
||||
updatedTestAPs[2].Active = false // xrp
|
||||
updatedTestAPs[2].MinBlockLock = uint64(210) // xrp
|
||||
updatedTestAPs[2].MaxSwapAmount = sdk.NewInt(10000000000000)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
@ -224,10 +243,12 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
Limit: true,
|
||||
},
|
||||
{ // allow all fields
|
||||
Denom: "xrp",
|
||||
CoinID: true,
|
||||
Limit: true,
|
||||
Active: true,
|
||||
Denom: "xrp",
|
||||
CoinID: true,
|
||||
Limit: true,
|
||||
Active: true,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
},
|
||||
},
|
||||
current: testAPs[:2],
|
||||
@ -265,9 +286,11 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParams_Allows() {
|
||||
Limit: true,
|
||||
},
|
||||
{
|
||||
Denom: "xrp",
|
||||
Limit: true,
|
||||
Active: true,
|
||||
Denom: "xrp",
|
||||
Limit: true,
|
||||
Active: true,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
},
|
||||
},
|
||||
current: testAPs,
|
||||
@ -596,9 +619,14 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
|
||||
func (suite *PermissionsTestSuite) TestAllowedAssetParam_Allows() {
|
||||
testAP := bep3types.AssetParam{
|
||||
Denom: "usdx",
|
||||
CoinID: 999,
|
||||
SupplyLimit: sdk.NewInt(1000000000),
|
||||
Denom: "usdx",
|
||||
CoinID: 999,
|
||||
SupplyLimit: bep3types.SupplyLimit{
|
||||
Limit: sdk.NewInt(350000000000000),
|
||||
TimeLimited: true,
|
||||
TimeBasedLimit: sdk.NewInt(50000000000),
|
||||
TimePeriod: time.Hour,
|
||||
},
|
||||
Active: true,
|
||||
DeputyAddress: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))),
|
||||
FixedFee: sdk.NewInt(1000),
|
||||
@ -611,11 +639,11 @@ func (suite *PermissionsTestSuite) TestAllowedAssetParam_Allows() {
|
||||
newCoinidAP.CoinID = 0
|
||||
|
||||
newLimitAP := testAP
|
||||
newLimitAP.SupplyLimit = i(1000)
|
||||
newLimitAP.SupplyLimit.Limit = i(1000)
|
||||
|
||||
newCoinidAndLimitAP := testAP
|
||||
newCoinidAndLimitAP.CoinID = 0
|
||||
newCoinidAndLimitAP.SupplyLimit = i(1000)
|
||||
newCoinidAndLimitAP.SupplyLimit.Limit = i(1000)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
|
@ -465,19 +465,25 @@ func (aaps AllowedAssetParams) Allows(current, incoming bep3types.AssetParams) b
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
// AllowedAssetParam bep3 asset parameters that can be changed by committee
|
||||
type AllowedAssetParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
CoinID bool `json:"coin_id" yaml:"coin_id"`
|
||||
Limit bool `json:"limit" yaml:"limit"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
CoinID bool `json:"coin_id" yaml:"coin_id"`
|
||||
Limit bool `json:"limit" yaml:"limit"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
MaxSwapAmount bool `json:"max_swap_amount" yaml:"max_swap_amount"`
|
||||
MinBlockLock bool `json:"min_block_lock" yaml:"min_block_lock"`
|
||||
}
|
||||
|
||||
// Allows bep3 AssetParam parameters than can be changed by committee
|
||||
func (aap AllowedAssetParam) Allows(current, incoming bep3types.AssetParam) bool {
|
||||
|
||||
allowed := ((aap.Denom == current.Denom) && (aap.Denom == incoming.Denom)) && // require denoms to be all equal
|
||||
((current.CoinID == incoming.CoinID) || aap.CoinID) &&
|
||||
(current.SupplyLimit.Equal(incoming.SupplyLimit) || aap.Limit) &&
|
||||
((current.Active == incoming.Active) || aap.Active)
|
||||
(current.SupplyLimit.Equals(incoming.SupplyLimit) || aap.Limit) &&
|
||||
((current.Active == incoming.Active) || aap.Active) &&
|
||||
((current.MaxSwapAmount.Equal(incoming.MaxSwapAmount)) || aap.MaxSwapAmount) &&
|
||||
((current.MinBlockLock == incoming.MinBlockLock) || aap.MinBlockLock)
|
||||
return allowed
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user