Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:register vesting type, add support for delegation/undelegation of native token #127

Merged
Merged
26 changes: 18 additions & 8 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ import (
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"

"github.com/cosmos/cosmos-sdk/x/authz"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
Expand Down Expand Up @@ -248,6 +250,7 @@ var (
ibctm.AppModuleBasic{},
ica.AppModuleBasic{},
authzmodule.AppModuleBasic{},
vesting.AppModuleBasic{},
feegrantmodule.AppModuleBasic{},
upgrade.AppModuleBasic{},
evidence.AppModuleBasic{},
Expand Down Expand Up @@ -278,8 +281,9 @@ var (
authtypes.Minter,
authtypes.Burner,
}, // used for secure addition and subtraction of balance using module account
erc20types.ModuleName: {authtypes.Minter, authtypes.Burner},
exominttypes.ModuleName: {authtypes.Minter},
exominttypes.ModuleName: {authtypes.Minter},
erc20types.ModuleName: {authtypes.Minter, authtypes.Burner},
delegationTypes.DelegatedPoolName: {authtypes.Burner, authtypes.Staking},
}

// module accounts that are allowed to receive tokens
Expand Down Expand Up @@ -534,14 +538,16 @@ func NewExocoreApp(
)

// asset and client chain registry.
app.AssetsKeeper = assetsKeeper.NewKeeper(keys[assetsTypes.StoreKey], appCodec, &app.OracleKeeper)
app.AssetsKeeper = assetsKeeper.NewKeeper(keys[assetsTypes.StoreKey], appCodec, &app.OracleKeeper, app.BankKeeper, &app.DelegationKeeper)

// handles delegations by stakers, and must know if the delegatee operator is registered.
app.DelegationKeeper = delegationKeeper.NewKeeper(
keys[delegationTypes.StoreKey], appCodec,
app.AssetsKeeper,
delegationTypes.VirtualSlashKeeper{},
&app.OperatorKeeper,
app.AccountKeeper,
app.BankKeeper,
)

// the dogfood module is the first AVS. it receives slashing calls from either x/slashing
Expand Down Expand Up @@ -728,15 +734,15 @@ func NewExocoreApp(
Create Transfer Stack

transfer stack contains (from bottom to top):
- ERC-20 Middleware
- Recovery Middleware
- IBC Transfer
- ERC-20 Middleware
- Recovery Middleware
- IBC Transfer

SendPacket, since it is originating from the application to core IBC:
transferKeeper.SendPacket -> recovery.SendPacket -> erc20.SendPacket -> channel.SendPacket
transferKeeper.SendPacket -> recovery.SendPacket -> erc20.SendPacket -> channel.SendPacket

RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> transfer.OnRecvPacket
channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> transfer.OnRecvPacket
Comment on lines +737 to +745
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update comments to reflect current transfer stack

The comments mention Recovery Middleware, but it appears the recovery middleware is no longer part of the transfer stack. Please update the comments to accurately represent the current middleware configuration.

Apply this diff to correct the comments:

-	// transfer stack contains (from bottom to top):
-	// - ERC-20 Middleware
-	// - Recovery Middleware
-	// - IBC Transfer
+	// Transfer stack contains (from bottom to top):
+	// - ERC-20 Middleware
+	// - IBC Transfer

-	transferKeeper.SendPacket -> recovery.SendPacket -> erc20.SendPacket -> channel.SendPacket
+	transferKeeper.SendPacket -> erc20.SendPacket -> channel.SendPacket

-	channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> transfer.OnRecvPacket
+	channel.RecvPacket -> erc20.OnRecvPacket -> transfer.OnRecvPacket
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- ERC-20 Middleware
- Recovery Middleware
- IBC Transfer
SendPacket, since it is originating from the application to core IBC:
transferKeeper.SendPacket -> recovery.SendPacket -> erc20.SendPacket -> channel.SendPacket
transferKeeper.SendPacket -> recovery.SendPacket -> erc20.SendPacket -> channel.SendPacket
RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> transfer.OnRecvPacket
channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> transfer.OnRecvPacket
// Transfer stack contains (from bottom to top):
// - ERC-20 Middleware
// - IBC Transfer
SendPacket, since it is originating from the application to core IBC:
transferKeeper.SendPacket -> erc20.SendPacket -> channel.SendPacket
RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
channel.RecvPacket -> erc20.OnRecvPacket -> transfer.OnRecvPacket

*/

// create IBC module from top to bottom of stack
Expand Down Expand Up @@ -802,6 +808,7 @@ func NewExocoreApp(
authsims.RandomGenesisAccounts,
app.GetSubspace(authtypes.ModuleName),
),
vesting.NewAppModule(app.AccountKeeper, app.BankKeeper),
bank.NewAppModule(
appCodec, app.BankKeeper, app.AccountKeeper,
app.GetSubspace(banktypes.ModuleName),
Expand Down Expand Up @@ -890,6 +897,7 @@ func NewExocoreApp(
genutiltypes.ModuleName,
feegrant.ModuleName,
paramstypes.ModuleName,
vestingtypes.ModuleName,
consensusparamtypes.ModuleName,
erc20types.ModuleName,
exominttypes.ModuleName, // called via hooks not directly
Expand Down Expand Up @@ -924,6 +932,7 @@ func NewExocoreApp(
authz.ModuleName,
paramstypes.ModuleName,
upgradetypes.ModuleName,
vestingtypes.ModuleName,
epochstypes.ModuleName, // begin blocker only
erc20types.ModuleName,
exominttypes.ModuleName,
Expand Down Expand Up @@ -968,6 +977,7 @@ func NewExocoreApp(
oracleTypes.ModuleName, // after staking module to ensure total vote power available
// no-op modules
paramstypes.ModuleName,
vestingtypes.ModuleName,
consensusparamtypes.ModuleName,
upgradetypes.ModuleName, // no-op since we don't call SetInitVersionMap
rewardTypes.ModuleName, // not fully implemented yet
Expand Down
20 changes: 7 additions & 13 deletions proto/exocore/delegation/v1/tx.proto
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

Check failure on line 1 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Previously present message "DelegationApproveInfo" was deleted from file.
syntax = "proto3";
package exocore.delegation.v1;

Expand Down Expand Up @@ -39,16 +39,8 @@
ValueField value = 2;
}

// DelegationApproveInfo is the delegation approve info.
message DelegationApproveInfo {
// signature of the delegation approve info.
string signature = 1;
// salt within the signature.
string salt = 2;
}

// DelegationIncOrDecInfo is the delegation increase or decrease info.
message DelegationIncOrDecInfo {

Check failure on line 43 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Previously present message "DelegationIncOrDecInfo.PerOperatorAmountsEntry" was deleted from file.
option (cosmos.msg.v1.signer) = "fromAddress";
option (amino.name) = "cosmos-sdk/MsgAddOrDecreaseDelegation";

Expand All @@ -59,15 +51,15 @@
string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// per_operator_amounts is the amount of the asset delegated to each operator.
map<string, ValueField> per_operator_amounts = 2;
repeated KeyValue per_operator_amounts = 2 [(gogoproto.nullable) = false];

Check failure on line 54 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "2" on message "DelegationIncOrDecInfo" changed type from "exocore.delegation.v1.DelegationIncOrDecInfo.PerOperatorAmountsEntry" to "exocore.delegation.v1.KeyValue".
}

// MsgDelegation is the delegation Msg.
message MsgDelegation {
// asset_id is the asset id.
string asset_id = 1 [(gogoproto.customname) = "AssetID"];

Check failure on line 60 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" with name "asset_id" on message "MsgDelegation" changed option "json_name" from "baseInfo" to "assetId".

Check failure on line 60 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" on message "MsgDelegation" changed type from "message" to "string".

Check failure on line 60 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" on message "MsgDelegation" changed name from "base_info" to "asset_id".
// base_info is the delegation increase or decrease request container.
DelegationIncOrDecInfo base_info = 1;
// approved_info is the delegation increase or decrease response container.
DelegationApproveInfo approved_info = 2;
DelegationIncOrDecInfo base_info = 2;

Check failure on line 62 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "2" with name "base_info" on message "MsgDelegation" changed option "json_name" from "approvedInfo" to "baseInfo".

Check failure on line 62 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "2" on message "MsgDelegation" changed type from "exocore.delegation.v1.DelegationApproveInfo" to "exocore.delegation.v1.DelegationIncOrDecInfo".

Check failure on line 62 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "2" on message "MsgDelegation" changed name from "approved_info" to "base_info".
}

// UndelegationRecord is the undelegation record, keyed by a RecordKey.
Expand Down Expand Up @@ -117,8 +109,10 @@

// MsgUndelegation is the undelegation Msg.
message MsgUndelegation {
// asset_id is the identity of the asset.
string asset_id = 1 [(gogoproto.customname) = "AssetID"];

Check failure on line 113 in proto/exocore/delegation/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" with name "asset_id" on message "MsgUndelegation" changed option "json_name" from "baseInfo" to "assetId".
// base_info is the delegation increase or decrease request container.
DelegationIncOrDecInfo base_info = 1;
DelegationIncOrDecInfo base_info = 2;
}

// UndelegationResponse is the response to an undelegation request.
Expand Down
1 change: 0 additions & 1 deletion testutil/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ func (suite *BaseTestSuite) DoSetupTest() {
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, amount)),
}

// Exocore modules genesis
// x/assets
suite.ClientChains = []assetstypes.ClientChainInfo{
Expand Down
3 changes: 0 additions & 3 deletions x/assets/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ func UpdateParams() *cobra.Command {
},
}

if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), msg)
},
}
Expand Down
24 changes: 14 additions & 10 deletions x/assets/keeper/bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ func (k Keeper) PerformDepositOrWithdraw(ctx sdk.Context, params *DepositWithdra
TotalDepositAmount: actualOpAmount,
WithdrawableAmount: actualOpAmount,
}
// update asset state of the specified staker
err := k.UpdateStakerAssetState(ctx, stakeID, assetID, changeAmount)
if err != nil {
return errorsmod.Wrapf(err, "stakeID:%s assetID:%s", stakeID, assetID)
}

