feat(x/accounts): Add new lockup account type (#19048)

Co-authored-by: testinginprod <frojdi@faulttolerance.net>
Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
son trinh
2024-03-14 09:06:02 +00:00
committed by GitHub
co-authored by testinginprod Facundo Medica coderabbitai[bot]
parent 5424b55c57
commit 3ce9224f00
29 changed files with 26748 additions and 128 deletions
+5
View File
@@ -0,0 +1,5 @@
# x/accounts/lockup
<!--- TODO: need to expand more on this --->
The x/accounts/lockup module provides the implementation for lockup accounts within the x/accounts module.
@@ -0,0 +1,225 @@
package lockup
import (
"context"
"time"
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/accountstd"
lockuptypes "cosmossdk.io/x/accounts/lockup/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Compile-time type assertions
var (
_ accountstd.Interface = (*ContinuousLockingAccount)(nil)
)
// NewContinuousLockingAccount creates a new ContinuousLockingAccount object.
func NewContinuousLockingAccount(d accountstd.Dependencies) (*ContinuousLockingAccount, error) {
baseLockup := newBaseLockup(d)
ContinuousLockingAccount := ContinuousLockingAccount{
BaseLockup: baseLockup,
StartTime: collections.NewItem(d.SchemaBuilder, StartTimePrefix, "start_time", collcodec.KeyToValueCodec[time.Time](sdk.TimeKey)),
}
return &ContinuousLockingAccount, nil
}
type ContinuousLockingAccount struct {
*BaseLockup
StartTime collections.Item[time.Time]
}
func (cva ContinuousLockingAccount) Init(ctx context.Context, msg *lockuptypes.MsgInitLockupAccount) (*lockuptypes.MsgInitLockupAccountResponse, error) {
if msg.EndTime.IsZero() {
return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid end time %s", msg.EndTime.String())
}
if msg.EndTime.Before(msg.StartTime) {
return nil, sdkerrors.ErrInvalidRequest.Wrap("invalid start and end time (must be start before end)")
}
hs := cva.headerService.GetHeaderInfo(ctx)
start := msg.StartTime
if msg.StartTime.IsZero() {
start = hs.Time
}
err := cva.StartTime.Set(ctx, start)
if err != nil {
return nil, err
}
return cva.BaseLockup.Init(ctx, msg)
}
func (cva *ContinuousLockingAccount) Delegate(ctx context.Context, msg *lockuptypes.MsgDelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return cva.BaseLockup.Delegate(ctx, msg, cva.GetLockedCoinsWithDenoms)
}
func (cva *ContinuousLockingAccount) Undelegate(ctx context.Context, msg *lockuptypes.MsgUndelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return cva.BaseLockup.Undelegate(ctx, msg)
}
func (cva *ContinuousLockingAccount) SendCoins(ctx context.Context, msg *lockuptypes.MsgSend) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return cva.BaseLockup.SendCoins(ctx, msg, cva.GetLockedCoinsWithDenoms)
}
func (cva *ContinuousLockingAccount) WithdrawUnlockedCoins(ctx context.Context, msg *lockuptypes.MsgWithdraw) (
*lockuptypes.MsgWithdrawResponse, error,
) {
return cva.BaseLockup.WithdrawUnlockedCoins(ctx, msg, cva.GetLockedCoinsWithDenoms)
}
// GetLockCoinsInfo returns the total number of unlocked and locked coins.
func (cva ContinuousLockingAccount) GetLockCoinsInfo(ctx context.Context, blockTime time.Time) (unlockedCoins, lockedCoins sdk.Coins, err error) {
unlockedCoins = sdk.Coins{}
lockedCoins = sdk.Coins{}
// We must handle the case where the start time for a lockup account has
// been set into the future or when the start of the chain is not exactly
// known.
startTime, err := cva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
endTime, err := cva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
var originalVesting sdk.Coins
err = cva.IterateCoinEntries(ctx, cva.OriginalLocking, func(key string, value math.Int) (stop bool, err error) {
originalVesting = append(originalVesting, sdk.NewCoin(key, value))
vestedCoin, vestingCoin, err := cva.GetLockCoinInfoWithDenom(ctx, blockTime, key)
if err != nil {
return true, err
}
unlockedCoins = append(unlockedCoins, *vestedCoin)
lockedCoins = append(lockedCoins, *vestingCoin)
return false, nil
})
if err != nil {
return nil, nil, err
}
if startTime.After(blockTime) {
return unlockedCoins, originalVesting, nil
} else if endTime.Before(blockTime) {
return originalVesting, lockedCoins, nil
}
return unlockedCoins, lockedCoins, nil
}
// GetLockCoinInfoWithDenom returns the number of locked coin for a specific denom. If no coins are locked,
// nil is returned.
func (cva ContinuousLockingAccount) GetLockCoinInfoWithDenom(ctx context.Context, blockTime time.Time, denom string) (unlockedCoin, lockedCoin *sdk.Coin, err error) {
// We must handle the case where the start time for a lockup account has
// been set into the future or when the start of the chain is not exactly
// known.
startTime, err := cva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
endTime, err := cva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
originalLockingAmt, err := cva.OriginalLocking.Get(ctx, denom)
if err != nil {
return nil, nil, err
}
originalLocking := sdk.NewCoin(denom, originalLockingAmt)
if startTime.After(blockTime) {
return &sdk.Coin{}, &originalLocking, nil
} else if endTime.Before(blockTime) {
return &originalLocking, &sdk.Coin{}, nil
}
// calculate the locking scalar
x := blockTime.Unix() - startTime.Unix()
y := endTime.Unix() - startTime.Unix()
s := math.LegacyNewDec(x).Quo(math.LegacyNewDec(y))
unlockedAmt := math.LegacyNewDecFromInt(originalLocking.Amount).Mul(s).RoundInt()
unlocked := sdk.NewCoin(originalLocking.Denom, unlockedAmt)
locked := originalLocking.Sub(unlocked)
return &unlocked, &locked, nil
}
// GetLockedCoins returns the total number of locked coins.
func (cva ContinuousLockingAccount) GetLockedCoins(ctx context.Context, blockTime time.Time) (sdk.Coins, error) {
_, lockedCoins, err := cva.GetLockCoinsInfo(ctx, blockTime)
if err != nil {
return nil, err
}
return lockedCoins, nil
}
// GetLockedCoinsWithDenoms returns the number of locked coin for a specific denom.
func (cva ContinuousLockingAccount) GetLockedCoinsWithDenoms(ctx context.Context, blockTime time.Time, denoms ...string) (sdk.Coins, error) {
lockedCoins := sdk.Coins{}
for _, denom := range denoms {
_, lockedCoin, err := cva.GetLockCoinInfoWithDenom(ctx, blockTime, denom)
if err != nil {
return nil, err
}
lockedCoins = append(lockedCoins, *lockedCoin)
}
return lockedCoins, nil
}
func (cva ContinuousLockingAccount) QueryLockupAccountInfo(ctx context.Context, req *lockuptypes.QueryLockupAccountInfoRequest) (
*lockuptypes.QueryLockupAccountInfoResponse, error,
) {
resp, err := cva.BaseLockup.QueryLockupAccountBaseInfo(ctx, req)
if err != nil {
return nil, err
}
startTime, err := cva.StartTime.Get(ctx)
if err != nil {
return nil, err
}
hs := cva.headerService.GetHeaderInfo(ctx)
unlockedCoins, lockedCoins, err := cva.GetLockCoinsInfo(ctx, hs.Time)
if err != nil {
return nil, err
}
resp.StartTime = &startTime
resp.LockedCoins = lockedCoins
resp.UnlockedCoins = unlockedCoins
return resp, nil
}
// Implement smart account interface
func (cva ContinuousLockingAccount) RegisterInitHandler(builder *accountstd.InitBuilder) {
accountstd.RegisterInitHandler(builder, cva.Init)
}
func (cva ContinuousLockingAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) {
accountstd.RegisterExecuteHandler(builder, cva.Delegate)
accountstd.RegisterExecuteHandler(builder, cva.Undelegate)
accountstd.RegisterExecuteHandler(builder, cva.SendCoins)
accountstd.RegisterExecuteHandler(builder, cva.WithdrawUnlockedCoins)
}
func (cva ContinuousLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, cva.QueryLockupAccountInfo)
}
@@ -0,0 +1,158 @@
package lockup
import (
"context"
"time"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/accountstd"
lockuptypes "cosmossdk.io/x/accounts/lockup/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Compile-time type assertions
var (
_ accountstd.Interface = (*DelayedLockingAccount)(nil)
)
// NewDelayedLockingAccount creates a new DelayedLockingAccount object.
func NewDelayedLockingAccount(d accountstd.Dependencies) (*DelayedLockingAccount, error) {
baseLockup := newBaseLockup(d)
return &DelayedLockingAccount{
baseLockup,
}, nil
}
type DelayedLockingAccount struct {
*BaseLockup
}
func (dva DelayedLockingAccount) Init(ctx context.Context, msg *lockuptypes.MsgInitLockupAccount) (*lockuptypes.MsgInitLockupAccountResponse, error) {
if msg.EndTime.IsZero() {
return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid end time %s", msg.EndTime.String())
}
return dva.BaseLockup.Init(ctx, msg)
}
func (dva *DelayedLockingAccount) Delegate(ctx context.Context, msg *lockuptypes.MsgDelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return dva.BaseLockup.Delegate(ctx, msg, dva.GetLockedCoinsWithDenoms)
}
func (dva *DelayedLockingAccount) Undelegate(ctx context.Context, msg *lockuptypes.MsgUndelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return dva.BaseLockup.Undelegate(ctx, msg)
}
func (dva *DelayedLockingAccount) SendCoins(ctx context.Context, msg *lockuptypes.MsgSend) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return dva.BaseLockup.SendCoins(ctx, msg, dva.GetLockedCoinsWithDenoms)
}
func (dva *DelayedLockingAccount) WithdrawUnlockedCoins(ctx context.Context, msg *lockuptypes.MsgWithdraw) (
*lockuptypes.MsgWithdrawResponse, error,
) {
return dva.BaseLockup.WithdrawUnlockedCoins(ctx, msg, dva.GetLockedCoinsWithDenoms)
}
// GetLockCoinsInfo returns the total number of unlocked and locked coins.
func (dva DelayedLockingAccount) GetLockCoinsInfo(ctx context.Context, blockTime time.Time) (sdk.Coins, sdk.Coins, error) {
endTime, err := dva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
originalLocking := sdk.Coins{}
err = dva.IterateCoinEntries(ctx, dva.OriginalLocking, func(key string, value math.Int) (stop bool, err error) {
originalLocking = append(originalLocking, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, nil, err
}
if blockTime.After(endTime) {
return originalLocking, sdk.Coins{}, nil
}
return sdk.Coins{}, originalLocking, nil
}
// GetLockedCoins returns the total number of locked coins. If no coins are
// locked, nil is returned.
func (dva DelayedLockingAccount) GetLockedCoins(ctx context.Context, blockTime time.Time) (sdk.Coins, error) {
_, lockedCoins, err := dva.GetLockCoinsInfo(ctx, blockTime)
if err != nil {
return nil, err
}
return lockedCoins, nil
}
// GetLockCoinInfoWithDenom returns the number of unlocked and locked coin for a specific denom.
func (dva DelayedLockingAccount) GetLockCoinInfoWithDenom(ctx context.Context, blockTime time.Time, denom string) (*sdk.Coin, *sdk.Coin, error) {
endTime, err := dva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
originalLockingAmt, err := dva.OriginalLocking.Get(ctx, denom)
if err != nil {
return nil, nil, err
}
originalLockingCoin := sdk.NewCoin(denom, originalLockingAmt)
if blockTime.After(endTime) {
return &originalLockingCoin, &sdk.Coin{}, nil
}
return &sdk.Coin{}, &originalLockingCoin, nil
}
// GetLockedCoinsWithDenoms returns the number of locked coin for a specific denom.
func (dva DelayedLockingAccount) GetLockedCoinsWithDenoms(ctx context.Context, blockTime time.Time, denoms ...string) (sdk.Coins, error) {
vestingCoins := sdk.Coins{}
for _, denom := range denoms {
_, vestingCoin, err := dva.GetLockCoinInfoWithDenom(ctx, blockTime, denom)
if err != nil {
return nil, err
}
vestingCoins = append(vestingCoins, *vestingCoin)
}
return vestingCoins, nil
}
func (dva DelayedLockingAccount) QueryVestingAccountInfo(ctx context.Context, req *lockuptypes.QueryLockupAccountInfoRequest) (
*lockuptypes.QueryLockupAccountInfoResponse, error,
) {
resp, err := dva.BaseLockup.QueryLockupAccountBaseInfo(ctx, req)
if err != nil {
return nil, err
}
hs := dva.headerService.GetHeaderInfo(ctx)
unlockedCoins, lockedCoins, err := dva.GetLockCoinsInfo(ctx, hs.Time)
if err != nil {
return nil, err
}
resp.LockedCoins = lockedCoins
resp.UnlockedCoins = unlockedCoins
return resp, nil
}
// Implement smart account interface
func (dva DelayedLockingAccount) RegisterInitHandler(builder *accountstd.InitBuilder) {
accountstd.RegisterInitHandler(builder, dva.Init)
}
func (dva DelayedLockingAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) {
accountstd.RegisterExecuteHandler(builder, dva.Delegate)
accountstd.RegisterExecuteHandler(builder, dva.Undelegate)
accountstd.RegisterExecuteHandler(builder, dva.SendCoins)
accountstd.RegisterExecuteHandler(builder, dva.WithdrawUnlockedCoins)
}
func (dva DelayedLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, dva.QueryVestingAccountInfo)
}
+184
View File
@@ -0,0 +1,184 @@
module cosmossdk.io/x/accounts/lockup
go 1.21
require (
cosmossdk.io/collections v0.4.0
cosmossdk.io/core v0.12.1-0.20231114100755-569e3ff6a0d7
cosmossdk.io/x/accounts v0.0.0-20240226161501-23359a0b6d91
cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91
github.com/cosmos/cosmos-sdk v0.51.0
github.com/cosmos/gogoproto v1.4.11
)
require (
cosmossdk.io/api v0.7.3
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.33.0
)
require (
cosmossdk.io/errors v1.0.1
cosmossdk.io/log v1.3.1 // indirect
cosmossdk.io/math v1.3.0
cosmossdk.io/store v1.0.2 // indirect
cosmossdk.io/x/tx v0.13.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/DataDog/datadog-go v4.8.3+incompatible // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cometbft/cometbft v0.38.5 // indirect
github.com/cometbft/cometbft-db v0.11.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.4
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v1.0.1 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/emicklei/dot v1.6.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.3 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/linxGnu/grocksdb v1.8.12 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.50.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.10.1 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
nhooyr.io/websocket v1.8.10 // indirect
pgregory.net/rapid v1.1.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
require (
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.32.0-20240130113600-88ef6483f90f.1 // indirect
buf.build/gen/go/tendermint/tendermint/protocolbuffers/go v1.32.0-20231117195010-33ed361a9051.1 // indirect
cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
)
replace github.com/cosmos/cosmos-sdk => ../../../../.
replace (
cosmossdk.io/api => ../../../../api
cosmossdk.io/core => ../../../../core
cosmossdk.io/depinject => ../../../../depinject
cosmossdk.io/x/accounts => ../../.
cosmossdk.io/x/auth => ../../../auth
cosmossdk.io/x/bank => ../../../bank
cosmossdk.io/x/distribution => ../../../distribution
cosmossdk.io/x/gov => ../../../gov
cosmossdk.io/x/mint => ../../../mint
cosmossdk.io/x/protocolpool => ../../../protocolpool
cosmossdk.io/x/slashing => ../../../slashing
cosmossdk.io/x/staking => ../../../staking
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1
)
File diff suppressed because it is too large Load Diff
+587
View File
@@ -0,0 +1,587 @@
package lockup
import (
"bytes"
"context"
"fmt"
"time"
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
"cosmossdk.io/core/address"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/accountstd"
lockuptypes "cosmossdk.io/x/accounts/lockup/types"
banktypes "cosmossdk.io/x/bank/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/gogoproto/proto"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var (
OriginalLockingPrefix = collections.NewPrefix(0)
DelegatedFreePrefix = collections.NewPrefix(1)
DelegatedLockingPrefix = collections.NewPrefix(2)
EndTimePrefix = collections.NewPrefix(3)
StartTimePrefix = collections.NewPrefix(4)
LockingPeriodsPrefix = collections.NewPrefix(5)
OwnerPrefix = collections.NewPrefix(6)
WithdrawedCoinsPrefix = collections.NewPrefix(7)
)
var (
CONTINUOUS_LOCKING_ACCOUNT = "continuous-locking-account"
DELAYED_LOCKING_ACCOUNT = "delayed-locking-account"
PERIODIC_LOCKING_ACCOUNT = "periodic-locking-account"
PERMANENT_LOCKING_ACCOUNT = "permanent-locking-account"
)
type getLockedCoinsFunc = func(ctx context.Context, time time.Time, denoms ...string) (sdk.Coins, error)
// newBaseLockup creates a new BaseLockup object.
func newBaseLockup(d accountstd.Dependencies) *BaseLockup {
BaseLockup := &BaseLockup{
Owner: collections.NewItem(d.SchemaBuilder, OwnerPrefix, "owner", collections.BytesValue),
OriginalLocking: collections.NewMap(d.SchemaBuilder, OriginalLockingPrefix, "original_locking", collections.StringKey, sdk.IntValue),
DelegatedFree: collections.NewMap(d.SchemaBuilder, DelegatedFreePrefix, "delegated_free", collections.StringKey, sdk.IntValue),
DelegatedLocking: collections.NewMap(d.SchemaBuilder, DelegatedLockingPrefix, "delegated_locking", collections.StringKey, sdk.IntValue),
WithdrawedCoins: collections.NewMap(d.SchemaBuilder, WithdrawedCoinsPrefix, "withdrawed_coins", collections.StringKey, sdk.IntValue),
addressCodec: d.AddressCodec,
headerService: d.Environment.HeaderService,
EndTime: collections.NewItem(d.SchemaBuilder, EndTimePrefix, "end_time", collcodec.KeyToValueCodec[time.Time](sdk.TimeKey)),
}
return BaseLockup
}
type BaseLockup struct {
// Owner is the address of the account owner.
Owner collections.Item[[]byte]
OriginalLocking collections.Map[string, math.Int]
DelegatedFree collections.Map[string, math.Int]
DelegatedLocking collections.Map[string, math.Int]
WithdrawedCoins collections.Map[string, math.Int]
addressCodec address.Codec
headerService header.Service
// lockup end time.
EndTime collections.Item[time.Time]
}
func (bva *BaseLockup) Init(ctx context.Context, msg *lockuptypes.MsgInitLockupAccount) (
*lockuptypes.MsgInitLockupAccountResponse, error,
) {
owner, err := bva.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid 'owner' address: %s", err)
}
err = bva.Owner.Set(ctx, owner)
if err != nil {
return nil, err
}
funds := accountstd.Funds(ctx)
sortedAmt := funds.Sort()
for _, coin := range sortedAmt {
err = bva.OriginalLocking.Set(ctx, coin.Denom, coin.Amount)
if err != nil {
return nil, err
}
// Set initial value for all locked token
err = bva.WithdrawedCoins.Set(ctx, coin.Denom, math.ZeroInt())
if err != nil {
return nil, err
}
// Set initial value for all locked token
err = bva.DelegatedFree.Set(ctx, coin.Denom, math.ZeroInt())
if err != nil {
return nil, err
}
// Set initial value for all locked token
err = bva.DelegatedLocking.Set(ctx, coin.Denom, math.ZeroInt())
if err != nil {
return nil, err
}
}
err = bva.EndTime.Set(ctx, msg.EndTime)
if err != nil {
return nil, err
}
return &lockuptypes.MsgInitLockupAccountResponse{}, nil
}
func (bva *BaseLockup) Delegate(
ctx context.Context, msg *lockuptypes.MsgDelegate, getLockedCoinsFunc getLockedCoinsFunc,
) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
err := bva.checkSender(ctx, msg.Sender)
if err != nil {
return nil, err
}
whoami := accountstd.Whoami(ctx)
delegatorAddress, err := bva.addressCodec.BytesToString(whoami)
if err != nil {
return nil, err
}
hs := bva.headerService.GetHeaderInfo(ctx)
balance, err := bva.getBalance(ctx, delegatorAddress, msg.Amount.Denom)
if err != nil {
return nil, err
}
lockedCoins, err := getLockedCoinsFunc(ctx, hs.Time, msg.Amount.Denom)
if err != nil {
return nil, err
}
err = bva.TrackDelegation(
ctx,
sdk.Coins{*balance},
lockedCoins,
sdk.Coins{msg.Amount},
)
if err != nil {
return nil, err
}
msgDelegate := makeMsgDelegate(delegatorAddress, msg.ValidatorAddress, msg.Amount)
responses, err := sendMessage(ctx, msgDelegate)
if err != nil {
return nil, err
}
return &lockuptypes.MsgExecuteMessagesResponse{Responses: responses}, nil
}
func (bva *BaseLockup) Undelegate(
ctx context.Context, msg *lockuptypes.MsgUndelegate,
) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
err := bva.checkSender(ctx, msg.Sender)
if err != nil {
return nil, err
}
whoami := accountstd.Whoami(ctx)
delegatorAddress, err := bva.addressCodec.BytesToString(whoami)
if err != nil {
return nil, err
}
err = bva.TrackUndelegation(ctx, sdk.Coins{msg.Amount})
if err != nil {
return nil, err
}
msgUndelegate := makeMsgUndelegate(delegatorAddress, msg.ValidatorAddress, msg.Amount)
responses, err := sendMessage(ctx, msgUndelegate)
if err != nil {
return nil, err
}
return &lockuptypes.MsgExecuteMessagesResponse{Responses: responses}, nil
}
func (bva *BaseLockup) SendCoins(
ctx context.Context, msg *lockuptypes.MsgSend, getLockedCoinsFunc getLockedCoinsFunc,
) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
err := bva.checkSender(ctx, msg.Sender)
if err != nil {
return nil, err
}
whoami := accountstd.Whoami(ctx)
fromAddress, err := bva.addressCodec.BytesToString(whoami)
if err != nil {
return nil, err
}
hs := bva.headerService.GetHeaderInfo(ctx)
lockedCoins, err := getLockedCoinsFunc(ctx, hs.Time, msg.Amount.Denoms()...)
if err != nil {
return nil, err
}
err = bva.checkTokensSendable(ctx, fromAddress, msg.Amount, lockedCoins)
if err != nil {
return nil, err
}
msgSend := makeMsgSend(fromAddress, msg.ToAddress, msg.Amount)
responses, err := sendMessage(ctx, msgSend)
if err != nil {
return nil, err
}
return &lockuptypes.MsgExecuteMessagesResponse{Responses: responses}, nil
}
// WithdrawUnlockedCoins allow owner to withdraw the unlocked token for a specific denoms to an
// account of choice. Update the withdrawed token tracking for lockup account
func (bva *BaseLockup) WithdrawUnlockedCoins(
ctx context.Context, msg *lockuptypes.MsgWithdraw, getLockedCoinsFunc getLockedCoinsFunc,
) (
*lockuptypes.MsgWithdrawResponse, error,
) {
err := bva.checkSender(ctx, msg.Withdrawer)
if err != nil {
return nil, err
}
whoami := accountstd.Whoami(ctx)
fromAddress, err := bva.addressCodec.BytesToString(whoami)
if err != nil {
return nil, err
}
hs := bva.headerService.GetHeaderInfo(ctx)
lockedCoins, err := getLockedCoinsFunc(ctx, hs.Time, msg.Denoms...)
if err != nil {
return nil, err
}
amount := sdk.Coins{}
for _, denom := range msg.Denoms {
balance, err := bva.getBalance(ctx, fromAddress, denom)
if err != nil {
return nil, err
}
lockedAmt := lockedCoins.AmountOf(denom)
// get lockedCoin from that are not bonded for the sent denom
notBondedLockedCoin, err := bva.GetNotBondedLockedCoin(ctx, sdk.NewCoin(denom, lockedAmt), denom)
if err != nil {
return nil, err
}
spendable, err := balance.SafeSub(notBondedLockedCoin)
if err != nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds,
"locked amount exceeds account balance funds: %s > %s", notBondedLockedCoin, balance)
}
withdrawedAmt, err := bva.WithdrawedCoins.Get(ctx, denom)
if err != nil {
return nil, err
}
originalLockingAmt, err := bva.OriginalLocking.Get(ctx, denom)
if err != nil {
return nil, err
}
// withdrawable amount is equal to original locking amount subtract already withdrawed amount
withdrawableAmt, err := originalLockingAmt.SafeSub(withdrawedAmt)
if err != nil {
return nil, err
}
withdrawAmt := math.MinInt(withdrawableAmt, spendable.Amount)
// if zero amount go to the next iteration
if withdrawAmt.IsZero() {
continue
}
amount = append(amount, sdk.NewCoin(denom, withdrawAmt))
// update the withdrawed amount
err = bva.WithdrawedCoins.Set(ctx, denom, withdrawedAmt.Add(withdrawAmt))
if err != nil {
return nil, err
}
}
if len(amount) == 0 {
return nil, fmt.Errorf("no tokens available for withdrawing")
}
msgSend := makeMsgSend(fromAddress, msg.ToAddress, amount)
_, err = sendMessage(ctx, msgSend)
if err != nil {
return nil, err
}
return &lockuptypes.MsgWithdrawResponse{
Reciever: msg.ToAddress,
AmountReceived: amount,
}, nil
}
func (bva *BaseLockup) checkSender(ctx context.Context, sender string) error {
owner, err := bva.Owner.Get(ctx)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid owner address: %s", err.Error())
}
senderBytes, err := bva.addressCodec.StringToBytes(sender)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid sender address: %s", err.Error())
}
if !bytes.Equal(owner, senderBytes) {
return fmt.Errorf("sender is not the owner of this vesting account")
}
return nil
}
func sendMessage(ctx context.Context, msg proto.Message) ([]*codectypes.Any, error) {
response, err := accountstd.ExecModuleUntyped(ctx, msg)
if err != nil {
return nil, err
}
respAny, err := accountstd.PackAny(response)
if err != nil {
return nil, err
}
return []*codectypes.Any{respAny}, nil
}
// TrackDelegation tracks a delegation amount for any given lockup account type
// given the amount of coins currently being locked and the current account balance
// of the delegation denominations.
//
// CONTRACT: The account's coins, delegation coins, locked coins, and delegated
// locking coins must be sorted.
func (bva *BaseLockup) TrackDelegation(
ctx context.Context, balance, lockedCoins, amount sdk.Coins,
) error {
for _, coin := range amount {
baseAmt := balance.AmountOf(coin.Denom)
lockedAmt := lockedCoins.AmountOf(coin.Denom)
delLockingAmt, err := bva.DelegatedLocking.Get(ctx, coin.Denom)
if err != nil {
return err
}
delFreeAmt, err := bva.DelegatedFree.Get(ctx, coin.Denom)
if err != nil {
return err
}
// return error if the delegation amount is zero or if the base coins does not
// exceed the desired delegation amount.
if coin.Amount.IsZero() || baseAmt.LT(coin.Amount) {
return sdkerrors.ErrInvalidCoins.Wrap("delegation attempt with zero coins or insufficient funds")
}
// compute x and y per the specification, where:
// X := min(max(V - DV, 0), D)
// Y := D - X
x := math.MinInt(math.MaxInt(lockedAmt.Sub(delLockingAmt), math.ZeroInt()), coin.Amount)
y := coin.Amount.Sub(x)
delLockingCoin := sdk.NewCoin(coin.Denom, delLockingAmt)
delFreeCoin := sdk.NewCoin(coin.Denom, delFreeAmt)
if !x.IsZero() {
xCoin := sdk.NewCoin(coin.Denom, x)
newDelLocking := delLockingCoin.Add(xCoin)
err = bva.DelegatedLocking.Set(ctx, newDelLocking.Denom, newDelLocking.Amount)
if err != nil {
return err
}
}
if !y.IsZero() {
yCoin := sdk.NewCoin(coin.Denom, y)
newDelFree := delFreeCoin.Add(yCoin)
err = bva.DelegatedFree.Set(ctx, newDelFree.Denom, newDelFree.Amount)
if err != nil {
return err
}
}
}
return nil
}
// TrackUndelegation tracks an undelegation amount by setting the necessary
// values by which delegated locking and delegated free need to decrease and
// by which amount the base coins need to increase.
//
// NOTE: The undelegation (bond refund) amount may exceed the delegated
// locking (bond) amount due to the way undelegation truncates the bond refund,
// which can increase the validator's exchange rate (tokens/shares) slightly if
// the undelegated tokens are non-integral.
//
// CONTRACT: The account's coins and undelegation coins must be sorted.
func (bva *BaseLockup) TrackUndelegation(ctx context.Context, amount sdk.Coins) error {
for _, coin := range amount {
// return error if the undelegation amount is zero
if coin.Amount.IsZero() {
return sdkerrors.ErrInvalidCoins.Wrap("undelegation attempt with zero coins")
}
delFreeAmt, err := bva.DelegatedFree.Get(ctx, coin.Denom)
if err != nil {
return err
}
delLockingAmt, err := bva.DelegatedLocking.Get(ctx, coin.Denom)
if err != nil {
return err
}
// compute x and y per the specification, where:
// X := min(DF, D)
// Y := min(DV, D - X)
x := math.MinInt(delFreeAmt, coin.Amount)
y := math.MinInt(delLockingAmt, coin.Amount.Sub(x))
delLockingCoin := sdk.NewCoin(coin.Denom, delLockingAmt)
delFreeCoin := sdk.NewCoin(coin.Denom, delFreeAmt)
if !x.IsZero() {
xCoin := sdk.NewCoin(coin.Denom, x)
newDelFree := delFreeCoin.Sub(xCoin)
err = bva.DelegatedFree.Set(ctx, newDelFree.Denom, newDelFree.Amount)
if err != nil {
return err
}
}
if !y.IsZero() {
yCoin := sdk.NewCoin(coin.Denom, y)
newDelLocking := delLockingCoin.Sub(yCoin)
err = bva.DelegatedLocking.Set(ctx, newDelLocking.Denom, newDelLocking.Amount)
if err != nil {
return err
}
}
}
return nil
}
func (bva BaseLockup) getBalance(ctx context.Context, sender, denom string) (*sdk.Coin, error) {
// Query account balance for the sent denom
balanceQueryReq := banktypes.NewQueryBalanceRequest(sdk.AccAddress(sender), denom)
resp, err := accountstd.QueryModule[banktypes.QueryBalanceResponse](ctx, balanceQueryReq)
if err != nil {
return nil, err
}
return resp.Balance, nil
}
func (bva BaseLockup) checkTokensSendable(ctx context.Context, sender string, amount, lockedCoins sdk.Coins) error {
// Check if any sent tokens is exceeds lockup account balances
for _, coin := range amount {
balance, err := bva.getBalance(ctx, sender, coin.Denom)
if err != nil {
return err
}
lockedAmt := lockedCoins.AmountOf(coin.Denom)
// get lockedCoin from that are not bonded for the sent denom
notBondedLockedCoin, err := bva.GetNotBondedLockedCoin(ctx, sdk.NewCoin(coin.Denom, lockedAmt), coin.Denom)
if err != nil {
return err
}
spendable, hasNeg := sdk.Coins{*balance}.SafeSub(notBondedLockedCoin)
if hasNeg {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds,
"locked amount exceeds account balance funds: %s > %s", notBondedLockedCoin, balance)
}
if _, hasNeg := spendable.SafeSub(coin); hasNeg {
if len(spendable) == 0 {
spendable = sdk.Coins{sdk.NewCoin(coin.Denom, math.ZeroInt())}
}
return errorsmod.Wrapf(
sdkerrors.ErrInsufficientFunds,
"spendable balance %s is smaller than %s",
spendable, coin,
)
}
}
return nil
}
// IterateSendEnabledEntries iterates over all the SendEnabled entries.
func (bva BaseLockup) IterateCoinEntries(
ctx context.Context,
entries collections.Map[string, math.Int],
cb func(denom string, value math.Int) (bool, error),
) error {
err := entries.Walk(ctx, nil, func(key string, value math.Int) (stop bool, err error) {
return cb(key, value)
})
return err
}
// GetNotBondedLockedCoin returns the coin that are not spendable that are not bonded by denom
// for a lockup account. If the coin by the provided denom are not locked, an coin with zero amount is returned.
func (bva BaseLockup) GetNotBondedLockedCoin(ctx context.Context, lockedCoin sdk.Coin, denom string) (sdk.Coin, error) {
delegatedLockingAmt, err := bva.DelegatedLocking.Get(ctx, denom)
if err != nil {
return sdk.Coin{}, err
}
x := math.MinInt(lockedCoin.Amount, delegatedLockingAmt)
lockedAmt := lockedCoin.Amount.Sub(x)
return sdk.NewCoin(denom, lockedAmt), nil
}
// QueryLockupAccountBaseInfo returns a lockup account's info
func (bva BaseLockup) QueryLockupAccountBaseInfo(ctx context.Context, _ *lockuptypes.QueryLockupAccountInfoRequest) (
*lockuptypes.QueryLockupAccountInfoResponse, error,
) {
owner, err := bva.Owner.Get(ctx)
if err != nil {
return nil, err
}
ownerAddress, err := bva.addressCodec.BytesToString(owner)
if err != nil {
return nil, err
}
endTime, err := bva.EndTime.Get(ctx)
if err != nil {
return nil, err
}
originalLocking := sdk.Coins{}
err = bva.IterateCoinEntries(ctx, bva.OriginalLocking, func(key string, value math.Int) (stop bool, err error) {
originalLocking = append(originalLocking, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, err
}
delegatedLocking := sdk.Coins{}
err = bva.IterateCoinEntries(ctx, bva.DelegatedLocking, func(key string, value math.Int) (stop bool, err error) {
delegatedLocking = append(delegatedLocking, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, err
}
delegatedFree := sdk.Coins{}
err = bva.IterateCoinEntries(ctx, bva.DelegatedFree, func(key string, value math.Int) (stop bool, err error) {
delegatedFree = append(delegatedFree, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, err
}
return &lockuptypes.QueryLockupAccountInfoResponse{
Owner: ownerAddress,
OriginalLocking: originalLocking,
DelegatedLocking: delegatedLocking,
DelegatedFree: delegatedFree,
EndTime: &endTime,
}, nil
}
@@ -0,0 +1,337 @@
package lockup
import (
"context"
"time"
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/accountstd"
lockuptypes "cosmossdk.io/x/accounts/lockup/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Compile-time type assertions
var (
_ accountstd.Interface = (*PeriodicLockingAccount)(nil)
)
// NewPeriodicLockingAccount creates a new PeriodicLockingAccount object.
func NewPeriodicLockingAccount(d accountstd.Dependencies) (*PeriodicLockingAccount, error) {
baseLockup := newBaseLockup(d)
periodicsVestingAccount := PeriodicLockingAccount{
BaseLockup: baseLockup,
StartTime: collections.NewItem(d.SchemaBuilder, StartTimePrefix, "start_time", collcodec.KeyToValueCodec[time.Time](sdk.TimeKey)),
LockingPeriods: collections.NewVec(d.SchemaBuilder, LockingPeriodsPrefix, "locking_periods", codec.CollValue[lockuptypes.Period](d.LegacyStateCodec)),
}
return &periodicsVestingAccount, nil
}
type PeriodicLockingAccount struct {
*BaseLockup
StartTime collections.Item[time.Time]
LockingPeriods collections.Vec[lockuptypes.Period]
}
func (pva PeriodicLockingAccount) Init(ctx context.Context, msg *lockuptypes.MsgInitPeriodicLockingAccount) (*lockuptypes.MsgInitPeriodicLockingAccountResponse, error) {
owner, err := pva.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid 'owner' address: %s", err)
}
hs := pva.headerService.GetHeaderInfo(ctx)
if msg.StartTime.Before(hs.Time) {
return nil, sdkerrors.ErrInvalidRequest.Wrap("start time %s should be after block time")
}
totalCoins := sdk.Coins{}
endTime := msg.StartTime
for _, period := range msg.LockingPeriods {
if period.Length.Seconds() <= 0 {
return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid period duration length %d", period.Length)
}
if err := validateAmount(period.Amount); err != nil {
return nil, err
}
totalCoins = totalCoins.Add(period.Amount...)
// Calculate end time
endTime = endTime.Add(period.Length)
err = pva.LockingPeriods.Push(ctx, period)
if err != nil {
return nil, err
}
}
funds := accountstd.Funds(ctx)
if !funds.Equal(totalCoins) {
return nil, sdkerrors.ErrInvalidRequest.Wrap("invalid funding amount, should be equal to total coins lockup")
}
sortedAmt := totalCoins.Sort()
for _, coin := range sortedAmt {
err := pva.OriginalLocking.Set(ctx, coin.Denom, coin.Amount)
if err != nil {
return nil, err
}
}
err = pva.StartTime.Set(ctx, msg.StartTime)
if err != nil {
return nil, err
}
err = pva.EndTime.Set(ctx, endTime)
if err != nil {
return nil, err
}
err = pva.Owner.Set(ctx, owner)
if err != nil {
return nil, err
}
return &lockuptypes.MsgInitPeriodicLockingAccountResponse{}, nil
}
func (pva *PeriodicLockingAccount) Delegate(ctx context.Context, msg *lockuptypes.MsgDelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return pva.BaseLockup.Delegate(ctx, msg, pva.GetLockedCoinsWithDenoms)
}
func (pva *PeriodicLockingAccount) Undelegate(ctx context.Context, msg *lockuptypes.MsgUndelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return pva.BaseLockup.Undelegate(ctx, msg)
}
func (pva *PeriodicLockingAccount) SendCoins(ctx context.Context, msg *lockuptypes.MsgSend) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return pva.BaseLockup.SendCoins(ctx, msg, pva.GetLockedCoinsWithDenoms)
}
func (pva *PeriodicLockingAccount) WithdrawUnlockedCoins(ctx context.Context, msg *lockuptypes.MsgWithdraw) (
*lockuptypes.MsgWithdrawResponse, error,
) {
return pva.BaseLockup.WithdrawUnlockedCoins(ctx, msg, pva.GetLockedCoinsWithDenoms)
}
// IterateSendEnabledEntries iterates over all the SendEnabled entries.
func (pva PeriodicLockingAccount) IteratePeriods(
ctx context.Context,
cb func(value lockuptypes.Period) (bool, error),
) error {
err := pva.LockingPeriods.Walk(ctx, nil, func(_ uint64, value lockuptypes.Period) (stop bool, err error) {
return cb(value)
})
if err != nil {
return err
}
return nil
}
// GetLockCoinsInfo returns the total number of locked and unlocked coins.
func (pva PeriodicLockingAccount) GetLockCoinsInfo(ctx context.Context, blockTime time.Time) (unlockedCoins, lockedCoins sdk.Coins, err error) {
unlockedCoins = sdk.Coins{}
lockedCoins = sdk.Coins{}
// We must handle the case where the start time for a lockup account has
// been set into the future or when the start of the chain is not exactly
// known.
startTime, err := pva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
endTime, err := pva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
originalLocking := sdk.Coins{}
err = pva.IterateCoinEntries(ctx, pva.OriginalLocking, func(key string, value math.Int) (stop bool, err error) {
originalLocking = append(originalLocking, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, nil, err
}
if blockTime.Before(startTime) {
return unlockedCoins, originalLocking, nil
} else if blockTime.After(endTime) {
return originalLocking, lockedCoins, nil
}
// track the start time of the next period
currentPeriodStartTime, err := pva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
err = pva.IteratePeriods(ctx, func(period lockuptypes.Period) (stop bool, err error) {
x := blockTime.Sub(currentPeriodStartTime)
if x.Seconds() < period.Length.Seconds() {
return true, nil
}
unlockedCoins = unlockedCoins.Add(period.Amount...)
// update the start time of the next period
err = pva.StartTime.Set(ctx, currentPeriodStartTime.Add(period.Length))
if err != nil {
return true, err
}
return false, nil
})
if err != nil {
return nil, nil, err
}
lockedCoins = originalLocking.Sub(unlockedCoins...)
return unlockedCoins, lockedCoins, err
}
// GetLockedCoins returns the total number of locked coins. If no coins are
// locked, nil is returned.
func (pva PeriodicLockingAccount) GetLockedCoins(ctx context.Context, blockTime time.Time) (sdk.Coins, error) {
_, vestingCoins, err := pva.GetLockCoinsInfo(ctx, blockTime)
if err != nil {
return nil, err
}
return vestingCoins, nil
}
// GetLockCoinInfoWithDenom returns the total number of locked and unlocked coin for a specific denom.
func (pva PeriodicLockingAccount) GetLockCoinInfoWithDenom(ctx context.Context, blockTime time.Time, denom string) (unlockedCoin, lockedCoin *sdk.Coin, err error) {
// We must handle the case where the start time for a lockup account has
// been set into the future or when the start of the chain is not exactly
// known.
startTime, err := pva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
endTime, err := pva.EndTime.Get(ctx)
if err != nil {
return nil, nil, err
}
originalLockingAmt, err := pva.OriginalLocking.Get(ctx, denom)
if err != nil {
return nil, nil, err
}
originalLockingCoin := sdk.NewCoin(denom, originalLockingAmt)
if blockTime.Before(startTime) {
return &sdk.Coin{}, &originalLockingCoin, nil
} else if blockTime.After(endTime) {
return &originalLockingCoin, &sdk.Coin{}, nil
}
// track the start time of the next period
currentPeriodStartTime, err := pva.StartTime.Get(ctx)
if err != nil {
return nil, nil, err
}
unlocked := sdk.NewCoin(denom, math.ZeroInt())
err = pva.IteratePeriods(ctx, func(period lockuptypes.Period) (stop bool, err error) {
x := blockTime.Sub(currentPeriodStartTime)
if x.Seconds() < period.Length.Seconds() {
return true, nil
}
unlocked = unlocked.Add(sdk.NewCoin(denom, period.Amount.AmountOf(denom)))
// update the start time of the next period
err = pva.StartTime.Set(ctx, currentPeriodStartTime.Add(period.Length))
if err != nil {
return true, err
}
return false, nil
})
if err != nil {
return nil, nil, err
}
locked := originalLockingCoin.Sub(unlocked)
return &unlocked, &locked, err
}
// GetLockedCoinsWithDenoms returns the total number of locked coins. If no coins are
// locked, nil is returned.
func (pva PeriodicLockingAccount) GetLockedCoinsWithDenoms(ctx context.Context, blockTime time.Time, denoms ...string) (sdk.Coins, error) {
lockedCoins := sdk.Coins{}
for _, denom := range denoms {
_, lockedCoin, err := pva.GetLockCoinInfoWithDenom(ctx, blockTime, denom)
if err != nil {
return nil, err
}
lockedCoins = append(lockedCoins, *lockedCoin)
}
return lockedCoins, nil
}
func (pva PeriodicLockingAccount) QueryLockupAccountInfo(ctx context.Context, req *lockuptypes.QueryLockupAccountInfoRequest) (
*lockuptypes.QueryLockupAccountInfoResponse, error,
) {
resp, err := pva.BaseLockup.QueryLockupAccountBaseInfo(ctx, req)
if err != nil {
return nil, err
}
startTime, err := pva.StartTime.Get(ctx)
if err != nil {
return nil, err
}
hs := pva.headerService.GetHeaderInfo(ctx)
unlockedCoins, lockedCoins, err := pva.GetLockCoinsInfo(ctx, hs.Time)
if err != nil {
return nil, err
}
resp.StartTime = &startTime
resp.LockedCoins = lockedCoins
resp.UnlockedCoins = unlockedCoins
return resp, nil
}
func (pva PeriodicLockingAccount) QueryLockingPeriods(ctx context.Context, msg *lockuptypes.QueryLockingPeriodsRequest) (
*lockuptypes.QueryLockingPeriodsResponse, error,
) {
lockingPeriods := []*lockuptypes.Period{}
err := pva.IteratePeriods(ctx, func(period lockuptypes.Period) (stop bool, err error) {
lockingPeriods = append(lockingPeriods, &period)
return false, nil
})
if err != nil {
return nil, err
}
return &lockuptypes.QueryLockingPeriodsResponse{
LockingPeriods: lockingPeriods,
}, nil
}
// Implement smart account interface
func (pva PeriodicLockingAccount) RegisterInitHandler(builder *accountstd.InitBuilder) {
accountstd.RegisterInitHandler(builder, pva.Init)
}
func (pva PeriodicLockingAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) {
accountstd.RegisterExecuteHandler(builder, pva.Delegate)
accountstd.RegisterExecuteHandler(builder, pva.Undelegate)
accountstd.RegisterExecuteHandler(builder, pva.SendCoins)
accountstd.RegisterExecuteHandler(builder, pva.WithdrawUnlockedCoins)
}
func (pva PeriodicLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, pva.QueryLockupAccountInfo)
accountstd.RegisterQueryHandler(builder, pva.QueryLockingPeriods)
}
@@ -0,0 +1,109 @@
package lockup
import (
"context"
"time"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/accountstd"
lockuptypes "cosmossdk.io/x/accounts/lockup/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Compile-time type assertions
var (
_ accountstd.Interface = (*PermanentLockingAccount)(nil)
)
// NewPermanentLockingAccount creates a new PermanentLockingAccount object.
func NewPermanentLockingAccount(d accountstd.Dependencies) (*PermanentLockingAccount, error) {
baseLockup := newBaseLockup(d)
return &PermanentLockingAccount{baseLockup}, nil
}
type PermanentLockingAccount struct {
*BaseLockup
}
func (plva PermanentLockingAccount) Init(ctx context.Context, msg *lockuptypes.MsgInitLockupAccount) (*lockuptypes.MsgInitLockupAccountResponse, error) {
resp, err := plva.BaseLockup.Init(ctx, msg)
if err != nil {
return nil, err
}
err = plva.EndTime.Set(ctx, time.Time{})
if err != nil {
return nil, err
}
return resp, err
}
// GetlockedCoinsWithDenoms returns the total number of locked coins. If no coins are
// locked, nil is returned.
func (plva PermanentLockingAccount) GetlockedCoinsWithDenoms(ctx context.Context, blockTime time.Time, denoms ...string) (sdk.Coins, error) {
vestingCoins := sdk.Coins{}
for _, denom := range denoms {
originalVestingAmt, err := plva.OriginalLocking.Get(ctx, denom)
if err != nil {
return nil, err
}
vestingCoins = append(vestingCoins, sdk.NewCoin(denom, originalVestingAmt))
}
return vestingCoins, nil
}
func (plva *PermanentLockingAccount) Delegate(ctx context.Context, msg *lockuptypes.MsgDelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return plva.BaseLockup.Delegate(ctx, msg, plva.GetlockedCoinsWithDenoms)
}
func (plva *PermanentLockingAccount) Undelegate(ctx context.Context, msg *lockuptypes.MsgUndelegate) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return plva.BaseLockup.Undelegate(ctx, msg)
}
func (plva *PermanentLockingAccount) SendCoins(ctx context.Context, msg *lockuptypes.MsgSend) (
*lockuptypes.MsgExecuteMessagesResponse, error,
) {
return plva.BaseLockup.SendCoins(ctx, msg, plva.GetlockedCoinsWithDenoms)
}
func (plva PermanentLockingAccount) QueryLockupAccountInfo(ctx context.Context, req *lockuptypes.QueryLockupAccountInfoRequest) (
*lockuptypes.QueryLockupAccountInfoResponse, error,
) {
resp, err := plva.BaseLockup.QueryLockupAccountBaseInfo(ctx, req)
if err != nil {
return nil, err
}
originalLocking := sdk.Coins{}
err = plva.IterateCoinEntries(ctx, plva.OriginalLocking, func(key string, value math.Int) (stop bool, err error) {
originalLocking = append(originalLocking, sdk.NewCoin(key, value))
return false, nil
})
if err != nil {
return nil, err
}
resp.LockedCoins = originalLocking
resp.UnlockedCoins = sdk.Coins{}
return resp, nil
}
// Implement smart account interface
func (plva PermanentLockingAccount) RegisterInitHandler(builder *accountstd.InitBuilder) {
accountstd.RegisterInitHandler(builder, plva.Init)
}
func (plva PermanentLockingAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) {
accountstd.RegisterExecuteHandler(builder, plva.Delegate)
accountstd.RegisterExecuteHandler(builder, plva.Undelegate)
accountstd.RegisterExecuteHandler(builder, plva.SendCoins)
}
func (plva PermanentLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, plva.QueryLockupAccountInfo)
}
@@ -0,0 +1,70 @@
package lockup
import (
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
stakingv1beta1 "cosmossdk.io/api/cosmos/staking/v1beta1"
sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/runtime/protoiface"
)
type ProtoMsg = protoiface.MessageV1
type gogoProtoPlusV2 interface {
proto.Message
ProtoMsg
}
// protoV2GogoWrapper is a wrapper of a protov2 message into a gogo message.
// this is exceptionally allowed to enable accounts to be decoupled from
// the SDK, since x/accounts can support only protov1 in its APIs.
// But in order to keep it decoupled from the SDK we need to use the API module.
// This is a temporary solution that is being used here:
// https://github.com/cosmos/cosmos-sdk/blob/main/x/accounts/coin_transfer.go
type protoV2GogoWrapper struct {
gogoProtoPlusV2
}
func (h protoV2GogoWrapper) XXX_MessageName() string {
return string(proto.MessageName(h.gogoProtoPlusV2))
}
func makeMsgSend(fromAddr, toAddr string, coins sdk.Coins) ProtoMsg {
v2Coins := make([]*v1beta1.Coin, len(coins))
for i, coin := range coins {
v2Coins[i] = &v1beta1.Coin{
Denom: coin.Denom,
Amount: coin.Amount.String(),
}
}
return protoV2GogoWrapper{&bankv1beta1.MsgSend{
FromAddress: fromAddr,
ToAddress: toAddr,
Amount: v2Coins,
}}
}
func makeMsgDelegate(delegatorAddr, validatorAddr string, amount sdk.Coin) ProtoMsg {
v2Coin := &v1beta1.Coin{
Denom: amount.Denom,
Amount: amount.Amount.String(),
}
return protoV2GogoWrapper{&stakingv1beta1.MsgDelegate{
DelegatorAddress: delegatorAddr,
ValidatorAddress: validatorAddr,
Amount: v2Coin,
}}
}
func makeMsgUndelegate(delegatorAddr, validatorAddr string, amount sdk.Coin) ProtoMsg {
v2Coin := &v1beta1.Coin{
Denom: amount.Denom,
Amount: amount.Amount.String(),
}
return protoV2GogoWrapper{&stakingv1beta1.MsgUndelegate{
DelegatorAddress: delegatorAddr,
ValidatorAddress: validatorAddr,
Amount: v2Coin,
}}
}
@@ -0,0 +1,26 @@
package types
import (
fmt "fmt"
"reflect"
"strings"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
proto "github.com/cosmos/gogoproto/proto"
)
func UnpackAnyRaw(m *codectypes.Any) (proto.Message, error) {
split := strings.Split(m.TypeUrl, "/")
name := split[len(split)-1]
typ := proto.MessageType(name)
if typ == nil {
return nil, fmt.Errorf("no message type found for %s", name)
}
concreteMsg := reflect.New(typ.Elem()).Interface().(proto.Message)
err := proto.Unmarshal(m.Value, concreteMsg)
if err != nil {
return nil, err
}
return concreteMsg, nil
}
@@ -0,0 +1,399 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: cosmos/accounts/defaults/lockup/lockup.proto
package types
import (
fmt "fmt"
github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
types "github.com/cosmos/cosmos-sdk/types"
_ "github.com/cosmos/cosmos-sdk/types/tx/amino"
_ "github.com/cosmos/gogoproto/gogoproto"
proto "github.com/cosmos/gogoproto/proto"
github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types"
_ "google.golang.org/protobuf/types/known/durationpb"
io "io"
math "math"
math_bits "math/bits"
time "time"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
var _ = time.Kitchen
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// Period defines a length of time and amount of coins that will be lock.
type Period struct {
// Period duration
Length time.Duration `protobuf:"bytes,1,opt,name=length,proto3,stdduration" json:"length"`
Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"`
}
func (m *Period) Reset() { *m = Period{} }
func (m *Period) String() string { return proto.CompactTextString(m) }
func (*Period) ProtoMessage() {}
func (*Period) Descriptor() ([]byte, []int) {
return fileDescriptor_79b466256e1a079c, []int{0}
}
func (m *Period) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Period) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Period.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Period) XXX_Merge(src proto.Message) {
xxx_messageInfo_Period.Merge(m, src)
}
func (m *Period) XXX_Size() int {
return m.Size()
}
func (m *Period) XXX_DiscardUnknown() {
xxx_messageInfo_Period.DiscardUnknown(m)
}
var xxx_messageInfo_Period proto.InternalMessageInfo
func (m *Period) GetLength() time.Duration {
if m != nil {
return m.Length
}
return 0
}
func (m *Period) GetAmount() github_com_cosmos_cosmos_sdk_types.Coins {
if m != nil {
return m.Amount
}
return nil
}
func init() {
proto.RegisterType((*Period)(nil), "cosmos.accounts.defaults.lockup.Period")
}
func init() {
proto.RegisterFile("cosmos/accounts/defaults/lockup/lockup.proto", fileDescriptor_79b466256e1a079c)
}
var fileDescriptor_79b466256e1a079c = []byte{
// 326 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x31, 0x4e, 0xc3, 0x30,
0x14, 0x86, 0x63, 0x90, 0x32, 0x04, 0x18, 0xa8, 0x18, 0x4a, 0x07, 0xa7, 0x62, 0xaa, 0x2a, 0x6a,
0xab, 0x70, 0x01, 0x54, 0x10, 0xac, 0x88, 0x91, 0x05, 0x39, 0x8e, 0xeb, 0x5a, 0x4d, 0xf2, 0xaa,
0xda, 0x41, 0xf4, 0x16, 0x8c, 0x88, 0x13, 0x20, 0xa6, 0x5e, 0x02, 0xa9, 0x63, 0x47, 0x26, 0x8a,
0x9a, 0xa1, 0xd7, 0x40, 0xb1, 0x9d, 0x91, 0xc5, 0xef, 0x59, 0xfe, 0xbf, 0xf7, 0xbf, 0x5f, 0x8e,
0xce, 0x39, 0xe8, 0x1c, 0x34, 0x65, 0x9c, 0x43, 0x59, 0x18, 0x4d, 0x53, 0x31, 0x66, 0x65, 0x66,
0x34, 0xcd, 0x80, 0x4f, 0xcb, 0x99, 0x2f, 0x64, 0x36, 0x07, 0x03, 0xad, 0xd8, 0xa9, 0x49, 0xa3,
0x26, 0x8d, 0x9a, 0x38, 0x59, 0xe7, 0x98, 0xe5, 0xaa, 0x00, 0x6a, 0x4f, 0xc7, 0x74, 0xb0, 0x77,
0x48, 0x98, 0x16, 0xf4, 0x79, 0x98, 0x08, 0xc3, 0x86, 0x94, 0x83, 0x2a, 0xfc, 0xfb, 0x89, 0x04,
0x09, 0xb6, 0xa5, 0x75, 0xd7, 0x50, 0x12, 0x40, 0x66, 0x82, 0xda, 0x5b, 0x52, 0x8e, 0x69, 0x5a,
0xce, 0x99, 0x51, 0xe0, 0xa9, 0xb3, 0x2f, 0x14, 0x85, 0xf7, 0x62, 0xae, 0x20, 0x6d, 0x5d, 0x45,
0x61, 0x26, 0x0a, 0x69, 0x26, 0x6d, 0xd4, 0x45, 0xbd, 0x83, 0x8b, 0x53, 0xe2, 0x58, 0xd2, 0xb0,
0xe4, 0xc6, 0xb3, 0xa3, 0xa3, 0xd5, 0x4f, 0x1c, 0xbc, 0x6d, 0x62, 0xf4, 0xb1, 0x5b, 0xf6, 0xd1,
0x83, 0xe7, 0x5a, 0x8b, 0x28, 0x64, 0x79, 0x1d, 0xa8, 0xbd, 0xd7, 0xdd, 0xb7, 0x13, 0x7c, 0xce,
0x7a, 0x67, 0xe2, 0x77, 0x26, 0xd7, 0xa0, 0x8a, 0xd1, 0x6d, 0x3d, 0xe1, 0x73, 0x13, 0xf7, 0xa4,
0x32, 0x93, 0x32, 0x21, 0x1c, 0x72, 0xea, 0x03, 0xba, 0x32, 0xd0, 0xe9, 0x94, 0x9a, 0xc5, 0x4c,
0x68, 0x0b, 0xe8, 0xf7, 0xdd, 0xb2, 0x7f, 0x98, 0x09, 0xc9, 0xf8, 0xe2, 0xa9, 0x4e, 0xad, 0xbd,
0xb5, 0x33, 0x1c, 0xdd, 0xad, 0xb6, 0x18, 0xad, 0xb7, 0x18, 0xfd, 0x6e, 0x31, 0x7a, 0xad, 0x70,
0xb0, 0xae, 0x70, 0xf0, 0x5d, 0xe1, 0xe0, 0x71, 0xe0, 0xe6, 0xe9, 0x74, 0x4a, 0x14, 0xd0, 0x97,
0xff, 0x7f, 0xc8, 0x9a, 0x25, 0xa1, 0x4d, 0x7b, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0xfe, 0xa5,
0x33, 0xd0, 0xd1, 0x01, 0x00, 0x00,
}
func (m *Period) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Period) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Period) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Amount) > 0 {
for iNdEx := len(m.Amount) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Amount[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintLockup(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x12
}
}
n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.Length, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Length):])
if err1 != nil {
return 0, err1
}
i -= n1
i = encodeVarintLockup(dAtA, i, uint64(n1))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintLockup(dAtA []byte, offset int, v uint64) int {
offset -= sovLockup(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Period) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Length)
n += 1 + l + sovLockup(uint64(l))
if len(m.Amount) > 0 {
for _, e := range m.Amount {
l = e.Size()
n += 1 + l + sovLockup(uint64(l))
}
}
return n
}
func sovLockup(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozLockup(x uint64) (n int) {
return sovLockup(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Period) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLockup
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Period: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Period: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Length", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLockup
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthLockup
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthLockup
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.Length, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLockup
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthLockup
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthLockup
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Amount = append(m.Amount, types.Coin{})
if err := m.Amount[len(m.Amount)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipLockup(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthLockup
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipLockup(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLockup
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLockup
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLockup
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthLockup
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupLockup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthLockup
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthLockup = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowLockup = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupLockup = fmt.Errorf("proto: unexpected end of group")
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
package lockup
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
func validateAmount(amount sdk.Coins) error {
if !amount.IsValid() {
return sdkerrors.ErrInvalidCoins.Wrap(amount.String())
}
if amount.IsZero() {
return sdkerrors.ErrInvalidCoins.Wrap(amount.String())
}
return nil
}
@@ -0,0 +1,22 @@
syntax = "proto3";
package cosmos.accounts.defaults.lockup;
import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/duration.proto";
option go_package = "cosmossdk.io/x/accounts/defaults/lockup/types";
// Period defines a length of time and amount of coins that will be lock.
message Period {
// Period duration
google.protobuf.Duration length = 1
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true];
repeated cosmos.base.v1beta1.Coin amount = 2 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
@@ -0,0 +1,53 @@
syntax = "proto3";
package cosmos.accounts.defaults.lockup;
import "cosmos/accounts/defaults/lockup/lockup.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
option go_package = "cosmossdk.io/x/accounts/defaults/lockup/types";
// QueryLockupAccountInfoRequest get lockup account info
message QueryLockupAccountInfoRequest {}
// QueryLockupAccountInfoResponse return lockup account info
message QueryLockupAccountInfoResponse {
// original_locking defines the value of the account original locking coins.
repeated cosmos.base.v1beta1.Coin original_locking = 1
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// delegated_free defines the value of the account free delegated amount.
repeated cosmos.base.v1beta1.Coin delegated_free = 2
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// delegated_locking defines the value of the account locking delegated amount.
repeated cosmos.base.v1beta1.Coin delegated_locking = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// end_time defines the value of the account lockup start time.
google.protobuf.Timestamp start_time = 4 [(gogoproto.stdtime) = true];
// end_time defines the value of the account lockup end time.
google.protobuf.Timestamp end_time = 5 [(gogoproto.stdtime) = true];
// locked_coins defines the value of the account locking coins.
repeated cosmos.base.v1beta1.Coin locked_coins = 6
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// unlocked_coins defines the value of the account released coins from lockup.
repeated cosmos.base.v1beta1.Coin unlocked_coins = 7
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// owner defines the value of the owner of the lockup account.
string owner = 8;
}
// QueryLockingPeriodsRequest is used to query the periodic lockup account locking periods.
message QueryLockingPeriodsRequest {}
// QueryLockingPeriodsResponse returns the periodic lockup account locking periods.
message QueryLockingPeriodsResponse {
// lockup_periods defines the value of the periodic lockup account locking periods.
repeated Period locking_periods = 1;
}
@@ -0,0 +1,126 @@
syntax = "proto3";
package cosmos.accounts.defaults.lockup;
import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos/accounts/defaults/lockup/lockup.proto";
import "cosmos/msg/v1/msg.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
option go_package = "cosmossdk.io/x/accounts/defaults/lockup/types";
//-------------------------------------- INIT --------------------------------------
// MsgInitLockupAccount defines a message that enables creating a lockup
// account.
message MsgInitLockupAccount {
option (amino.name) = "cosmos-sdk/MsgInitLockupAccount";
option (gogoproto.equal) = true;
// owner of the vesting account
string owner = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// end of lockup
google.protobuf.Timestamp end_time = 2
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true];
// start of lockup
google.protobuf.Timestamp start_time = 3
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true];
}
// MsgInitLockupAccountResponse defines the Msg/InitLockupAccount response type.
message MsgInitLockupAccountResponse {}
// MsgInitPeriodicLockingAccount defines a message that enables creating a periodic locking
// account.
message MsgInitPeriodicLockingAccount {
option (amino.name) = "cosmos-sdk/MsgInitPeriodLockupAccount";
option (gogoproto.equal) = false;
// owner of the lockup account
string owner = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// start of lockup
google.protobuf.Timestamp start_time = 2
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true];
repeated Period locking_periods = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
// MsgInitPeriodicLockingAccountResponse defines the Msg/InitPeriodicLockingAccount
// response type.
message MsgInitPeriodicLockingAccountResponse {}
// MsgDelegate defines a message that enable lockup account to execute delegate message
message MsgDelegate {
option (cosmos.msg.v1.signer) = "sender";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// sender is the owner of the lockup account
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.ValidatorAddressString"];
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
// MsgUndelegate defines a message that enable lockup account to execute undelegate message
message MsgUndelegate {
option (cosmos.msg.v1.signer) = "sender";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.ValidatorAddressString"];
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
// MsgSend defines a message that enable lockup account to execute send message
message MsgSend {
option (cosmos.msg.v1.signer) = "sender";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
// MsgExecuteMessagesResponse defines the response for lockup execute operations
message MsgExecuteMessagesResponse {
repeated google.protobuf.Any responses = 1;
}
// MsgWithdraw defines a message that the owner of the lockup can perform to withdraw unlocked token to an account of
// choice
message MsgWithdraw {
option (cosmos.msg.v1.signer) = "withdrawer";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
string withdrawer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated string denoms = 3;
}
// MsgWithdrawResponse defines the response for MsgWithdraw
message MsgWithdrawResponse {
string reciever = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount_received = 2 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}