Skip to content

Commit

Permalink
feature fee address (#1091)
Browse files Browse the repository at this point in the history
## Context 
There's been some discussion on the best way to create the fee module account.

## How we got here
* The code on main was working on dockernet, but threw an error when initializing the chain for the testnet
* It's not clear why it didn't work on both, but it appears that the genesis validation was being run independent from InitGenesis
* When testing the `.GetModuleAccount` approach from informal - we were forgetting to register the module with maccPerms, which is why it was failing

## Conclusion
* The `.GetModuleAccount` approach does work if we add it to maccPerms
* There's really no need to store this on the host zone if it's a normal module account - we can query it through the auth store
* There's also no need to validate the address in genesis since it's a module account

## Brief Changelog
* Removed fee address from host zone
* Added module account using `.GetModuleAccount` approach
* Added module name to maccPerms 
* Updated LS and distribute logic to use new account
* Fixed unit tests
* Fixed dockernet scripts

## Testing
* Covered by unit test
* Tested distribution to STRD stakers in dockernet
  • Loading branch information
sampocs authored Jan 26, 2024
1 parent a2289f8 commit 6a5dbc4
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 231 deletions.
4 changes: 3 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ var (
icatypes.ModuleName: nil,
stakeibcmoduletypes.RewardCollectorName: nil,
staketiatypes.ModuleName: {authtypes.Minter, authtypes.Burner},
staketiatypes.FeeAddress: nil,
}
)