// update total amount of the deposited asset
err = k.UpdateStakingAssetTotalAmount(ctx, assetID, actualOpAmount)
if err != nil {
return errorsmod.Wrapf(err, "assetID:%s", assetID)
// don't update staker info for exo-native-token
// TODO: do we need additional process for exo-native-token ?
if assetID != assetstypes.NativeAssetID {
// update asset state of the specified staker
err := k.UpdateStakerAssetState(ctx, stakeID, assetID, changeAmount)
if err != nil {
return errorsmod.Wrapf(err, "stakeID:%s assetID:%s", stakeID, assetID)
}

// update total amount of the deposited asset
err = k.UpdateStakingAssetTotalAmount(ctx, assetID, actualOpAmount)
if err != nil {
return errorsmod.Wrapf(err, "assetID:%s", assetID)
}
}
return nil
}
11 changes: 11 additions & 0 deletions x/assets/keeper/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package keeper
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved

import (
delegationtype "github.com/ExocoreNetwork/exocore/x/delegation/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// this keeper interface is defined here to avoid a circular dependency
type delegationKeeper interface {
GetDelegationInfo(ctx sdk.Context, stakerID, assetID string) (*delegationtype.QueryDelegationInfoResponse, error)
}
6 changes: 6 additions & 0 deletions x/assets/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,23 @@ type Keeper struct {
storeKey storetypes.StoreKey
cdc codec.BinaryCodec
assetstype.OracleKeeper
bk assetstype.BankKeeper
dk delegationKeeper
}

func NewKeeper(
storeKey storetypes.StoreKey,
cdc codec.BinaryCodec,
oracleKeeper assetstype.OracleKeeper,
bk assetstype.BankKeeper,
dk delegationKeeper,
) Keeper {
return Keeper{
storeKey: storeKey,
cdc: cdc,
OracleKeeper: oracleKeeper,
bk: bk,
dk: dk,
}
}

Expand Down
46 changes: 46 additions & 0 deletions x/assets/keeper/staker_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"fmt"

assetstype "github.com/ExocoreNetwork/exocore/x/assets/types"
delegationkeeper "github.com/ExocoreNetwork/exocore/x/delegation/keeper"
"github.com/ethereum/go-ethereum/common/hexutil"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
Expand Down Expand Up @@ -63,10 +65,54 @@
Info: stateInfo,
})
}
// add exo-native-token info
info, err := k.GetStakerSpecifiedAssetInfo(ctx, stakerID, assetstype.NativeAssetID)
if err != nil {
return nil, err
Comment on lines +70 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap error with additional context

When returning the error from GetStakerSpecifiedAssetInfo, consider wrapping it to provide more context for debugging.

Apply this diff to enhance the error handling:

 if err != nil {
-    return nil, err
+    return nil, errorsmod.Wrap(err, "failed to get staker specified asset info for native asset")
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err != nil {
return nil, err
if err != nil {
return nil, errorsmod.Wrap(err, "failed to get staker specified asset info for native asset")

}
ret = append(ret, assetstype.DepositByAsset{
AssetID: assetstype.NativeAssetID,
Info: *info,
})
return ret, nil
}

func (k Keeper) GetStakerSpecifiedAssetInfo(ctx sdk.Context, stakerID string, assetID string) (info *assetstype.StakerAssetInfo, err error) {
if assetID == assetstype.NativeAssetID {
stakerAddrStr, _, err := assetstype.ParseID(stakerID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to parse stakerID")
}
stakerAccDecode, err := hexutil.Decode(stakerAddrStr)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to decode staker address")
}
stakerAcc := sdk.AccAddress(stakerAccDecode)
balance := k.bk.GetBalance(ctx, stakerAcc, assetstype.NativeAssetDenom)
info := &assetstype.StakerAssetInfo{
TotalDepositAmount: balance.Amount,
WithdrawableAmount: balance.Amount,
PendingUndelegationAmount: math.NewInt(0),
}

delegationInfoRecords, err := k.dk.GetDelegationInfo(ctx, stakerID, assetID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to GetDelegationInfo")
}
for operator, record := range delegationInfoRecords.DelegationInfos {
operatorAssetInfo, err := k.GetOperatorSpecifiedAssetInfo(ctx, sdk.MustAccAddressFromBech32(operator), assetID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to GetOperatorSpecifiedAssetInfo")
}
Comment on lines +103 to +106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid potential panics by not using sdk.MustAccAddressFromBech32

Using sdk.MustAccAddressFromBech32 can lead to a panic if the operator address is invalid. To prevent this, use sdk.AccAddressFromBech32 and handle any errors gracefully.

Apply this diff to handle potential errors:

-operatorAssetInfo, err := k.GetOperatorSpecifiedAssetInfo(ctx, sdk.MustAccAddressFromBech32(operator), assetID)
+operatorAccAddr, err := sdk.AccAddressFromBech32(operator)
+if err != nil {
+    return nil, errorsmod.Wrapf(err, "invalid operator address: %s", operator)
+}
+operatorAssetInfo, err := k.GetOperatorSpecifiedAssetInfo(ctx, operatorAccAddr, assetID)
 if err != nil {
     return nil, errorsmod.Wrap(err, "failed to GetOperatorSpecifiedAssetInfo")
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
operatorAssetInfo, err := k.GetOperatorSpecifiedAssetInfo(ctx, sdk.MustAccAddressFromBech32(operator), assetID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to GetOperatorSpecifiedAssetInfo")
}
operatorAccAddr, err := sdk.AccAddressFromBech32(operator)
if err != nil {
return nil, errorsmod.Wrapf(err, "invalid operator address: %s", operator)
}
operatorAssetInfo, err := k.GetOperatorSpecifiedAssetInfo(ctx, operatorAccAddr, assetID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to GetOperatorSpecifiedAssetInfo")
}

undelegatableTokens, err := delegationkeeper.TokensFromShares(record.UndelegatableShare, operatorAssetInfo.TotalShare, operatorAssetInfo.TotalAmount)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to get shares from token")
}
info.TotalDepositAmount = info.TotalDepositAmount.Add(undelegatableTokens).Add(record.WaitUndelegationAmount)
info.PendingUndelegationAmount = info.PendingUndelegationAmount.Add(record.WaitUndelegationAmount)
}
return info, nil
}
store := prefix.NewStore(ctx.KVStore(k.storeKey), assetstype.KeyPrefixReStakerAssetInfos)
key := assetstype.GetJoinedStoreKey(stakerID, assetID)
value := store.Get(key)
Expand Down
2 changes: 1 addition & 1 deletion x/assets/keeper/staker_asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (suite *StakingAssetsTestSuite) TestUpdateStakerAssetsState() {
}

func (suite *StakingAssetsTestSuite) TestGetStakerAssetInfos() {
stakerID := fmt.Sprintf("%s_%s", suite.Address, "0")
stakerID := fmt.Sprintf("%s_%s", suite.Address, "0x0")
ethUniAssetID := fmt.Sprintf("%s_%s", "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "101")
ethUsdtAssetID := fmt.Sprintf("%s_%s", "0xdac17f958d2ee523a2206206994597c13d831ec7", "101")
ethUniInitialChangeValue := assetstype.DeltaStakerSingleAsset{
Expand Down
4 changes: 4 additions & 0 deletions x/assets/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ type OracleKeeper interface {
GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (oracletypes.Price, error)
RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *oracletypes.OracleInfo) error
}

type BankKeeper interface {
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
}
7 changes: 7 additions & 0 deletions x/assets/types/general.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)

const (
NativeChainLzID = 0
NativeAssetAddr = "0x0000000000000000000000000000000000000000"
NativeAssetID = "0x0000000000000000000000000000000000000000_0x0"
NativeAssetDenom = utils.BaseDenom
)

const (
CrossChainActionLength = 1
CrossChainOpAmountLength = 32
Expand Down
4 changes: 3 additions & 1 deletion x/delegation/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ func NewTxCmd() *cobra.Command {
}

txCmd.AddCommand(
// add tx commands
// add tx commands
CmdDelegate(),
CmdUndelegate(),
)
return txCmd
}
Loading
Loading