Skip to content

Commit 27a99e2

Browse files
fix: genesis data validation (#259)
* fix: genesis data validation * fix: stateful validation of genesis block * fix(comment) * fix(dogfood): reject genesis duplicate assets * feat(delegation): query assoc staker for operator * fix: respond to AI comments
1 parent 53e2162 commit 27a99e2

File tree

13 files changed

+681
-75
lines changed

13 files changed

+681
-75
lines changed

app/app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ func NewExocoreApp(
547547

548548
// asset and client chain registry.
549549
app.AssetsKeeper = assetsKeeper.NewKeeper(
550-
keys[assetsTypes.StoreKey], appCodec, &app.OracleKeeper,
550+
keys[assetsTypes.StoreKey], appCodec, &app.OracleKeeper, app.AccountKeeper,
551551
app.BankKeeper, &app.DelegationKeeper, authAddrString,
552552
)
553553

proto/exocore/delegation/v1/query.proto

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ message QueryAssociatedOperatorByStakerResponse {
114114
string operator = 1;
115115
}
116116

117+
// QueryAssociatedStakersByOperatorReq is the request to obtain the associated stakers of the specified operator
118+
message QueryAssociatedStakersByOperatorReq {
119+
// operator is the operator address for which the query is made.
120+
string operator = 1;
121+
}
122+
123+
// QueryAssociatedStakersByOperatorResponse is the response to QueryAssociatedStakersByOperatorReq
124+
message QueryAssociatedStakersByOperatorResponse {
125+
// stakers is the returned stakers associated to the specified operator
126+
repeated string stakers = 1;
127+
}
128+
117129
// Query is the service API for the delegation module.
118130
service Query {
119131
// DelegationInfo queries the delegation information for {stakerID, assetID}.
@@ -153,4 +165,11 @@ service Query {
153165
option (cosmos.query.v1.module_query_safe) = true;
154166
option (google.api.http).get = "/exocore/delegation/v1/QueryAssociatedOperatorByStaker";
155167
}
168+
169+
// QueryAssociatedStakersByOperator queries the associated stakers for the specified operator
170+
rpc QueryAssociatedStakersByOperator(QueryAssociatedStakersByOperatorReq)
171+
returns (QueryAssociatedStakersByOperatorResponse) {
172+
option (cosmos.query.v1.module_query_safe) = true;
173+
option (google.api.http).get = "/exocore/delegation/v1/QueryAssociatedStakersByOperator/{operator}";
174+
}
156175
}

x/assets/keeper/genesis.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package keeper
22

33
import (
44
errorsmod "cosmossdk.io/errors"
5+
"github.com/ExocoreNetwork/exocore/utils"
56
"github.com/ExocoreNetwork/exocore/x/assets/types"
7+
delegationtypes "github.com/ExocoreNetwork/exocore/x/delegation/types"
68
sdk "github.com/cosmos/cosmos-sdk/types"
79
)
810

@@ -46,6 +48,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) {
4648
}
4749
}
4850

51+
exoDelegation := sdk.ZeroInt()
4952
for _, assets := range data.OperatorAssets {
5053
for _, assetInfo := range assets.AssetsState {
5154
// #nosec G703 // already validated
@@ -55,8 +58,26 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) {
5558
if err != nil {
5659
panic(errorsmod.Wrap(err, "failed to set operator asset info"))
5760
}
61+
if assetInfo.AssetID == types.ExocoreAssetID {
62+
exoDelegation = exoDelegation.Add(assetInfo.Info.TotalAmount)
63+
}
5864
}
5965
}
66+
67+
// check that this exoDelegation amount is reflected in x/bank
68+
address := k.ak.GetModuleAccount(ctx, delegationtypes.DelegatedPoolName).GetAddress()
69+
balance := k.bk.GetBalance(ctx, address, utils.BaseDenom)
70+
// slashing is lazily applied at the time of withdrawal. it means that
71+
// exoDelegation may have been slashed, but balance will never be slashed.
72+
// it implies that exoDelegation should be less than or equal to balance.
73+
// or conversely, if exoDelegation is greater than balance, we have a problem.
74+
if exoDelegation.GT(balance.Amount) {
75+
panic(errorsmod.Wrapf(
76+
types.ErrInvalidGenesisData,
77+
"delegated pool account balance is too low, exoDelegation: %s, balance: %s",
78+
exoDelegation, balance.Amount,
79+
).Error())
80+
}
6081
}
6182

