mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
Add optional rate limits to issuance (#627)
* add rate-limiting and optional blocklists * fix: check account is not nil * add tests for rate-limiting * update simulations * fix typos * remove unsued function arg
This commit is contained in:
parent
2a3192fa0e
commit
b356309d90
@ -74,6 +74,9 @@ func SimulateMsgSubmitProposal(cdc *codec.Codec, ak AccountKeeper, k keeper.Keep
|
|||||||
}
|
}
|
||||||
// pick a committee that has permissions for proposal
|
// pick a committee that has permissions for proposal
|
||||||
pp := types.PubProposal(contentSim(r, ctx, accs))
|
pp := types.PubProposal(contentSim(r, ctx, accs))
|
||||||
|
if pp == nil {
|
||||||
|
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation (conent generation function returned nil)", "", false, nil), nil, nil
|
||||||
|
}
|
||||||
var selectedCommittee types.Committee
|
var selectedCommittee types.Committee
|
||||||
var found bool
|
var found bool
|
||||||
for _, c := range committees {
|
for _, c := range committees {
|
||||||
|
@ -7,11 +7,10 @@ import (
|
|||||||
|
|
||||||
// BeginBlocker iterates over each asset and seizes coins from blocked addresses by returning them to the asset owner
|
// BeginBlocker iterates over each asset and seizes coins from blocked addresses by returning them to the asset owner
|
||||||
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
params := k.GetParams(ctx)
|
err := k.SeizeCoinsForBlockableAssets(ctx)
|
||||||
for _, asset := range params.Assets {
|
if err != nil {
|
||||||
err := k.SeizeCoinsFromBlockedAddresses(ctx, asset.Denom)
|
panic(err)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
k.SynchronizeBlockList(ctx)
|
||||||
|
k.UpdateTimeBasedSupplyLimits(ctx)
|
||||||
}
|
}
|
||||||
|
111
x/issuance/abci_test.go
Normal file
111
x/issuance/abci_test.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package issuance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
"github.com/kava-labs/kava/x/issuance"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test suite used for all keeper tests
|
||||||
|
type ABCITestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper issuance.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
modAccount sdk.AccAddress
|
||||||
|
blockTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default state used by each test
|
||||||
|
func (suite *ABCITestSuite) SetupTest() {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
blockTime := tmtime.Now()
|
||||||
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: blockTime})
|
||||||
|
tApp.InitializeFromGenesisStates()
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
keeper := tApp.GetIssuanceKeeper()
|
||||||
|
modAccount, err := sdk.AccAddressFromBech32("kava1cj7njkw2g9fqx4e768zc75dp9sks8u9znxrf0w")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.app = tApp
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.keeper = keeper
|
||||||
|
suite.addrs = addrs
|
||||||
|
suite.modAccount = modAccount
|
||||||
|
suite.blockTime = blockTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ABCITestSuite) TestRateLimitingTimePassage() {
|
||||||
|
type args struct {
|
||||||
|
assets issuance.Assets
|
||||||
|
supplies issuance.AssetSupplies
|
||||||
|
blockTimes []time.Duration
|
||||||
|
expectedSupply issuance.AssetSupply
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"time passage same period",
|
||||||
|
args{
|
||||||
|
assets: issuance.Assets{
|
||||||
|
issuance.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, issuance.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: issuance.AssetSupplies{
|
||||||
|
issuance.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
blockTimes: []time.Duration{time.Hour},
|
||||||
|
expectedSupply: issuance.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour*2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time passage new period",
|
||||||
|
args{
|
||||||
|
assets: issuance.Assets{
|
||||||
|
issuance.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, issuance.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: issuance.AssetSupplies{
|
||||||
|
issuance.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
blockTimes: []time.Duration{time.Hour * 12, time.Hour * 12},
|
||||||
|
expectedSupply: issuance.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Duration(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
params := issuance.NewParams(tc.args.assets)
|
||||||
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
|
for _, supply := range tc.args.supplies {
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, supply, supply.GetDenom())
|
||||||
|
}
|
||||||
|
suite.keeper.SetPreviousBlockTime(suite.ctx, suite.blockTime)
|
||||||
|
for _, bt := range tc.args.blockTimes {
|
||||||
|
nextBlockTime := suite.ctx.BlockTime().Add(bt)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(nextBlockTime)
|
||||||
|
suite.Require().NotPanics(func() {
|
||||||
|
issuance.BeginBlocker(suite.ctx, suite.keeper)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
actualSupply, found := suite.keeper.GetAssetSupply(suite.ctx, tc.args.expectedSupply.GetDenom())
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(tc.args.expectedSupply, actualSupply)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABCITestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ABCITestSuite))
|
||||||
|
}
|
@ -51,6 +51,8 @@ var (
|
|||||||
DefaultParams = types.DefaultParams
|
DefaultParams = types.DefaultParams
|
||||||
ParamKeyTable = types.ParamKeyTable
|
ParamKeyTable = types.ParamKeyTable
|
||||||
NewAsset = types.NewAsset
|
NewAsset = types.NewAsset
|
||||||
|
NewRateLimit = types.NewRateLimit
|
||||||
|
NewAssetSupply = types.NewAssetSupply
|
||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
ModuleCdc = types.ModuleCdc
|
ModuleCdc = types.ModuleCdc
|
||||||
@ -61,6 +63,10 @@ var (
|
|||||||
ErrAccountAlreadyBlocked = types.ErrAccountAlreadyBlocked
|
ErrAccountAlreadyBlocked = types.ErrAccountAlreadyBlocked
|
||||||
ErrAccountAlreadyUnblocked = types.ErrAccountAlreadyUnblocked
|
ErrAccountAlreadyUnblocked = types.ErrAccountAlreadyUnblocked
|
||||||
ErrIssueToModuleAccount = types.ErrIssueToModuleAccount
|
ErrIssueToModuleAccount = types.ErrIssueToModuleAccount
|
||||||
|
ErrExceedsSupplyLimit = types.ErrExceedsSupplyLimit
|
||||||
|
ErrAssetUnblockable = types.ErrAssetUnblockable
|
||||||
|
AssetSupplyPrefix = types.AssetSupplyPrefix
|
||||||
|
PreviousBlockTimeKey = types.PreviousBlockTimeKey
|
||||||
KeyAssets = types.KeyAssets
|
KeyAssets = types.KeyAssets
|
||||||
DefaultAssets = types.DefaultAssets
|
DefaultAssets = types.DefaultAssets
|
||||||
ModuleAccountName = types.ModuleAccountName
|
ModuleAccountName = types.ModuleAccountName
|
||||||
@ -77,5 +83,8 @@ type (
|
|||||||
Params = types.Params
|
Params = types.Params
|
||||||
Asset = types.Asset
|
Asset = types.Asset
|
||||||
Assets = types.Assets
|
Assets = types.Assets
|
||||||
|
RateLimit = types.RateLimit
|
||||||
QueryAssetParams = types.QueryAssetParams
|
QueryAssetParams = types.QueryAssetParams
|
||||||
|
AssetSupply = types.AssetSupply
|
||||||
|
AssetSupplies = types.AssetSupplies
|
||||||
)
|
)
|
||||||
|
@ -160,7 +160,7 @@ func getCmdPauseAsset(cdc *codec.Codec) *cobra.Command {
|
|||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "set-pause-status [denom] [status]",
|
Use: "set-pause-status [denom] [status]",
|
||||||
Short: "pause or unpause an asset",
|
Short: "pause or unpause an asset",
|
||||||
Long: "The asset owner pauses or unpauses the input asset, halting new issuance and redemption",
|
Long: "The asset owner pauses or un-pauses the input asset, halting new issuance and redemption",
|
||||||
Example: fmt.Sprintf(`$ %s tx %s pause usdtoken true
|
Example: fmt.Sprintf(`$ %s tx %s pause usdtoken true
|
||||||
`, version.ClientName, types.ModuleName),
|
`, version.ClientName, types.ModuleName),
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
|
@ -12,17 +12,27 @@ import (
|
|||||||
// InitGenesis initializes the store state from a genesis state.
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, gs types.GenesisState) {
|
func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, gs types.GenesisState) {
|
||||||
|
|
||||||
k.SetParams(ctx, gs.Params)
|
if err := gs.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
|
||||||
|
}
|
||||||
|
|
||||||
// check if the module account exists
|
// check if the module account exists
|
||||||
moduleAcc := supplyKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
|
moduleAcc := supplyKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
|
||||||
if moduleAcc == nil {
|
if moduleAcc == nil {
|
||||||
panic(fmt.Sprintf("%s module account has not been set", types.ModuleAccountName))
|
panic(fmt.Sprintf("%s module account has not been set", types.ModuleAccountName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k.SetParams(ctx, gs.Params)
|
||||||
|
|
||||||
|
for _, supply := range gs.Supplies {
|
||||||
|
k.SetAssetSupply(ctx, supply, supply.GetDenom())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportGenesis export genesis state for issuance module
|
// ExportGenesis export genesis state for issuance module
|
||||||
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
||||||
params := k.GetParams(ctx)
|
params := k.GetParams(ctx)
|
||||||
return types.NewGenesisState(params)
|
supplies := k.GetAllAssetSupplies(ctx)
|
||||||
|
return types.NewGenesisState(params, supplies)
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,26 @@ func (k Keeper) IssueTokens(ctx sdk.Context, tokens sdk.Coin, owner, receiver sd
|
|||||||
if asset.Paused {
|
if asset.Paused {
|
||||||
return sdkerrors.Wrapf(types.ErrAssetPaused, "denom: %s", tokens.Denom)
|
return sdkerrors.Wrapf(types.ErrAssetPaused, "denom: %s", tokens.Denom)
|
||||||
}
|
}
|
||||||
blocked, _ := k.checkBlockedAddress(ctx, asset, receiver)
|
if asset.Blockable {
|
||||||
if blocked {
|
blocked, _ := k.checkBlockedAddress(asset, receiver)
|
||||||
return sdkerrors.Wrapf(types.ErrAccountBlocked, "address: %s", receiver)
|
if blocked {
|
||||||
|
return sdkerrors.Wrapf(types.ErrAccountBlocked, "address: %s", receiver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := k.accountKeeper.GetAccount(ctx, receiver)
|
acc := k.accountKeeper.GetAccount(ctx, receiver)
|
||||||
_, ok := acc.(supplyexported.ModuleAccountI)
|
_, ok := acc.(supplyexported.ModuleAccountI)
|
||||||
if ok {
|
if ok {
|
||||||
return sdkerrors.Wrapf(types.ErrIssueToModuleAccount, "address: %s", receiver)
|
return sdkerrors.Wrapf(types.ErrIssueToModuleAccount, "address: %s", receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for rate-limited assets, check that the issuance isn't over the limit
|
||||||
|
if asset.RateLimit.Active {
|
||||||
|
err := k.IncrementCurrentAssetSupply(ctx, tokens)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mint new tokens
|
// mint new tokens
|
||||||
err := k.supplyKeeper.MintCoins(ctx, types.ModuleAccountName, sdk.NewCoins(tokens))
|
err := k.supplyKeeper.MintCoins(ctx, types.ModuleAccountName, sdk.NewCoins(tokens))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,13 +96,20 @@ func (k Keeper) BlockAddress(ctx sdk.Context, denom string, owner, blockedAddres
|
|||||||
if !found {
|
if !found {
|
||||||
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
||||||
}
|
}
|
||||||
|
if !asset.Blockable {
|
||||||
|
return sdkerrors.Wrap(types.ErrAssetUnblockable, denom)
|
||||||
|
}
|
||||||
if !owner.Equals(asset.Owner) {
|
if !owner.Equals(asset.Owner) {
|
||||||
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
||||||
}
|
}
|
||||||
blocked, _ := k.checkBlockedAddress(ctx, asset, blockedAddress)
|
blocked, _ := k.checkBlockedAddress(asset, blockedAddress)
|
||||||
if blocked {
|
if blocked {
|
||||||
return sdkerrors.Wrapf(types.ErrAccountAlreadyBlocked, "address: %s", blockedAddress)
|
return sdkerrors.Wrapf(types.ErrAccountAlreadyBlocked, "address: %s", blockedAddress)
|
||||||
}
|
}
|
||||||
|
account := k.accountKeeper.GetAccount(ctx, blockedAddress)
|
||||||
|
if account == nil {
|
||||||
|
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address: %s", blockedAddress)
|
||||||
|
}
|
||||||
asset.BlockedAddresses = append(asset.BlockedAddresses, blockedAddress)
|
asset.BlockedAddresses = append(asset.BlockedAddresses, blockedAddress)
|
||||||
k.SetAsset(ctx, asset)
|
k.SetAsset(ctx, asset)
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
@ -112,17 +128,20 @@ func (k Keeper) UnblockAddress(ctx sdk.Context, denom string, owner, addr sdk.Ac
|
|||||||
if !found {
|
if !found {
|
||||||
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
||||||
}
|
}
|
||||||
|
if !asset.Blockable {
|
||||||
|
return sdkerrors.Wrap(types.ErrAssetUnblockable, denom)
|
||||||
|
}
|
||||||
if !owner.Equals(asset.Owner) {
|
if !owner.Equals(asset.Owner) {
|
||||||
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
||||||
}
|
}
|
||||||
blocked, i := k.checkBlockedAddress(ctx, asset, addr)
|
blocked, i := k.checkBlockedAddress(asset, addr)
|
||||||
if !blocked {
|
if !blocked {
|
||||||
if blocked {
|
if blocked {
|
||||||
return sdkerrors.Wrapf(types.ErrAccountAlreadyUnblocked, "address: %s", addr)
|
return sdkerrors.Wrapf(types.ErrAccountAlreadyUnblocked, "address: %s", addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockedAddrs := k.removeBlockedAddress(ctx, asset.BlockedAddresses, i)
|
blockedAddrs := k.removeBlockedAddress(asset.BlockedAddresses, i)
|
||||||
asset.BlockedAddresses = blockedAddrs
|
asset.BlockedAddresses = blockedAddrs
|
||||||
k.SetAsset(ctx, asset)
|
k.SetAsset(ctx, asset)
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
@ -159,6 +178,20 @@ func (k Keeper) SetPauseStatus(ctx sdk.Context, owner sdk.AccAddress, denom stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SeizeCoinsForBlockableAssets seizes coins from blocked addresses for assets that have blocking enabled
|
||||||
|
func (k Keeper) SeizeCoinsForBlockableAssets(ctx sdk.Context) error {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
for _, asset := range params.Assets {
|
||||||
|
if asset.Blockable {
|
||||||
|
err := k.SeizeCoinsFromBlockedAddresses(ctx, asset.Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SeizeCoinsFromBlockedAddresses checks blocked addresses for coins of the input denom and transfers them to the owner account
|
// SeizeCoinsFromBlockedAddresses checks blocked addresses for coins of the input denom and transfers them to the owner account
|
||||||
func (k Keeper) SeizeCoinsFromBlockedAddresses(ctx sdk.Context, denom string) error {
|
func (k Keeper) SeizeCoinsFromBlockedAddresses(ctx sdk.Context, denom string) error {
|
||||||
asset, found := k.GetAsset(ctx, denom)
|
asset, found := k.GetAsset(ctx, denom)
|
||||||
@ -167,6 +200,11 @@ func (k Keeper) SeizeCoinsFromBlockedAddresses(ctx sdk.Context, denom string) er
|
|||||||
}
|
}
|
||||||
for _, address := range asset.BlockedAddresses {
|
for _, address := range asset.BlockedAddresses {
|
||||||
account := k.accountKeeper.GetAccount(ctx, address)
|
account := k.accountKeeper.GetAccount(ctx, address)
|
||||||
|
if account == nil {
|
||||||
|
// avoids a potential panic
|
||||||
|
// this could happen if, for example, an account was pruned from state but remained in the block list,
|
||||||
|
continue
|
||||||
|
}
|
||||||
coinsAmount := account.GetCoins().AmountOf(denom)
|
coinsAmount := account.GetCoins().AmountOf(denom)
|
||||||
if !coinsAmount.IsPositive() {
|
if !coinsAmount.IsPositive() {
|
||||||
continue
|
continue
|
||||||
@ -191,7 +229,7 @@ func (k Keeper) SeizeCoinsFromBlockedAddresses(ctx sdk.Context, denom string) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) checkBlockedAddress(ctx sdk.Context, asset types.Asset, checkAddress sdk.AccAddress) (bool, int) {
|
func (k Keeper) checkBlockedAddress(asset types.Asset, checkAddress sdk.AccAddress) (bool, int) {
|
||||||
for i, address := range asset.BlockedAddresses {
|
for i, address := range asset.BlockedAddresses {
|
||||||
if address.Equals(checkAddress) {
|
if address.Equals(checkAddress) {
|
||||||
return true, i
|
return true, i
|
||||||
@ -200,7 +238,7 @@ func (k Keeper) checkBlockedAddress(ctx sdk.Context, asset types.Asset, checkAdd
|
|||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) removeBlockedAddress(ctx sdk.Context, blockedAddrs []sdk.AccAddress, i int) []sdk.AccAddress {
|
func (k Keeper) removeBlockedAddress(blockedAddrs []sdk.AccAddress, i int) []sdk.AccAddress {
|
||||||
blockedAddrs[len(blockedAddrs)-1], blockedAddrs[i] = blockedAddrs[i], blockedAddrs[len(blockedAddrs)-1]
|
blockedAddrs[len(blockedAddrs)-1], blockedAddrs[i] = blockedAddrs[i], blockedAddrs[len(blockedAddrs)-1]
|
||||||
return blockedAddrs[:len(blockedAddrs)-1]
|
return blockedAddrs[:len(blockedAddrs)-1]
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package keeper_test
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ import (
|
|||||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
@ -35,6 +37,10 @@ func (suite *KeeperTestSuite) SetupTest() {
|
|||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||||
tApp.InitializeFromGenesisStates()
|
tApp.InitializeFromGenesisStates()
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
for _, addr := range addrs {
|
||||||
|
acc := tApp.GetAccountKeeper().NewAccountWithAddress(ctx, addr)
|
||||||
|
tApp.GetAccountKeeper().SetAccount(ctx, acc)
|
||||||
|
}
|
||||||
keeper := tApp.GetIssuanceKeeper()
|
keeper := tApp.GetIssuanceKeeper()
|
||||||
modAccount, err := sdk.AccAddressFromBech32("kava1cj7njkw2g9fqx4e768zc75dp9sks8u9znxrf0w")
|
modAccount, err := sdk.AccAddressFromBech32("kava1cj7njkw2g9fqx4e768zc75dp9sks8u9znxrf0w")
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
@ -58,7 +64,7 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
|
|||||||
func (suite *KeeperTestSuite) TestGetSetParams() {
|
func (suite *KeeperTestSuite) TestGetSetParams() {
|
||||||
params := suite.keeper.GetParams(suite.ctx)
|
params := suite.keeper.GetParams(suite.ctx)
|
||||||
suite.Require().Equal(types.Params{Assets: types.Assets(nil)}, params)
|
suite.Require().Equal(types.Params{Assets: types.Assets(nil)}, params)
|
||||||
asset := types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false)
|
asset := types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0)))
|
||||||
params = types.NewParams(types.Assets{asset})
|
params = types.NewParams(types.Assets{asset})
|
||||||
suite.keeper.SetParams(suite.ctx, params)
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
newParams := suite.keeper.GetParams(suite.ctx)
|
newParams := suite.keeper.GetParams(suite.ctx)
|
||||||
@ -85,7 +91,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"valid issuance",
|
"valid issuance",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -100,7 +106,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"non-owner issuance",
|
"non-owner issuance",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[2],
|
sender: suite.addrs[2],
|
||||||
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -115,7 +121,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"invalid denom",
|
"invalid denom",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
tokens: sdk.NewCoin("othertoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("othertoken", sdk.NewInt(100000)),
|
||||||
@ -130,7 +136,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"issue to blocked address",
|
"issue to blocked address",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -145,7 +151,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"issue to module account",
|
"issue to module account",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -160,7 +166,7 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
"paused issuance",
|
"paused issuance",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, true),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, true, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -190,6 +196,85 @@ func (suite *KeeperTestSuite) TestIssueTokens() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestIssueTokensRateLimited() {
|
||||||
|
type args struct {
|
||||||
|
assets types.Assets
|
||||||
|
supplies types.AssetSupplies
|
||||||
|
sender sdk.AccAddress
|
||||||
|
tokens sdk.Coin
|
||||||
|
receiver sdk.AccAddress
|
||||||
|
blockTime time.Time
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid issuance",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
|
receiver: suite.addrs[2],
|
||||||
|
blockTime: suite.ctx.BlockTime().Add(time.Hour),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"over-limit issuance",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.NewCoin("usdtoken", sdk.NewInt(10000000001)),
|
||||||
|
receiver: suite.addrs[2],
|
||||||
|
blockTime: suite.ctx.BlockTime().Add(time.Hour),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "asset supply over limit",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
params := types.NewParams(tc.args.assets)
|
||||||
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
|
for _, supply := range tc.args.supplies {
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, supply, supply.GetDenom())
|
||||||
|
}
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(tc.args.blockTime)
|
||||||
|
err := suite.keeper.IssueTokens(suite.ctx, tc.args.tokens, tc.args.sender, tc.args.receiver)
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err, tc.name)
|
||||||
|
receiverAccount := suite.getAccount(tc.args.receiver)
|
||||||
|
suite.Require().Equal(sdk.NewCoins(tc.args.tokens), receiverAccount.GetCoins())
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err, tc.name)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestRedeemTokens() {
|
func (suite *KeeperTestSuite) TestRedeemTokens() {
|
||||||
type args struct {
|
type args struct {
|
||||||
assets types.Assets
|
assets types.Assets
|
||||||
@ -210,7 +295,7 @@ func (suite *KeeperTestSuite) TestRedeemTokens() {
|
|||||||
"valid redemption",
|
"valid redemption",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -225,7 +310,7 @@ func (suite *KeeperTestSuite) TestRedeemTokens() {
|
|||||||
"invalid denom redemption",
|
"invalid denom redemption",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -240,7 +325,7 @@ func (suite *KeeperTestSuite) TestRedeemTokens() {
|
|||||||
"non-owner redemption",
|
"non-owner redemption",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[2],
|
sender: suite.addrs[2],
|
||||||
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -255,7 +340,7 @@ func (suite *KeeperTestSuite) TestRedeemTokens() {
|
|||||||
"paused redemption",
|
"paused redemption",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, true),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, true, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -270,7 +355,7 @@ func (suite *KeeperTestSuite) TestRedeemTokens() {
|
|||||||
"redeem amount greater than balance",
|
"redeem amount greater than balance",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
initialTokens: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
@ -328,7 +413,7 @@ func (suite *KeeperTestSuite) TestBlockAddress() {
|
|||||||
"valid block",
|
"valid block",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -339,11 +424,26 @@ func (suite *KeeperTestSuite) TestBlockAddress() {
|
|||||||
contains: "",
|
contains: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"unblockable token",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, false, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
|
},
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
blockedAddr: suite.addrs[1],
|
||||||
|
denom: "usdtoken",
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "asset does not support block/unblock functionality",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"non-owner block",
|
"non-owner block",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[2],
|
sender: suite.addrs[2],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -358,7 +458,7 @@ func (suite *KeeperTestSuite) TestBlockAddress() {
|
|||||||
"invalid denom block",
|
"invalid denom block",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -369,6 +469,21 @@ func (suite *KeeperTestSuite) TestBlockAddress() {
|
|||||||
contains: "no asset with input denom found",
|
contains: "no asset with input denom found",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"block non-existing account",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
|
},
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
blockedAddr: sdk.AccAddress(crypto.AddressHash([]byte("RandomAddr"))),
|
||||||
|
denom: "usdtoken",
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "cannot block account that does not exist in state",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
suite.Run(tc.name, func() {
|
||||||
@ -416,7 +531,7 @@ func (suite *KeeperTestSuite) TestUnblockAddress() {
|
|||||||
"valid unblock",
|
"valid unblock",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -431,7 +546,7 @@ func (suite *KeeperTestSuite) TestUnblockAddress() {
|
|||||||
"non-owner unblock",
|
"non-owner unblock",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[2],
|
sender: suite.addrs[2],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -446,7 +561,7 @@ func (suite *KeeperTestSuite) TestUnblockAddress() {
|
|||||||
"invalid denom block",
|
"invalid denom block",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
blockedAddr: suite.addrs[1],
|
blockedAddr: suite.addrs[1],
|
||||||
@ -505,7 +620,7 @@ func (suite *KeeperTestSuite) TestChangePauseStatus() {
|
|||||||
"valid pause",
|
"valid pause",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
startStatus: false,
|
startStatus: false,
|
||||||
@ -521,7 +636,7 @@ func (suite *KeeperTestSuite) TestChangePauseStatus() {
|
|||||||
"valid unpause",
|
"valid unpause",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, true),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, true, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
startStatus: true,
|
startStatus: true,
|
||||||
@ -537,7 +652,7 @@ func (suite *KeeperTestSuite) TestChangePauseStatus() {
|
|||||||
"non-owner pause",
|
"non-owner pause",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[2],
|
sender: suite.addrs[2],
|
||||||
startStatus: false,
|
startStatus: false,
|
||||||
@ -553,7 +668,7 @@ func (suite *KeeperTestSuite) TestChangePauseStatus() {
|
|||||||
"invalid denom pause",
|
"invalid denom pause",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
sender: suite.addrs[0],
|
sender: suite.addrs[0],
|
||||||
startStatus: true,
|
startStatus: true,
|
||||||
@ -606,7 +721,7 @@ func (suite *KeeperTestSuite) TestSeizeCoinsFromBlockedAddress() {
|
|||||||
"valid seize",
|
"valid seize",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
initialCoins: sdk.NewCoin("usdtoken", sdk.NewInt(100000000)),
|
initialCoins: sdk.NewCoin("usdtoken", sdk.NewInt(100000000)),
|
||||||
denom: "usdtoken",
|
denom: "usdtoken",
|
||||||
@ -621,7 +736,7 @@ func (suite *KeeperTestSuite) TestSeizeCoinsFromBlockedAddress() {
|
|||||||
"invalid denom seize",
|
"invalid denom seize",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
initialCoins: sdk.NewCoin("usdtoken", sdk.NewInt(100000000)),
|
initialCoins: sdk.NewCoin("usdtoken", sdk.NewInt(100000000)),
|
||||||
denom: "othertoken",
|
denom: "othertoken",
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||||
|
|
||||||
@ -31,3 +34,62 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
|
|||||||
supplyKeeper: sk,
|
supplyKeeper: sk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAssetSupply gets an asset's current supply from the store.
|
||||||
|
func (k Keeper) GetAssetSupply(ctx sdk.Context, denom string) (types.AssetSupply, bool) {
|
||||||
|
var assetSupply types.AssetSupply
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
bz := store.Get([]byte(denom))
|
||||||
|
if bz == nil {
|
||||||
|
return types.AssetSupply{}, false
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &assetSupply)
|
||||||
|
return assetSupply, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetSupply updates an asset's supply
|
||||||
|
func (k Keeper) SetAssetSupply(ctx sdk.Context, supply types.AssetSupply, denom string) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(supply))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAssetSupplies provides an iterator over all stored AssetSupplies.
|
||||||
|
func (k Keeper) IterateAssetSupplies(ctx sdk.Context, cb func(supply types.AssetSupply) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.AssetSupplyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var supply types.AssetSupply
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &supply)
|
||||||
|
|
||||||
|
if cb(supply) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAssetSupplies returns all asset supplies from the store
|
||||||
|
func (k Keeper) GetAllAssetSupplies(ctx sdk.Context) (supplies types.AssetSupplies) {
|
||||||
|
k.IterateAssetSupplies(ctx, func(supply types.AssetSupply) bool {
|
||||||
|
supplies = append(supplies, supply)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package keeper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/issuance/types"
|
"github.com/kava-labs/kava/x/issuance/types"
|
||||||
)
|
)
|
||||||
@ -39,3 +40,23 @@ func (k Keeper) SetAsset(ctx sdk.Context, asset types.Asset) {
|
|||||||
}
|
}
|
||||||
k.SetParams(ctx, params)
|
k.SetParams(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRateLimit returns the rete-limit parameters for the input denom
|
||||||
|
func (k Keeper) GetRateLimit(ctx sdk.Context, denom string) (types.RateLimit, error) {
|
||||||
|
asset, found := k.GetAsset(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
sdkerrors.Wrap(types.ErrAssetNotFound, denom)
|
||||||
|
}
|
||||||
|
return asset.RateLimit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SynchronizeBlockList resets the block list to empty for any asset that is not blockable - could happen if this value is changed via governance
|
||||||
|
func (k Keeper) SynchronizeBlockList(ctx sdk.Context) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
for _, asset := range params.Assets {
|
||||||
|
if !asset.Blockable && len(asset.BlockedAddresses) > 0 {
|
||||||
|
asset.BlockedAddresses = []sdk.AccAddress{}
|
||||||
|
k.SetAsset(ctx, asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
73
x/issuance/keeper/supply.go
Normal file
73
x/issuance/keeper/supply.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/issuance/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()), time.Duration(0))
|
||||||
|
k.SetAssetSupply(ctx, supply, denom)
|
||||||
|
return supply
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementCurrentAssetSupply increments an asset's supply by the coin
|
||||||
|
func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, coin.Denom)
|
||||||
|
if !found {
|
||||||
|
return sdkerrors.Wrap(types.ErrAssetNotFound, coin.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := k.GetRateLimit(ctx, coin.Denom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit.Active {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
supply.CurrentSupply = supply.CurrentSupply.Add(coin)
|
||||||
|
k.SetAssetSupply(ctx, supply, coin.Denom)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||||
|
if !found {
|
||||||
|
previousBlockTime = ctx.BlockTime()
|
||||||
|
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
||||||
|
}
|
||||||
|
timeElapsed := ctx.BlockTime().Sub(previousBlockTime)
|
||||||
|
for _, asset := range params.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)
|
||||||
|
}
|
||||||
|
if asset.RateLimit.Active {
|
||||||
|
if asset.RateLimit.TimePeriod <= supply.TimeElapsed+timeElapsed {
|
||||||
|
supply.TimeElapsed = time.Duration(0)
|
||||||
|
supply.CurrentSupply = sdk.NewCoin(asset.Denom, sdk.ZeroInt())
|
||||||
|
} else {
|
||||||
|
supply.TimeElapsed = supply.TimeElapsed + timeElapsed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
supply.CurrentSupply = sdk.NewCoin(asset.Denom, sdk.ZeroInt())
|
||||||
|
supply.TimeElapsed = time.Duration(0)
|
||||||
|
}
|
||||||
|
k.SetAssetSupply(ctx, supply, asset.Denom)
|
||||||
|
}
|
||||||
|
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||||
|
}
|
83
x/issuance/keeper/supply_test.go
Normal file
83
x/issuance/keeper/supply_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/issuance/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestIncrementCurrentAssetSupply() {
|
||||||
|
type args struct {
|
||||||
|
assets types.Assets
|
||||||
|
supplies types.AssetSupplies
|
||||||
|
coin sdk.Coin
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid supply increase",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
coin: sdk.NewCoin("usdtoken", sdk.NewInt(100000)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"over limit increase",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(true, sdk.NewInt(10000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{
|
||||||
|
types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour),
|
||||||
|
},
|
||||||
|
coin: sdk.NewCoin("usdtoken", sdk.NewInt(10000000001)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "asset supply over limit",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.SetupTest()
|
||||||
|
params := types.NewParams(tc.args.assets)
|
||||||
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
|
for _, supply := range tc.args.supplies {
|
||||||
|
suite.keeper.SetAssetSupply(suite.ctx, supply, supply.GetDenom())
|
||||||
|
}
|
||||||
|
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coin)
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err, tc.name)
|
||||||
|
for _, expectedSupply := range tc.args.supplies {
|
||||||
|
expectedSupply.CurrentSupply = expectedSupply.CurrentSupply.Add(tc.args.coin)
|
||||||
|
actualSupply, found := suite.keeper.GetAssetSupply(suite.ctx, expectedSupply.GetDenom())
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(expectedSupply, actualSupply, tc.name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err, tc.name)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package simulation
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
@ -12,5 +14,20 @@ import (
|
|||||||
|
|
||||||
// DecodeStore the issuance module has no store keys -- all state is stored in params
|
// DecodeStore the issuance module has no store keys -- all state is stored in params
|
||||||
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
switch {
|
||||||
|
case bytes.Equal(kvA.Key[:1], types.AssetSupplyPrefix):
|
||||||
|
var supplyA, supplyB types.AssetSupply
|
||||||
|
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]))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
54
x/issuance/simulation/decoder_test.go
Normal file
54
x/issuance/simulation/decoder_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/libs/kv"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/issuance/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeTestCodec() (cdc *codec.Codec) {
|
||||||
|
cdc = codec.New()
|
||||||
|
sdk.RegisterCodec(cdc)
|
||||||
|
types.RegisterCodec(cdc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeIssuanceStore(t *testing.T) {
|
||||||
|
cdc := makeTestCodec()
|
||||||
|
supply := types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour)
|
||||||
|
prevBlockTime := time.Now().UTC()
|
||||||
|
|
||||||
|
kvPairs := kv.Pairs{
|
||||||
|
kv.Pair{Key: types.AssetSupplyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)},
|
||||||
|
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expectedLog string
|
||||||
|
}{
|
||||||
|
{"AssetSupply", fmt.Sprintf("%v\n%v", supply, supply)},
|
||||||
|
{"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)},
|
||||||
|
{"other", ""},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
switch i {
|
||||||
|
case len(tests) - 1:
|
||||||
|
require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name)
|
||||||
|
default:
|
||||||
|
require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -21,7 +22,7 @@ var (
|
|||||||
func RandomizedGenState(simState *module.SimulationState) {
|
func RandomizedGenState(simState *module.SimulationState) {
|
||||||
accs = simState.Accounts
|
accs = simState.Accounts
|
||||||
params := randomizedParams(simState.Rand)
|
params := randomizedParams(simState.Rand)
|
||||||
gs := types.NewGenesisState(params)
|
gs := types.NewGenesisState(params, types.AssetSupplies{})
|
||||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, gs))
|
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, gs))
|
||||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(gs)
|
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(gs)
|
||||||
}
|
}
|
||||||
@ -38,7 +39,14 @@ func randomizedAssets(r *rand.Rand) types.Assets {
|
|||||||
denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3)))
|
denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3)))
|
||||||
owner := randomOwner(r)
|
owner := randomOwner(r)
|
||||||
paused := r.Intn(2) == 0
|
paused := r.Intn(2) == 0
|
||||||
randomAsset := types.NewAsset(owner.Address, denom, []sdk.AccAddress{}, paused)
|
rateLimited := r.Intn(2) == 0
|
||||||
|
rateLimit := types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))
|
||||||
|
if rateLimited {
|
||||||
|
timeLimit := time.Duration(3600000000000 + (r.Intn(24) + 1))
|
||||||
|
assetLimit := simulation.RandIntBetween(r, 100000000000, 1000000000000)
|
||||||
|
rateLimit = types.NewRateLimit(true, sdk.NewInt(int64(assetLimit)), timeLimit)
|
||||||
|
}
|
||||||
|
randomAsset := types.NewAsset(owner.Address, denom, []sdk.AccAddress{}, paused, true, rateLimit)
|
||||||
randomAssets = append(randomAssets, randomAsset)
|
randomAssets = append(randomAssets, randomAsset)
|
||||||
}
|
}
|
||||||
return randomAssets
|
return randomAssets
|
||||||
|
@ -116,6 +116,21 @@ func SimulateMsgIssueTokens(ak types.AccountKeeper, k keeper.Keeper) simulation.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
randomAmount := simulation.RandIntBetween(r, 10000000, 1000000000000)
|
randomAmount := simulation.RandIntBetween(r, 10000000, 1000000000000)
|
||||||
|
if asset.RateLimit.Active {
|
||||||
|
supply, found := k.GetAssetSupply(ctx, asset.Denom)
|
||||||
|
if !found {
|
||||||
|
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("issuance - no asset supply for %s", asset.Denom)
|
||||||
|
}
|
||||||
|
if asset.RateLimit.Limit.LT(supply.CurrentSupply.Amount.Add(sdk.NewInt(int64(randomAmount)))) {
|
||||||
|
maxAmount := asset.RateLimit.Limit.Sub(supply.CurrentSupply.Amount)
|
||||||
|
if maxAmount.IsPositive() && maxAmount.GT(sdk.OneInt()) {
|
||||||
|
randomAmount = simulation.RandIntBetween(r, 1, int(maxAmount.Int64()))
|
||||||
|
} else {
|
||||||
|
randomAmount = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
msg := types.NewMsgIssueTokens(asset.Owner, sdk.NewCoin(asset.Denom, sdk.NewInt(int64(randomAmount))), recipient.GetAddress())
|
msg := types.NewMsgIssueTokens(asset.Owner, sdk.NewCoin(asset.Denom, sdk.NewInt(int64(randomAmount))), recipient.GetAddress())
|
||||||
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime())
|
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime())
|
||||||
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
|
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
|
||||||
|
@ -15,4 +15,7 @@ var (
|
|||||||
ErrAccountAlreadyBlocked = sdkerrors.Register(ModuleName, 6, "account is already blocked")
|
ErrAccountAlreadyBlocked = sdkerrors.Register(ModuleName, 6, "account is already blocked")
|
||||||
ErrAccountAlreadyUnblocked = sdkerrors.Register(ModuleName, 7, "account is already unblocked")
|
ErrAccountAlreadyUnblocked = sdkerrors.Register(ModuleName, 7, "account is already unblocked")
|
||||||
ErrIssueToModuleAccount = sdkerrors.Register(ModuleName, 8, "cannot issue tokens to module account")
|
ErrIssueToModuleAccount = sdkerrors.Register(ModuleName, 8, "cannot issue tokens to module account")
|
||||||
|
ErrExceedsSupplyLimit = sdkerrors.Register(ModuleName, 9, "asset supply over limit")
|
||||||
|
ErrAssetUnblockable = sdkerrors.Register(ModuleName, 10, "asset does not support block/unblock functionality")
|
||||||
|
ErrAccountNotFound = sdkerrors.Register(ModuleName, 11, "cannot block account that does not exist in state")
|
||||||
)
|
)
|
||||||
|
@ -4,26 +4,35 @@ import "bytes"
|
|||||||
|
|
||||||
// GenesisState is the state that must be provided at genesis for the issuance module
|
// GenesisState is the state that must be provided at genesis for the issuance module
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
Params Params `json:"params" yaml:"params"`
|
Params Params `json:"params" yaml:"params"`
|
||||||
|
Supplies AssetSupplies `json:"supplies" yaml:"supplies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState returns a new GenesisState
|
// NewGenesisState returns a new GenesisState
|
||||||
func NewGenesisState(params Params) GenesisState {
|
func NewGenesisState(params Params, supplies AssetSupplies) GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Params: params,
|
Params: params,
|
||||||
|
Supplies: supplies,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultGenesisState returns the default GenesisState for the issuance module
|
// DefaultGenesisState returns the default GenesisState for the issuance module
|
||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Params: DefaultParams(),
|
Params: DefaultParams(),
|
||||||
|
Supplies: AssetSupplies{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate performs basic validation of genesis data returning an
|
// Validate performs basic validation of genesis data returning an
|
||||||
// error for any failed validation criteria.
|
// error for any failed validation criteria.
|
||||||
func (gs GenesisState) Validate() error {
|
func (gs GenesisState) Validate() error {
|
||||||
|
for _, supply := range gs.Supplies {
|
||||||
|
err := supply.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return gs.Params.Validate()
|
return gs.Params.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package types_test
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -28,7 +29,8 @@ func (suite *GenesisTestSuite) SetupTest() {
|
|||||||
|
|
||||||
func (suite *GenesisTestSuite) TestValidate() {
|
func (suite *GenesisTestSuite) TestValidate() {
|
||||||
type args struct {
|
type args struct {
|
||||||
assets types.Assets
|
assets types.Assets
|
||||||
|
supplies types.AssetSupplies
|
||||||
}
|
}
|
||||||
type errArgs struct {
|
type errArgs struct {
|
||||||
expectPass bool
|
expectPass bool
|
||||||
@ -42,7 +44,8 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
{
|
{
|
||||||
"default",
|
"default",
|
||||||
args{
|
args{
|
||||||
assets: types.DefaultAssets,
|
assets: types.DefaultAssets,
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
@ -53,8 +56,36 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"with asset",
|
"with asset",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.NewInt(1000000)), time.Hour)},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with asset rate limit",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(true, sdk.NewInt(1000000000), time.Hour*24)),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with multiple assets",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
|
types.NewAsset(suite.addrs[0], "pegtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
@ -65,8 +96,9 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"blocked owner",
|
"blocked owner",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[0]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[0]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -77,8 +109,9 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"empty owner",
|
"empty owner",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(sdk.AccAddress{}, "usdtoken", []sdk.AccAddress{suite.addrs[0]}, false),
|
types.NewAsset(sdk.AccAddress{}, "usdtoken", []sdk.AccAddress{suite.addrs[0]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -89,8 +122,9 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"empty blocked address",
|
"empty blocked address",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{sdk.AccAddress{}}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{nil}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -101,8 +135,9 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"invalid denom",
|
"invalid denom",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "USD2T ", []sdk.AccAddress{}, false),
|
types.NewAsset(suite.addrs[0], "USD2T ", []sdk.AccAddress{}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -113,9 +148,10 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"duplicate denom",
|
"duplicate denom",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
types.NewAsset(suite.addrs[1], "usdtoken", []sdk.AccAddress{}, true),
|
types.NewAsset(suite.addrs[1], "usdtoken", []sdk.AccAddress{}, true, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -126,19 +162,33 @@ func (suite *GenesisTestSuite) TestValidate() {
|
|||||||
"duplicate asset",
|
"duplicate asset",
|
||||||
args{
|
args{
|
||||||
assets: types.Assets{
|
assets: types.Assets{
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false),
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, true, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
},
|
},
|
||||||
|
supplies: types.AssetSupplies{},
|
||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "duplicate asset denoms",
|
contains: "duplicate asset denoms",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"invalid block list",
|
||||||
|
args{
|
||||||
|
assets: types.Assets{
|
||||||
|
types.NewAsset(suite.addrs[0], "usdtoken", []sdk.AccAddress{suite.addrs[1]}, false, false, types.NewRateLimit(false, sdk.ZeroInt(), time.Duration(0))),
|
||||||
|
},
|
||||||
|
supplies: types.AssetSupplies{types.NewAssetSupply(sdk.NewCoin("usdtoken", sdk.ZeroInt()), time.Hour)},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "blocked-list should be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
suite.Run(tc.name, func() {
|
||||||
gs := types.NewGenesisState(types.NewParams(tc.args.assets))
|
gs := types.NewGenesisState(types.NewParams(tc.args.assets), tc.args.supplies)
|
||||||
err := gs.Validate()
|
err := gs.Validate()
|
||||||
if tc.errArgs.expectPass {
|
if tc.errArgs.expectPass {
|
||||||
suite.Require().NoError(err, tc.name)
|
suite.Require().NoError(err, tc.name)
|
||||||
|
@ -16,3 +16,9 @@ const (
|
|||||||
// QuerierRoute route used for abci queries
|
// QuerierRoute route used for abci queries
|
||||||
QuerierRoute = ModuleName
|
QuerierRoute = ModuleName
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// KVStore key prefixes
|
||||||
|
var (
|
||||||
|
AssetSupplyPrefix = []byte{0x01}
|
||||||
|
PreviousBlockTimeKey = []byte{0x02}
|
||||||
|
)
|
||||||
|
393
x/issuance/types/msg_test.go
Normal file
393
x/issuance/types/msg_test.go
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
"github.com/kava-labs/kava/x/issuance/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MsgTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||||
|
suite.addrs = addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgIssueTokens() {
|
||||||
|
type args struct {
|
||||||
|
sender sdk.AccAddress
|
||||||
|
tokens sdk.Coin
|
||||||
|
receiver sdk.AccAddress
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.NewCoin("valid", sdk.NewInt(100)),
|
||||||
|
receiver: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender",
|
||||||
|
args{
|
||||||
|
sender: sdk.AccAddress{},
|
||||||
|
tokens: sdk.NewCoin("valid", sdk.NewInt(100)),
|
||||||
|
receiver: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "sender address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid receiver",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.NewCoin("valid", sdk.NewInt(100)),
|
||||||
|
receiver: sdk.AccAddress{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "receiver address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid tokens",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.Coin{Denom: "Invalid", Amount: sdk.NewInt(100)},
|
||||||
|
receiver: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "invalid tokens",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
testMsg := types.NewMsgIssueTokens(tc.args.sender, tc.args.tokens, tc.args.receiver)
|
||||||
|
err := testMsg.ValidateBasic()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgRedeemTokens() {
|
||||||
|
type args struct {
|
||||||
|
sender sdk.AccAddress
|
||||||
|
tokens sdk.Coin
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.NewCoin("valid", sdk.NewInt(100)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender",
|
||||||
|
args{
|
||||||
|
sender: sdk.AccAddress{},
|
||||||
|
tokens: sdk.NewCoin("valid", sdk.NewInt(100)),
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "sender address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid tokens",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
tokens: sdk.Coin{Denom: "Invalid", Amount: sdk.NewInt(100)},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "invalid tokens",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
testMsg := types.NewMsgRedeemTokens(tc.args.sender, tc.args.tokens)
|
||||||
|
err := testMsg.ValidateBasic()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgBlockAddress() {
|
||||||
|
type args struct {
|
||||||
|
sender sdk.AccAddress
|
||||||
|
denom string
|
||||||
|
address sdk.AccAddress
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "valid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender",
|
||||||
|
args{
|
||||||
|
sender: sdk.AccAddress{},
|
||||||
|
denom: "valid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "sender address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid blocked",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "valid",
|
||||||
|
address: sdk.AccAddress{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "blocked address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "Invalid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "invalid denom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
testMsg := types.NewMsgBlockAddress(tc.args.sender, tc.args.denom, tc.args.address)
|
||||||
|
err := testMsg.ValidateBasic()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgUnblockAddress() {
|
||||||
|
type args struct {
|
||||||
|
sender sdk.AccAddress
|
||||||
|
denom string
|
||||||
|
address sdk.AccAddress
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "valid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender",
|
||||||
|
args{
|
||||||
|
sender: sdk.AccAddress{},
|
||||||
|
denom: "valid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "sender address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid blocked",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "valid",
|
||||||
|
address: sdk.AccAddress{},
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "blocked address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "Invalid",
|
||||||
|
address: suite.addrs[1],
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "invalid denom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
testMsg := types.NewMsgUnblockAddress(tc.args.sender, tc.args.denom, tc.args.address)
|
||||||
|
err := testMsg.ValidateBasic()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgSetPauseStatus() {
|
||||||
|
type args struct {
|
||||||
|
sender sdk.AccAddress
|
||||||
|
denom string
|
||||||
|
status bool
|
||||||
|
}
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "valid",
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid sender",
|
||||||
|
args{
|
||||||
|
sender: sdk.AccAddress{},
|
||||||
|
denom: "valid",
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "sender address cannot be empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
args{
|
||||||
|
sender: suite.addrs[0],
|
||||||
|
denom: "Invalid",
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
errArgs{
|
||||||
|
expectPass: false,
|
||||||
|
contains: "invalid denom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
testMsg := types.NewMsgSetPauseStatus(tc.args.sender, tc.args.denom, tc.args.status)
|
||||||
|
err := testMsg.ValidateBasic()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MsgTestSuite))
|
||||||
|
}
|
@ -2,6 +2,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
@ -67,15 +68,19 @@ type Asset struct {
|
|||||||
Denom string `json:"denom" yaml:"denom"`
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
BlockedAddresses []sdk.AccAddress `json:"blocked_addresses" yaml:"blocked_addresses"`
|
BlockedAddresses []sdk.AccAddress `json:"blocked_addresses" yaml:"blocked_addresses"`
|
||||||
Paused bool `json:"paused" yaml:"paused"`
|
Paused bool `json:"paused" yaml:"paused"`
|
||||||
|
Blockable bool `json:"blockable" yaml:"blockable"`
|
||||||
|
RateLimit RateLimit `json:"rate_limit" yaml:"rate_limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAsset returns a new Asset
|
// NewAsset returns a new Asset
|
||||||
func NewAsset(owner sdk.AccAddress, denom string, blockedAddresses []sdk.AccAddress, paused bool) Asset {
|
func NewAsset(owner sdk.AccAddress, denom string, blockedAddresses []sdk.AccAddress, paused bool, blockable bool, limit RateLimit) Asset {
|
||||||
return Asset{
|
return Asset{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Denom: denom,
|
Denom: denom,
|
||||||
BlockedAddresses: blockedAddresses,
|
BlockedAddresses: blockedAddresses,
|
||||||
Paused: paused,
|
Paused: paused,
|
||||||
|
Blockable: blockable,
|
||||||
|
RateLimit: limit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +89,9 @@ func (a Asset) Validate() error {
|
|||||||
if a.Owner.Empty() {
|
if a.Owner.Empty() {
|
||||||
return fmt.Errorf("owner must not be empty")
|
return fmt.Errorf("owner must not be empty")
|
||||||
}
|
}
|
||||||
|
if !a.Blockable && len(a.BlockedAddresses) > 0 {
|
||||||
|
return fmt.Errorf("asset %s does not support blocking, blocked-list should be empty: %s", a.Denom, a.BlockedAddresses)
|
||||||
|
}
|
||||||
for _, address := range a.BlockedAddresses {
|
for _, address := range a.BlockedAddresses {
|
||||||
if address.Empty() {
|
if address.Empty() {
|
||||||
return fmt.Errorf("blocked address must not be empty")
|
return fmt.Errorf("blocked address must not be empty")
|
||||||
@ -101,8 +109,9 @@ func (a Asset) String() string {
|
|||||||
Owner: %s
|
Owner: %s
|
||||||
Paused: %t
|
Paused: %t
|
||||||
Denom: %s
|
Denom: %s
|
||||||
Blocked Addresses: %s`,
|
Blocked Addresses: %s
|
||||||
a.Owner, a.Paused, a.Denom, a.BlockedAddresses)
|
Rate limits: %s`,
|
||||||
|
a.Owner, a.Paused, a.Denom, a.BlockedAddresses, a.RateLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assets array of Asset
|
// Assets array of Asset
|
||||||
@ -131,3 +140,28 @@ func (as Assets) String() string {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RateLimit parameters for rate-limiting the supply of an issued asset
|
||||||
|
type RateLimit struct {
|
||||||
|
Active bool `json:"active" yaml:"active"`
|
||||||
|
Limit sdk.Int `json:"limit" yaml:"limit"`
|
||||||
|
TimePeriod time.Duration `json:"time_period" yaml:"time_period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRateLimit initializes a new RateLimit
|
||||||
|
func NewRateLimit(active bool, limit sdk.Int, timePeriod time.Duration) RateLimit {
|
||||||
|
return RateLimit{
|
||||||
|
Active: active,
|
||||||
|
Limit: limit,
|
||||||
|
TimePeriod: timePeriod,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (r RateLimit) String() string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
Active: %t
|
||||||
|
Limit: %s
|
||||||
|
Time Period: %s`,
|
||||||
|
r.Active, r.Limit, r.TimePeriod)
|
||||||
|
}
|
||||||
|
51
x/issuance/types/supply.go
Normal file
51
x/issuance/types/supply.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssetSupply contains information about an asset's rate-limited supply (the total supply of the asset is tracked in the top-level supply module)
|
||||||
|
type AssetSupply struct {
|
||||||
|
CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"`
|
||||||
|
TimeElapsed time.Duration `json:"time_elapsed" yaml:"time_elapsed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAssetSupply initializes a new AssetSupply
|
||||||
|
func NewAssetSupply(currentSupply sdk.Coin, timeElapsed time.Duration) AssetSupply {
|
||||||
|
return AssetSupply{
|
||||||
|
CurrentSupply: currentSupply,
|
||||||
|
TimeElapsed: timeElapsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic validation of an asset supply fields.
|
||||||
|
func (a AssetSupply) Validate() error {
|
||||||
|
if !a.CurrentSupply.IsValid() {
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "outgoing supply %s", a.CurrentSupply)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements stringer
|
||||||
|
func (a AssetSupply) String() string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
asset supply:
|
||||||
|
Current supply: %s
|
||||||
|
Time elapsed: %s
|
||||||
|
`,
|
||||||
|
a.CurrentSupply, 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
|
||||||
|
|
||||||
|
// TODO copy over supply tests from bep3
|
Loading…
Reference in New Issue
Block a user