Expand Down Expand Up @@ -1106,7 +1107,8 @@ func (app *StrideApp) BlacklistedModuleAccountAddrs() map[string]bool {
if acc == stakeibcmoduletypes.ModuleName ||
acc == stakeibcmoduletypes.RewardCollectorName ||
acc == ccvconsumertypes.ConsumerToSendToProviderName ||
acc == staketiatypes.ModuleName {
acc == staketiatypes.ModuleName ||
acc == staketiatypes.FeeAddress {
continue
}
modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true
Expand Down
2 changes: 1 addition & 1 deletion dockernet/scripts/staketia/reinvest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ HOST_DENOM=$(GET_VAR_VALUE ${HOST_CHAIN}_DENOM)

reward_address=$($HOST_MAIN_CMD keys show -a reward)
deposit_address=$($STRIDE_MAIN_CMD keys show -a deposit)
fee_address=$($STRIDE_MAIN_CMD q staketia host-zone | grep fee_address | awk '{print $2}')
fee_address=$($STRIDE_MAIN_CMD q auth module-account staketia_fee_address | grep "address:" | awk '{print $2}')

echo ">>> Claiming outstanding rewards records..."
$HOST_MAIN_CMD tx distribution withdraw-all-rewards --from delegation -y | TRIM_TX
Expand Down
2 changes: 1 addition & 1 deletion dockernet/src/create_logs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ while true; do
deposit_address=$($STRIDE_MAIN_CMD keys show -a deposit)
redemption_address=$($STRIDE_MAIN_CMD keys show -a redemption)
claim_address=$($STRIDE_MAIN_CMD keys show -a claim)
fee_address=$($STRIDE_MAIN_CMD q staketia host-zone | grep fee_address | awk '{print $2}')
fee_address=$($STRIDE_MAIN_CMD q auth module-account staketia_fee_address | grep "address:" | awk '{print $2}')

print_stride_balance $deposit_address "DEPOSIT"
print_stride_balance $redemption_address "REDEMPTION"
Expand Down
25 changes: 12 additions & 13 deletions proto/stride/staketia/staketia.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,61 +28,60 @@ message HostZone {
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Claim address on stride
string claim_address = 9 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Fee address on stride
string fee_address = 10 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// operator address set by safe, on stride
string operator_address_on_stride = 11
string operator_address_on_stride = 10
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// admin address set upon host zone creation, on stride
string safe_address_on_stride = 12 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
string safe_address_on_stride = 11
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];

// Previous redemption rate
string last_redemption_rate = 13 [
string last_redemption_rate = 12 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Current redemption rate
string redemption_rate = 14 [
string redemption_rate = 13 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Min outer redemption rate - adjusted by governance
string min_redemption_rate = 15 [
string min_redemption_rate = 14 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Max outer redemption rate - adjusted by governance
string max_redemption_rate = 16 [
string max_redemption_rate = 15 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Min inner redemption rate - adjusted by controller
string min_inner_redemption_rate = 17 [
string min_inner_redemption_rate = 16 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Max inner redemption rate - adjusted by controller
string max_inner_redemption_rate = 18 [
string max_inner_redemption_rate = 17 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// Total delegated balance on the host zone delegation account
string delegated_balance = 19 [
string delegated_balance = 18 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];

// The undelegation period for Celestia in days
uint64 unbonding_period_seconds = 20;
uint64 unbonding_period_seconds = 19;
// Indicates whether the host zone has been halted
bool halted = 21;
bool halted = 20;
}

// Status fields for a delegation record
Expand Down
12 changes: 4 additions & 8 deletions x/staketia/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,26 +186,22 @@ func (k Keeper) LiquidStakeAndDistributeFees(ctx sdk.Context) error {
return err
}

feeAccount, err := sdk.AccAddressFromBech32(hostZone.FeeAddress)
if err != nil {
return errorsmod.Wrapf(err, "invalid fee address")
}

// Get the balance of native tokens in the fee address, if there are no tokens, no action is necessary
feesBalance := k.bankKeeper.GetBalance(ctx, feeAccount, hostZone.NativeTokenIbcDenom)
feeAddress := k.accountKeeper.GetModuleAddress(types.FeeAddress)
feesBalance := k.bankKeeper.GetBalance(ctx, feeAddress, hostZone.NativeTokenIbcDenom)
if feesBalance.IsZero() {
k.Logger(ctx).Info("No fees generated this epoch")
return nil
}

// Liquid stake those native tokens
stTokens, err := k.LiquidStake(ctx, hostZone.FeeAddress, feesBalance.Amount)
stTokens, err := k.LiquidStake(ctx, feeAddress.String(), feesBalance.Amount)
if err != nil {
return errorsmod.Wrapf(err, "unable to liquid stake fees")
}

// Send the stTokens to the fee collector
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, feeAccount, authtypes.FeeCollectorName, sdk.NewCoins(stTokens))
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.FeeAddress, authtypes.FeeCollectorName, sdk.NewCoins(stTokens))
if err != nil {
return errorsmod.Wrapf(err, "unable to send liquid staked tokens to fee collector")
}
Expand Down
13 changes: 2 additions & 11 deletions x/staketia/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ func (s *KeeperTestSuite) TestConfirmDelegation_RecordIncorrectState() {

func (s *KeeperTestSuite) TestLiquidStakeAndDistributeFees() {
// Create relevant addresses
feeAddress := s.TestAccs[0]
depositAddress := s.TestAccs[1]
depositAddress := s.TestAccs[0]
feeAddress := s.App.AccountKeeper.GetModuleAddress(types.FeeAddress)

// Liquid stake 1000 with a RR of 2, should return 500 tokens
liquidStakeAmount := sdkmath.NewInt(1000)
Expand All @@ -510,7 +510,6 @@ func (s *KeeperTestSuite) TestLiquidStakeAndDistributeFees() {
NativeTokenDenom: HostNativeDenom,
NativeTokenIbcDenom: HostIBCDenom,
DepositAddress: depositAddress.String(),
FeeAddress: feeAddress.String(),
RedemptionRate: redemptionRate,
MinRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.2")),
MinInnerRedemptionRate: redemptionRate.Sub(sdk.MustNewDecFromStr("0.1")),
Expand Down Expand Up @@ -541,14 +540,6 @@ func (s *KeeperTestSuite) TestLiquidStakeAndDistributeFees() {
s.Require().Equal(expectedStTokens.Int64(), feeCollectorBalance.Amount.Int64(),
"fee collector should not have changed")

// Test that if an invalid fee address is provided, it will error
invalidHostZone := hostZone
invalidHostZone.FeeAddress = "invalid_address"
s.App.StaketiaKeeper.SetHostZone(s.Ctx, invalidHostZone)

err = s.App.StaketiaKeeper.LiquidStakeAndDistributeFees(s.Ctx)
s.Require().ErrorContains(err, "invalid fee address")

// Test that if the host zone is halted, it will error
haltedHostZone := hostZone
haltedHostZone.Halted = true
Expand Down
15 changes: 3 additions & 12 deletions x/staketia/keeper/genesis.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/cosmos-sdk/types/address"

"github.com/Stride-Labs/stride/v17/utils"
"github.com/Stride-Labs/stride/v17/x/staketia/types"
)

// Initializes the genesis state in the store
func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) {
// Create the fee address on the host zone
var feeAddress sdk.AccAddress = address.Module(types.ModuleName, types.FeeAddressKey)
if err := utils.CreateModuleAccount(ctx, k.accountKeeper, feeAddress); err != nil {
panic(fmt.Sprintf("unable to create fee address for host zone, %s", err))
}
genState.HostZone.FeeAddress = feeAddress.String()

// Validate that all required fields are specified
if err := genState.Validate(); err != nil {
panic(err)
}

// Create fee module account (calling GetModuleAccount will set it for the first time)
k.accountKeeper.GetModuleAccount(ctx, types.FeeAddress)

// Set the main host zone config
k.SetHostZone(ctx, genState.HostZone)

Expand Down
6 changes: 3 additions & 3 deletions x/staketia/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (

// Required AccountKeeper functions
type AccountKeeper interface {
NewAccount(sdk.Context, authtypes.AccountI) authtypes.AccountI
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
SetAccount(ctx sdk.Context, acc authtypes.AccountI)
GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI
GetModuleAddress(name string) sdk.AccAddress
}

// Required BankKeeper functions
Expand All @@ -24,6 +23,7 @@ type BankKeeper interface {
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddress sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error
}

// Required TransferKeeper functions
Expand Down
6 changes: 0 additions & 6 deletions x/staketia/types/host_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ func (h HostZone) ValidateGenesis() error {
if h.ClaimAddress == "" {
return ErrInvalidHostZone.Wrap("claim address must be specified")
}
if h.FeeAddress == "" {
return ErrInvalidHostZone.Wrap("fee address must be specified")
}
if h.OperatorAddressOnStride == "" {
return ErrInvalidHostZone.Wrap("operator address must be specified")
}
Expand All @@ -72,9 +69,6 @@ func (h HostZone) ValidateGenesis() error {
if _, err := sdk.AccAddressFromBech32(h.ClaimAddress); err != nil {
return errorsmod.Wrapf(err, "invalid claim address")
}
if _, err := sdk.AccAddressFromBech32(h.FeeAddress); err != nil {
return errorsmod.Wrapf(err, "invalid fee address")
}
if _, err := sdk.AccAddressFromBech32(h.OperatorAddressOnStride); err != nil {
return errorsmod.Wrapf(err, "invalid operator address")
}
Expand Down
15 changes: 0 additions & 15 deletions x/staketia/types/host_zone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ func fillDefaultHostZone(hostZone types.HostZone) types.HostZone {
hostZone.DepositAddress = fillDefaultValue(hostZone.DepositAddress, validAddress)
hostZone.RedemptionAddress = fillDefaultValue(hostZone.RedemptionAddress, validAddress)
hostZone.ClaimAddress = fillDefaultValue(hostZone.ClaimAddress, validAddress)
hostZone.FeeAddress = fillDefaultValue(hostZone.FeeAddress, validAddress)
hostZone.OperatorAddressOnStride = fillDefaultValue(hostZone.OperatorAddressOnStride, validAddress)
hostZone.SafeAddressOnStride = fillDefaultValue(hostZone.SafeAddressOnStride, validAddress)

Expand Down Expand Up @@ -152,13 +151,6 @@ func TestValidateHostZoneGenesis(t *testing.T) {
},
expectedError: "claim address must be specified",
},
{
name: "missing fee address",
hostZone: types.HostZone{
FeeAddress: Uninitialized,
},
expectedError: "fee address must be specified",
},
{
name: "missing operator address",
hostZone: types.HostZone{
Expand Down Expand Up @@ -194,13 +186,6 @@ func TestValidateHostZoneGenesis(t *testing.T) {
},
expectedError: "invalid claim address",
},
{
name: "invalid fee address",
hostZone: types.HostZone{
FeeAddress: "invalid_address",
},
expectedError: "invalid fee address",
},
{
name: "invalid operator address",
hostZone: types.HostZone{
Expand Down
4 changes: 3 additions & 1 deletion x/staketia/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const (

// RouterKey defines the routing key
RouterKey = ModuleName

// Module Account for Fee Collection
FeeAddress = "staketia_fee_address"
)

var (
Expand All @@ -23,7 +26,6 @@ var (
SlashRecordsKeyPrefix = []byte("slash-records")
SlashRecordStoreKeyPrefix = []byte("slash-record-id")
TransferInProgressRecordIdKeyPrefix = []byte("transfer-in-progress")
FeeAddressKey = []byte("fee-address")

ChannelIdBufferFixedLength int = 16
)
Expand Down
Loading

0 comments on commit 6a5dbc4

Please sign in to comment.