6283
// ExportGenesis returns the module's exported genesis.

x/assets/keeper/keeper.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Keeper struct {
1515
storeKey storetypes.StoreKey
1616
cdc codec.BinaryCodec
1717
assetstype.OracleKeeper
18+
ak assetstype.AccountKeeper
1819
bk assetstype.BankKeeper
1920
dk delegationKeeper
2021
authority string
@@ -24,6 +25,7 @@ func NewKeeper(
2425
storeKey storetypes.StoreKey,
2526
cdc codec.BinaryCodec,
2627
oracleKeeper assetstype.OracleKeeper,
28+
ak assetstype.AccountKeeper,
2729
bk assetstype.BankKeeper,
2830
dk delegationKeeper,
2931
authority string,
@@ -36,6 +38,7 @@ func NewKeeper(
3638
storeKey: storeKey,
3739
cdc: cdc,
3840
OracleKeeper: oracleKeeper,
41+
ak: ak,
3942
bk: bk,
4043
dk: dk,
4144
authority: authority,

x/assets/types/expected_keepers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
sdkmath "cosmossdk.io/math"
55
oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types"
66
sdk "github.com/cosmos/cosmos-sdk/types"
7+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
78
)
89

910
type OracleKeeper interface {
@@ -15,3 +16,7 @@ type OracleKeeper interface {
1516
type BankKeeper interface {
1617
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
1718
}
19+
20+
type AccountKeeper interface {
21+
GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI
22+
}

x/assets/types/genesis.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"cosmossdk.io/math"
88
"github.com/ExocoreNetwork/exocore/utils"
99
sdk "github.com/cosmos/cosmos-sdk/types"
10-
"github.com/ethereum/go-ethereum/common"
1110
)
1211

1312
// NewGenesis returns a new genesis state with the given inputs.
@@ -86,15 +85,6 @@ func (gs GenesisState) ValidateTokens(lzIDs map[uint64]struct{}) (map[string]mat
8685
info.AssetBasicInfo.Name, address,
8786
)
8887
}
89-
// build for 0x addresses only.
90-
// TODO: consider removing this check for non-EVM client chains.
91-
if !common.IsHexAddress(address) {
92-
return errorsmod.Wrapf(
93-
ErrInvalidGenesisData,
94-
"not hex address for token %s, address: %s",
95-
info.AssetBasicInfo.Name, address,
96-
)
97-
}
9888

9989
// ensure there are no deposits for this asset already (since they are handled in the
10090
// genesis exec). while it is possible to remove this field entirely (and assume 0),
@@ -255,8 +245,10 @@ func (gs GenesisState) ValidateOperatorAssets(tokensTotalStaking map[string]math
255245
assets.Operator, asset.AssetID,
256246
)
257247
}
258-
// the sum amount of operators shouldn't be greater than the total staking amount of this asset
259-
if asset.Info.TotalAmount.Add(asset.Info.PendingUndelegationAmount).GT(totalStaking) {
248+
// the sum amount of operators shouldn't be greater than the total staking amount of this asset.
249+
// however, this line should not apply for the native asset, since that is handled by x/bank.
250+
if asset.Info.TotalAmount.Add(asset.Info.PendingUndelegationAmount).GT(totalStaking) &&
251+
asset.AssetID != ExocoreAssetID {
260252
return errorsmod.Wrapf(
261253
ErrInvalidGenesisData,
262254
"operator's sum amount exceeds the total staking amount for %s: %+v",

x/delegation/client/cli/query.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func GetQueryCmd() *cobra.Command {
3232
QueryUndelegationsByHeight(),
3333
QueryUndelegationHoldCount(),
3434
QueryAssociatedOperatorByStaker(),
35+
QueryAssociatedStakersByOperator(),
3536
)
3637
return cmd
3738
}
@@ -244,3 +245,35 @@ func QueryAssociatedOperatorByStaker() *cobra.Command {
244245
flags.AddQueryFlagsToCmd(cmd)
245246
return cmd
246247
}
248+
249+
func QueryAssociatedStakersByOperator() *cobra.Command {
250+
cmd := &cobra.Command{
251+
Use: "QueryAssociatedStakersByOperator <operatorAddr>",
252+
Short: "Get the associated stakers for the specified operator",
253+
Long: "Get the associated stakers for the specified operator",
254+
Args: cobra.ExactArgs(1),
255+
RunE: func(cmd *cobra.Command, args []string) error {
256+
clientCtx, err := client.GetClientQueryContext(cmd)
257+
if err != nil {
258+
return err
259+
}
260+
261+
accAddr, err := sdk.AccAddressFromBech32(args[0])
262+
if err != nil {
263+
return errorsmod.Wrap(types.ErrInvalidCliCmdArg, err.Error())
264+
}
265+
queryClient := delegationtype.NewQueryClient(clientCtx)
266+
req := &delegationtype.QueryAssociatedStakersByOperatorReq{
267+
Operator: accAddr.String(),
268+
}
269+
res, err := queryClient.QueryAssociatedStakersByOperator(context.Background(), req)
270+
if err != nil {
271+
return err
272+
}
273+
return clientCtx.PrintProto(res)
274+
},
275+
}
276+
277+
flags.AddQueryFlagsToCmd(cmd)
278+
return cmd
279+
}

x/delegation/keeper/delegation_state.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,27 @@ func (k *Keeper) GetAssociatedOperator(ctx sdk.Context, stakerID string) (string
373373
return "", nil
374374
}
375375

376+
func (k *Keeper) GetAssociatedStakers(ctx sdk.Context, operator string) ([]string, error) {
377+
if _, err := sdk.AccAddressFromBech32(operator); err != nil {
378+
return nil, delegationtype.ErrOperatorAddrIsNotAccAddr
379+
}
380+
store := prefix.NewStore(ctx.KVStore(k.storeKey), delegationtype.KeyPrefixAssociatedOperatorByStaker)
381+
iterator := sdk.KVStorePrefixIterator(store, []byte{})
382+
defer iterator.Close()
383+
384+
// assuming that we support 5 client chains, this is a reasonable capacity.
385+
// we can of course support more or less than that, but this is a good
386+
// starting point.
387+
// ideally, we should have a reverse lookup stored for this.
388+
ret := make([]string, 0, 5)
389+
for ; iterator.Valid(); iterator.Next() {
390+
if string(iterator.Value()) == operator {
391+
ret = append(ret, string(iterator.Key()))
392+
}
393+
}
394+
return ret, nil
395+
}
396+
376397
func (k *Keeper) GetAllAssociations(ctx sdk.Context) ([]delegationtype.StakerToOperator, error) {
377398
store := prefix.NewStore(ctx.KVStore(k.storeKey), delegationtype.KeyPrefixAssociatedOperatorByStaker)
378399
iterator := sdk.KVStorePrefixIterator(store, []byte{})

x/delegation/keeper/grpc_query.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@ func (k Keeper) QueryAssociatedOperatorByStaker(ctx context.Context, req *delega
5757
Operator: operator,
5858
}, nil
5959
}
60+
61+
func (k Keeper) QueryAssociatedStakersByOperator(ctx context.Context, req *delegationtype.QueryAssociatedStakersByOperatorReq) (*delegationtype.QueryAssociatedStakersByOperatorResponse, error) {
62+
c := sdk.UnwrapSDKContext(ctx)
63+
stakers, err := k.GetAssociatedStakers(c, req.Operator)
64+
if err != nil {
65+
return nil, err
66+
}
67+
return &delegationtype.QueryAssociatedStakersByOperatorResponse{
68+
Stakers: stakers,
69+
}, nil
70+
}

0 commit comments

Comments
 (0)