From 3a5c91f27f6acbcb8ad5d75b6a8da45ff0a76a1a Mon Sep 17 00:00:00 2001 From: keruch Date: Wed, 30 Oct 2024 19:40:33 +0100 Subject: [PATCH 1/6] feat: rollapp royalties --- utils/denom/iro.go | 16 ++++++++++++++ utils/denom/iro_test.go | 43 +++++++++++++++++++++++++++++++++++++ x/iro/keeper/create_plan.go | 5 +++-- x/iro/types/plan.go | 8 ++++--- x/rollapp/keeper/rollapp.go | 26 ++++++++++++++++++++++ 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 utils/denom/iro.go create mode 100644 utils/denom/iro_test.go diff --git a/utils/denom/iro.go b/utils/denom/iro.go new file mode 100644 index 000000000..72f5e8c80 --- /dev/null +++ b/utils/denom/iro.go @@ -0,0 +1,16 @@ +package denom + +import ( + "fmt" + "strings" + + "github.com/dymensionxyz/dymension/v3/x/iro/types" +) + +func IRODenom(rollappID string) string { + return fmt.Sprintf("%s%s", types.IROTokenPrefix, rollappID) +} + +func RollappIDFromIRODenom(denom string) (string, bool) { + return strings.CutPrefix(denom, types.IROTokenPrefix) +} diff --git a/utils/denom/iro_test.go b/utils/denom/iro_test.go new file mode 100644 index 000000000..e1e4baf2d --- /dev/null +++ b/utils/denom/iro_test.go @@ -0,0 +1,43 @@ +package denom + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dymensionxyz/dymension/v3/x/iro/types" +) + +func FuzzIRODenom(f *testing.F) { + f.Add("exampleRollappID") + f.Add("") + f.Add("123456") + f.Add("🚀🌕") + + f.Fuzz(func(t *testing.T, rollappID string) { + denom := IRODenom(rollappID) + id, ok := RollappIDFromIRODenom(denom) + require.True(t, ok) + require.Equal(t, rollappID, id) + }) +} + +func FuzzRollappIDFromIRODenom(f *testing.F) { + f.Add(types.IROTokenPrefix + "exampleRollappID") + f.Add(types.IROTokenPrefix) + f.Add("notfuture_prefix") + f.Add(types.IROTokenPrefix + "🚀🌕") + + f.Fuzz(func(t *testing.T, denom string) { + rollappID, ok := RollappIDFromIRODenom(denom) + if ok { + // Ensure that reconstructing the denom gives the original denom + reconstructedDenom := IRODenom(rollappID) + require.Equal(t, denom, reconstructedDenom) + } else { + // Denom do not have the prefix + require.False(t, strings.HasPrefix(denom, types.IROTokenPrefix)) + } + }) +} diff --git a/x/iro/keeper/create_plan.go b/x/iro/keeper/create_plan.go index 091fbda1e..9d5fc6b38 100644 --- a/x/iro/keeper/create_plan.go +++ b/x/iro/keeper/create_plan.go @@ -16,6 +16,7 @@ import ( "github.com/dymensionxyz/sdk-utils/utils/uevent" appparams "github.com/dymensionxyz/dymension/v3/app/params" + "github.com/dymensionxyz/dymension/v3/utils/denom" "github.com/dymensionxyz/dymension/v3/x/iro/types" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) @@ -162,8 +163,8 @@ func (k Keeper) CreateModuleAccountForPlan(ctx sdk.Context, plan types.Plan) (au // MintAllocation mints the allocated amount and registers the denom in the bank denom metadata store func (k Keeper) MintAllocation(ctx sdk.Context, allocatedAmount math.Int, rollappId, rollappTokenSymbol string, exponent uint64) (sdk.Coin, error) { - baseDenom := fmt.Sprintf("%s_%s", types.IROTokenPrefix, rollappId) - displayDenom := fmt.Sprintf("%s_%s", types.IROTokenPrefix, rollappTokenSymbol) + baseDenom := denom.IRODenom(rollappId) + displayDenom := denom.IRODenom(rollappTokenSymbol) metadata := banktypes.Metadata{ Description: fmt.Sprintf("Future token for rollapp %s", rollappId), DenomUnits: []*banktypes.DenomUnit{ diff --git a/x/iro/types/plan.go b/x/iro/types/plan.go index 7dfed17d3..c4b59c89e 100644 --- a/x/iro/types/plan.go +++ b/x/iro/types/plan.go @@ -8,9 +8,11 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/dymensionxyz/dymension/v3/utils/denom" ) -const IROTokenPrefix = "future" +const IROTokenPrefix = "future_" var MinTokenAllocation = math.LegacyNewDec(10) // min allocation in decimal representation @@ -78,9 +80,9 @@ func (p Plan) GetAddress() sdk.AccAddress { return addr } -// get IRO token's denom +// GetIRODenom returns IRO token's denom func (p Plan) GetIRODenom() string { - return fmt.Sprintf("%s_%s", IROTokenPrefix, p.RollappId) + return denom.IRODenom(p.RollappId) } func DefaultIncentivePlanParams() IncentivePlanParams { diff --git a/x/rollapp/keeper/rollapp.go b/x/rollapp/keeper/rollapp.go index 02ef7da74..57cd37896 100644 --- a/x/rollapp/keeper/rollapp.go +++ b/x/rollapp/keeper/rollapp.go @@ -2,11 +2,13 @@ package keeper import ( "fmt" + "strings" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/dymensionxyz/gerr-cosmos/gerrc" irotypes "github.com/dymensionxyz/dymension/v3/x/iro/types" @@ -219,6 +221,30 @@ func (k Keeper) GetRollappByName( return val, true } +func (k Keeper) GetRollappByDenom(ctx sdk.Context, denom string) (*types.Rollapp, error) { + // by future token + if rollappID, ok := denom.IRODenom(rollappId); ok { + return k.GetRollapp(ctx, rollappID), nil + } + // by ibc token + hash, err := transfertypes.ParseHexHash(strings.TrimPrefix(denom, "ibc/")) + // handle err... + var trace transfertypes.DenomTrace + trace, ok := k.transferKeeper.GetDenomTrace(ctx, hash) + // handle err... + + var port string + var channel string + strings.Split(trace.Path, "/") // set port,channel + return k.GetRollappByPortChan(ctx, port, channel) // exists already +} + +func (k Keeper) GetRollappOwnerByDenom(ctx sdk.Context, denom string) (sdk.AccAddress, error) { + ra, err := k.GetRollappByDenom(ctx, denom) + // handle err + return ra.OwnerAddr() +} + func (k Keeper) MustGetRollapp(ctx sdk.Context, rollappId string) types.Rollapp { ret, found := k.GetRollapp(ctx, rollappId) if !found { From 6116667799cfed44ff5aaf8db28894f7f5764168 Mon Sep 17 00:00:00 2001 From: keruch Date: Thu, 31 Oct 2024 10:37:01 +0100 Subject: [PATCH 2/6] get rollapp owner by denom --- app/keepers/keepers.go | 1 + testutil/keeper/dymns.go | 1 + testutil/keeper/rollapp.go | 2 +- utils/denom/ibc.go | 37 +++++++++++ utils/denom/ibc_test.go | 40 ++++++++++++ utils/denom/iro.go | 16 ----- x/dymns/keeper/keeper_suite_test.go | 1 + x/iro/keeper/create_plan.go | 5 +- x/iro/keeper/create_plan_test.go | 2 +- x/iro/types/plan.go | 13 +++- .../iro_test.go => x/iro/types/plan_test.go | 12 ++-- x/rollapp/keeper/expected_keepers.go | 7 +++ x/rollapp/keeper/keeper.go | 3 + x/rollapp/keeper/rollapp.go | 63 ++++++++++++++----- 14 files changed, 155 insertions(+), 48 deletions(-) create mode 100644 utils/denom/ibc.go create mode 100644 utils/denom/ibc_test.go delete mode 100644 utils/denom/iro.go rename utils/denom/iro_test.go => x/iro/types/plan_test.go (74%) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 5d494e6c5..5291c438e 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -359,6 +359,7 @@ func (a *AppKeepers) InitKeepers( a.IBCKeeper.ClientKeeper, nil, a.BankKeeper, + a.TransferKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil, ) diff --git a/testutil/keeper/dymns.go b/testutil/keeper/dymns.go index 76856a7c2..f3ebd8d00 100644 --- a/testutil/keeper/dymns.go +++ b/testutil/keeper/dymns.go @@ -95,6 +95,7 @@ func DymNSKeeper(t testing.TB) (dymnskeeper.Keeper, dymnstypes.BankKeeper, rolla rollappParamsSubspace, nil, nil, nil, nil, bankKeeper, + nil, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil, ) diff --git a/testutil/keeper/rollapp.go b/testutil/keeper/rollapp.go index 318be9ccb..a5887650b 100644 --- a/testutil/keeper/rollapp.go +++ b/testutil/keeper/rollapp.go @@ -40,7 +40,7 @@ func RollappKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { memStoreKey, "RollappParams", ) - k := keeper.NewKeeper(cdc, storeKey, paramsSubspace, nil, nil, nil, nil, nil, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil) + k := keeper.NewKeeper(cdc, storeKey, paramsSubspace, nil, nil, nil, nil, nil, nil, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil) ctx := sdk.NewContext(stateStore, cometbftproto.Header{}, false, log.NewNopLogger()) diff --git a/utils/denom/ibc.go b/utils/denom/ibc.go new file mode 100644 index 000000000..4f0f8ca6e --- /dev/null +++ b/utils/denom/ibc.go @@ -0,0 +1,37 @@ +package denom + +import ( + "strings" + + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" +) + +// ValidateIBCDenom validates that the given denomination is a valid fungible token representation (i.e 'ibc/{hash}') +// per ADR 001 https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md. +// If the denom is valid, return its hash-string part. Inspired by +// https://github.com/cosmos/ibc-go/blob/5d7655684554e4f577be9573ef94ef4ad6c82667/modules/apps/transfer/types/denom.go#L190. +func ValidateIBCDenom(denom string) (string, bool) { + denomSplit := strings.SplitN(denom, "/", 2) + + if len(denomSplit) == 2 && denomSplit[0] == transferTypes.DenomPrefix && strings.TrimSpace(denomSplit[1]) != "" { + return denomSplit[1], true + } + + return "", false +} + +// SourcePortChanFromTracePath extracts source port and channel from the provided IBC denom trace path. +// References: +// - https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md +// - https://github.com/cosmos/relayer/issues/288 +func SourcePortChanFromTracePath(tracePath string) (sourcePort, sourceChannel string, validTrace bool) { + sp := strings.Split(tracePath, "/") + if len(sp) < 2 { + return "", "", false + } + sourcePort, sourceChannel = sp[len(sp)-2], sp[len(sp)-1] + if sourcePort == "" || sourceChannel == "" { + return "", "", false + } + return sourcePort, sourceChannel, true +} diff --git a/utils/denom/ibc_test.go b/utils/denom/ibc_test.go new file mode 100644 index 000000000..17895b9c6 --- /dev/null +++ b/utils/denom/ibc_test.go @@ -0,0 +1,40 @@ +package denom_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dymensionxyz/dymension/v3/utils/denom" +) + +func TestSourcePortChanFromTracePath(t *testing.T) { + testCases := []struct { + name string + trace string + expValid bool + expPort string + expChan string + }{ + {"invalid: empty trace", "", false, "", ""}, + {"invalid: only port", "transfer", false, "", ""}, + {"invalid: only port with '/'", "transfer/", false, "", ""}, + {"invalid: only channel with '/'", "/channel-1", false, "", ""}, + {"invalid: only '/'", "/", false, "", ""}, + {"invalid: double '/'", "transfer//channel-1", false, "", ""}, + {"valid trace", "transfer/channel-1", true, "transfer", "channel-1"}, + {"valid trace with multiple port/channel pairs", "transfer/channel-1/transfer/channel-2", true, "transfer", "channel-2"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + port, channel, valid := denom.SourcePortChanFromTracePath(tc.trace) + + require.Equal(t, tc.expValid, valid) + if tc.expValid { + require.Equal(t, tc.expPort, port) + require.Equal(t, tc.expChan, channel) + } + }) + } +} diff --git a/utils/denom/iro.go b/utils/denom/iro.go deleted file mode 100644 index 72f5e8c80..000000000 --- a/utils/denom/iro.go +++ /dev/null @@ -1,16 +0,0 @@ -package denom - -import ( - "fmt" - "strings" - - "github.com/dymensionxyz/dymension/v3/x/iro/types" -) - -func IRODenom(rollappID string) string { - return fmt.Sprintf("%s%s", types.IROTokenPrefix, rollappID) -} - -func RollappIDFromIRODenom(denom string) (string, bool) { - return strings.CutPrefix(denom, types.IROTokenPrefix) -} diff --git a/x/dymns/keeper/keeper_suite_test.go b/x/dymns/keeper/keeper_suite_test.go index 6cbff1d3a..8229fcbc0 100644 --- a/x/dymns/keeper/keeper_suite_test.go +++ b/x/dymns/keeper/keeper_suite_test.go @@ -144,6 +144,7 @@ func (s *KeeperTestSuite) SetupTest() { rollappParamsSubspace, nil, nil, nil, nil, bk, + nil, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil, ) diff --git a/x/iro/keeper/create_plan.go b/x/iro/keeper/create_plan.go index 9d5fc6b38..d17708e4f 100644 --- a/x/iro/keeper/create_plan.go +++ b/x/iro/keeper/create_plan.go @@ -16,7 +16,6 @@ import ( "github.com/dymensionxyz/sdk-utils/utils/uevent" appparams "github.com/dymensionxyz/dymension/v3/app/params" - "github.com/dymensionxyz/dymension/v3/utils/denom" "github.com/dymensionxyz/dymension/v3/x/iro/types" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) @@ -163,8 +162,8 @@ func (k Keeper) CreateModuleAccountForPlan(ctx sdk.Context, plan types.Plan) (au // MintAllocation mints the allocated amount and registers the denom in the bank denom metadata store func (k Keeper) MintAllocation(ctx sdk.Context, allocatedAmount math.Int, rollappId, rollappTokenSymbol string, exponent uint64) (sdk.Coin, error) { - baseDenom := denom.IRODenom(rollappId) - displayDenom := denom.IRODenom(rollappTokenSymbol) + baseDenom := types.IRODenom(rollappId) + displayDenom := types.IRODenom(rollappTokenSymbol) metadata := banktypes.Metadata{ Description: fmt.Sprintf("Future token for rollapp %s", rollappId), DenomUnits: []*banktypes.DenomUnit{ diff --git a/x/iro/keeper/create_plan_test.go b/x/iro/keeper/create_plan_test.go index 9f8db72c3..7dc7a6ddd 100644 --- a/x/iro/keeper/create_plan_test.go +++ b/x/iro/keeper/create_plan_test.go @@ -112,7 +112,7 @@ func (s *KeeperTestSuite) TestMintAllocation() { k := s.App.IROKeeper allocatedAmount := sdk.NewInt(10).MulRaw(1e18) - expectedBaseDenom := fmt.Sprintf("%s_%s", types.IROTokenPrefix, rollappId) + expectedBaseDenom := types.IRODenom(rollappId) rollapp, _ := s.App.RollappKeeper.GetRollapp(s.Ctx, rollappId) minted, err := k.MintAllocation(s.Ctx, allocatedAmount, rollapp.RollappId, rollapp.GenesisInfo.NativeDenom.Base, uint64(rollapp.GenesisInfo.NativeDenom.Exponent)) diff --git a/x/iro/types/plan.go b/x/iro/types/plan.go index c4b59c89e..ba8e1aed4 100644 --- a/x/iro/types/plan.go +++ b/x/iro/types/plan.go @@ -3,17 +3,24 @@ package types import ( "errors" fmt "fmt" + "strings" time "time" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/dymensionxyz/dymension/v3/utils/denom" ) const IROTokenPrefix = "future_" +func IRODenom(rollappID string) string { + return fmt.Sprintf("%s%s", IROTokenPrefix, rollappID) +} + +func RollappIDFromIRODenom(denom string) (string, bool) { + return strings.CutPrefix(denom, IROTokenPrefix) +} + var MinTokenAllocation = math.LegacyNewDec(10) // min allocation in decimal representation func NewPlan(id uint64, rollappId string, allocation sdk.Coin, curve BondingCurve, start time.Time, end time.Time, incentivesParams IncentivePlanParams) Plan { @@ -82,7 +89,7 @@ func (p Plan) GetAddress() sdk.AccAddress { // GetIRODenom returns IRO token's denom func (p Plan) GetIRODenom() string { - return denom.IRODenom(p.RollappId) + return IRODenom(p.RollappId) } func DefaultIncentivePlanParams() IncentivePlanParams { diff --git a/utils/denom/iro_test.go b/x/iro/types/plan_test.go similarity index 74% rename from utils/denom/iro_test.go rename to x/iro/types/plan_test.go index e1e4baf2d..5c3f7d70f 100644 --- a/utils/denom/iro_test.go +++ b/x/iro/types/plan_test.go @@ -1,12 +1,10 @@ -package denom +package types import ( "strings" "testing" "github.com/stretchr/testify/require" - - "github.com/dymensionxyz/dymension/v3/x/iro/types" ) func FuzzIRODenom(f *testing.F) { @@ -24,10 +22,10 @@ func FuzzIRODenom(f *testing.F) { } func FuzzRollappIDFromIRODenom(f *testing.F) { - f.Add(types.IROTokenPrefix + "exampleRollappID") - f.Add(types.IROTokenPrefix) + f.Add(IROTokenPrefix + "exampleRollappID") + f.Add(IROTokenPrefix) f.Add("notfuture_prefix") - f.Add(types.IROTokenPrefix + "🚀🌕") + f.Add(IROTokenPrefix + "🚀🌕") f.Fuzz(func(t *testing.T, denom string) { rollappID, ok := RollappIDFromIRODenom(denom) @@ -37,7 +35,7 @@ func FuzzRollappIDFromIRODenom(f *testing.F) { require.Equal(t, denom, reconstructedDenom) } else { // Denom do not have the prefix - require.False(t, strings.HasPrefix(denom, types.IROTokenPrefix)) + require.False(t, strings.HasPrefix(denom, IROTokenPrefix)) } }) } diff --git a/x/rollapp/keeper/expected_keepers.go b/x/rollapp/keeper/expected_keepers.go index c039c51d1..3d5fa45fe 100644 --- a/x/rollapp/keeper/expected_keepers.go +++ b/x/rollapp/keeper/expected_keepers.go @@ -1,8 +1,11 @@ package keeper import ( + tmbytes "github.com/cometbft/cometbft/libs/bytes" sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/dymensionxyz/dymension/v3/x/sequencer/types" ) @@ -34,3 +37,7 @@ type BankKeeper interface { type CanonicalLightClientKeeper interface { GetRollappForClientID(ctx sdk.Context, clientID string) (string, bool) } + +type TransferKeeper interface { + GetDenomTrace(ctx sdk.Context, denomTraceHash tmbytes.HexBytes) (transfertypes.DenomTrace, bool) +} diff --git a/x/rollapp/keeper/keeper.go b/x/rollapp/keeper/keeper.go index d81ef7ae9..7279353ae 100644 --- a/x/rollapp/keeper/keeper.go +++ b/x/rollapp/keeper/keeper.go @@ -28,6 +28,7 @@ type Keeper struct { channelKeeper ChannelKeeper sequencerKeeper SequencerKeeper bankKeeper BankKeeper + transferKeeper TransferKeeper vulnerableDRSVersions collections.KeySet[string] registeredRollappDenoms collections.KeySet[collections.Pair[string, string]] @@ -44,6 +45,7 @@ func NewKeeper( ibcclientKeeper IBCClientKeeper, sequencerKeeper SequencerKeeper, bankKeeper BankKeeper, + transferKeeper TransferKeeper, authority string, canonicalClientKeeper CanonicalLightClientKeeper, ) *Keeper { @@ -67,6 +69,7 @@ func NewKeeper( ibcClientKeeper: ibcclientKeeper, sequencerKeeper: sequencerKeeper, bankKeeper: bankKeeper, + transferKeeper: transferKeeper, vulnerableDRSVersions: collections.NewKeySet( collections.NewSchemaBuilder(collcompat.NewKVStoreService(storeKey)), collections.NewPrefix(types.VulnerableDRSVersionsKeyPrefix), diff --git a/x/rollapp/keeper/rollapp.go b/x/rollapp/keeper/rollapp.go index 57cd37896..27646343e 100644 --- a/x/rollapp/keeper/rollapp.go +++ b/x/rollapp/keeper/rollapp.go @@ -1,16 +1,17 @@ package keeper import ( + "errors" "fmt" - "strings" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/dymensionxyz/gerr-cosmos/gerrc" + udenom "github.com/dymensionxyz/dymension/v3/utils/denom" irotypes "github.com/dymensionxyz/dymension/v3/x/iro/types" "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) @@ -221,28 +222,56 @@ func (k Keeper) GetRollappByName( return val, true } +// GetRollappByDenom tries to extract a rollapp ID from the provided denom and returns a rollapp object if found. +// Denom may be either IRO token or IBC token. func (k Keeper) GetRollappByDenom(ctx sdk.Context, denom string) (*types.Rollapp, error) { - // by future token - if rollappID, ok := denom.IRODenom(rollappId); ok { - return k.GetRollapp(ctx, rollappID), nil - } - // by ibc token - hash, err := transfertypes.ParseHexHash(strings.TrimPrefix(denom, "ibc/")) - // handle err... - var trace transfertypes.DenomTrace + // by IRO token + // try to get the rollapp ID from the denom + rollappID, ok := irotypes.RollappIDFromIRODenom(denom) + if ok { + ra, ok := k.GetRollapp(ctx, rollappID) + if ok { + return &ra, nil + } + return nil, types.ErrUnknownRollappID + } + + // by IBC token + // first, validate that the denom is IBC + hexHash, ok := udenom.ValidateIBCDenom(denom) + if !ok { + return nil, errors.New("denom is neither IRO nor IBC") + } + + // parse IBC denom hash string + hash, err := transferTypes.ParseHexHash(hexHash) + if err != nil { + return nil, fmt.Errorf("parse IBC hex hash: %w", err) + } + // get IBC denom trace trace, ok := k.transferKeeper.GetDenomTrace(ctx, hash) - // handle err... + if !ok { + return nil, errors.New("denom trace not found") + } + // try to get source port and channel from the trace + sourcePort, sourceChan, ok := udenom.SourcePortChanFromTracePath(trace.Path) + if !ok { + return nil, errors.New("invalid denom trace path") + } - var port string - var channel string - strings.Split(trace.Path, "/") // set port,channel - return k.GetRollappByPortChan(ctx, port, channel) // exists already + return k.GetRollappByPortChan(ctx, sourcePort, sourceChan) } func (k Keeper) GetRollappOwnerByDenom(ctx sdk.Context, denom string) (sdk.AccAddress, error) { ra, err := k.GetRollappByDenom(ctx, denom) - // handle err - return ra.OwnerAddr() + if err != nil { + return nil, fmt.Errorf("get rollapp by denom: %w", err) + } + owner, err := sdk.AccAddressFromBech32(ra.Owner) + if err != nil { + return nil, fmt.Errorf("owner account address: %w", err) + } + return owner, nil } func (k Keeper) MustGetRollapp(ctx sdk.Context, rollappId string) types.Rollapp { From 4abe341af85031299cb2b46250827a14319ef60e Mon Sep 17 00:00:00 2001 From: keruch Date: Fri, 1 Nov 2024 10:39:53 +0100 Subject: [PATCH 3/6] feat: rev share in x/bridgingfee and x/iro --- app/keepers/keepers.go | 8 +++- testutil/keeper/iro.go | 4 +- x/bridgingfee/ibc_module.go | 30 ++++++++++++--- x/iro/keeper/keeper.go | 3 ++ x/iro/keeper/trade.go | 37 ++++++++++-------- x/iro/keeper/trade_test.go | 68 ++++++++++++++++++++++++++++++--- x/iro/types/expected_keepers.go | 8 +++- x/rollapp/keeper/rollapp.go | 5 +++ 8 files changed, 131 insertions(+), 32 deletions(-) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 5291c438e..4e87aed19 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -311,7 +311,9 @@ func (a *AppKeepers) InitKeepers( appCodec, a.keys[gammtypes.StoreKey], a.GetSubspace(gammtypes.ModuleName), a.AccountKeeper, - a.BankKeeper, a.DistrKeeper, + a.BankKeeper, + a.DistrKeeper, + a.RollappKeeper, ) a.GAMMKeeper = &gammKeeper @@ -417,6 +419,7 @@ func (a *AppKeepers) InitKeepers( a.GAMMKeeper, a.IncentivesKeeper, a.PoolManagerKeeper, + a.TxFeesKeeper, ) a.SponsorshipKeeper = sponsorshipkeeper.NewKeeper( @@ -528,10 +531,11 @@ func (a *AppKeepers) InitTransferStack() { a.TransferStack = ibctransfer.NewIBCModule(a.TransferKeeper) a.TransferStack = bridgingfee.NewIBCModule( a.TransferStack.(ibctransfer.IBCModule), + *a.RollappKeeper, a.DelayedAckKeeper, a.TransferKeeper, + *a.TxFeesKeeper, a.AccountKeeper.GetModuleAddress(txfeestypes.ModuleName), - *a.RollappKeeper, ) a.TransferStack = packetforwardmiddleware.NewIBCMiddleware( a.TransferStack, diff --git a/testutil/keeper/iro.go b/testutil/keeper/iro.go index 72428b5e4..89cee3360 100644 --- a/testutil/keeper/iro.go +++ b/testutil/keeper/iro.go @@ -11,9 +11,10 @@ import ( "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/dymensionxyz/dymension/v3/x/iro/keeper" "github.com/dymensionxyz/dymension/v3/x/iro/types" - "github.com/stretchr/testify/require" ) func IROKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { @@ -38,6 +39,7 @@ func IROKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { nil, nil, nil, + nil, ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) diff --git a/x/bridgingfee/ibc_module.go b/x/bridgingfee/ibc_module.go index 43ebfb3d0..00764e88c 100644 --- a/x/bridgingfee/ibc_module.go +++ b/x/bridgingfee/ibc_module.go @@ -10,6 +10,7 @@ import ( channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" "github.com/dymensionxyz/sdk-utils/utils/uevent" + txfeeskeeper "github.com/osmosis-labs/osmosis/v15/x/txfees/keeper" commontypes "github.com/dymensionxyz/dymension/v3/x/common/types" delayedackkeeper "github.com/dymensionxyz/dymension/v3/x/delayedack/keeper" @@ -29,22 +30,25 @@ type IBCModule struct { rollappKeeper rollappkeeper.Keeper delayedAckKeeper delayedackkeeper.Keeper transferKeeper transferkeeper.Keeper + txFeesKeeper txfeeskeeper.Keeper feeModuleAddr sdk.AccAddress } func NewIBCModule( next ibctransfer.IBCModule, - keeper delayedackkeeper.Keeper, + rollappKeeper rollappkeeper.Keeper, + delayedAckKeeper delayedackkeeper.Keeper, transferKeeper transferkeeper.Keeper, + txFeesKeeper txfeeskeeper.Keeper, feeModuleAddr sdk.AccAddress, - rollappKeeper rollappkeeper.Keeper, ) *IBCModule { return &IBCModule{ IBCModule: next, - delayedAckKeeper: keeper, + rollappKeeper: rollappKeeper, + delayedAckKeeper: delayedAckKeeper, transferKeeper: transferKeeper, + txFeesKeeper: txFeesKeeper, feeModuleAddr: feeModuleAddr, - rollappKeeper: rollappKeeper, } } @@ -90,9 +94,25 @@ func (w *IBCModule) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, re err = w.transferKeeper.OnRecvPacket(ctx, packet, feeData.FungibleTokenPacketData) if err != nil { l.Error("Charge bridging fee.", "err", err) - // we continue as we don't want the fee charge to fail the transfer in any case + // We continue as we don't want the fee charge to fail the transfer in any case fee = sdk.ZeroInt() } else { + // Charge the fee from the txfees module account: construct the IBC denom and use it for the fee coin. + // The IBC denom construction is inherited from here: + // https://github.com/cosmos/ibc-go/blob/v7.5.1/modules/apps/transfer/keeper/relay.go#L244-L259 + var ( + sourcePrefix = transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + prefixedDenom = sourcePrefix + feeData.Denom + denomTrace = transfertypes.ParseDenomTrace(prefixedDenom) + feeCoin = sdk.NewCoin(denomTrace.IBCDenom(), fee) + ) + err = w.txFeesKeeper.ChargeFeesFromModuleAcc(ctx, feeCoin) + if err != nil { + // We continue as we don't want the fee charge to fail the transfer in any case. + // Also, the fee was already successfully sent to x/txfees and charging will be retried at the epoch end. + w.logger(ctx, packet, "OnRecvPacket").Error("Charge bridging fee from x/txfees account.", "err", err) + } + ctx.EventManager().EmitEvent( sdk.NewEvent( EventTypeBridgingFee, diff --git a/x/iro/keeper/keeper.go b/x/iro/keeper/keeper.go index bdd6788d3..5f1b685c4 100644 --- a/x/iro/keeper/keeper.go +++ b/x/iro/keeper/keeper.go @@ -26,6 +26,7 @@ type Keeper struct { gk types.GammKeeper pm types.PoolManagerKeeper ik types.IncentivesKeeper + tk types.TxFeesKeeper } func NewKeeper( @@ -39,6 +40,7 @@ func NewKeeper( gk types.GammKeeper, ik types.IncentivesKeeper, pm types.PoolManagerKeeper, + tk types.TxFeesKeeper, ) *Keeper { return &Keeper{ cdc: cdc, @@ -51,6 +53,7 @@ func NewKeeper( gk: gk, ik: ik, pm: pm, + tk: tk, } } diff --git a/x/iro/keeper/trade.go b/x/iro/keeper/trade.go index ee0339bda..eb3be38e6 100644 --- a/x/iro/keeper/trade.go +++ b/x/iro/keeper/trade.go @@ -2,14 +2,11 @@ package keeper import ( "context" + "fmt" errorsmod "cosmossdk.io/errors" "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - - txfeestypes "github.com/osmosis-labs/osmosis/v15/x/txfees/types" - "github.com/dymensionxyz/sdk-utils/utils/uevent" appparams "github.com/dymensionxyz/dymension/v3/app/params" @@ -62,7 +59,7 @@ func (m msgServer) Sell(ctx context.Context, req *types.MsgSell) (*types.MsgSell // Buy buys fixed amount of allocation with price according to the price curve func (k Keeper) Buy(ctx sdk.Context, planId string, buyer sdk.AccAddress, amountTokensToBuy, maxCostAmt math.Int) error { - plan, err := k.GetTradeableIRO(ctx, planId, buyer.String()) + plan, err := k.GetTradeableIRO(ctx, planId, buyer) if err != nil { return err } @@ -86,7 +83,8 @@ func (k Keeper) Buy(ctx sdk.Context, planId string, buyer sdk.AccAddress, amount // Charge taker fee takerFee := sdk.NewCoin(appparams.BaseDenom, takerFeeAmt) - err = k.chargeTakerFee(ctx, takerFee, buyer) + owner := k.rk.MustGetRollappOwner(ctx, plan.RollappId) + err = k.chargeTakerFee(ctx, takerFee, buyer, &owner) if err != nil { return err } @@ -127,7 +125,7 @@ func (k Keeper) Buy(ctx sdk.Context, planId string, buyer sdk.AccAddress, amount // BuyExactSpend uses exact amount of DYM to buy tokens on the curve func (k Keeper) BuyExactSpend(ctx sdk.Context, planId string, buyer sdk.AccAddress, amountToSpend, minTokensAmt math.Int) error { - plan, err := k.GetTradeableIRO(ctx, planId, buyer.String()) + plan, err := k.GetTradeableIRO(ctx, planId, buyer) if err != nil { return err } @@ -156,7 +154,8 @@ func (k Keeper) BuyExactSpend(ctx sdk.Context, planId string, buyer sdk.AccAddre // Charge taker fee takerFee := sdk.NewCoin(appparams.BaseDenom, takerFeeAmt) - err = k.chargeTakerFee(ctx, takerFee, buyer) + owner := k.rk.MustGetRollappOwner(ctx, plan.RollappId) + err = k.chargeTakerFee(ctx, takerFee, buyer, &owner) if err != nil { return err } @@ -197,7 +196,7 @@ func (k Keeper) BuyExactSpend(ctx sdk.Context, planId string, buyer sdk.AccAddre // Sell sells allocation with price according to the price curve func (k Keeper) Sell(ctx sdk.Context, planId string, seller sdk.AccAddress, amountTokensToSell, minIncomeAmt math.Int) error { - plan, err := k.GetTradeableIRO(ctx, planId, seller.String()) + plan, err := k.GetTradeableIRO(ctx, planId, seller) if err != nil { return err } @@ -216,7 +215,8 @@ func (k Keeper) Sell(ctx sdk.Context, planId string, seller sdk.AccAddress, amou // Charge taker fee takerFee := sdk.NewCoin(appparams.BaseDenom, takerFeeAmt) - err = k.chargeTakerFee(ctx, takerFee, seller) + owner := k.rk.MustGetRollappOwner(ctx, plan.RollappId) + err = k.chargeTakerFee(ctx, takerFee, seller, &owner) if err != nil { return err } @@ -259,7 +259,7 @@ func (k Keeper) Sell(ctx sdk.Context, planId string, seller sdk.AccAddress, amou // - plan must exist // - plan must not be settled // - plan must have started (unless the trader is the owner) -func (k Keeper) GetTradeableIRO(ctx sdk.Context, planId string, trader string) (*types.Plan, error) { +func (k Keeper) GetTradeableIRO(ctx sdk.Context, planId string, trader sdk.AccAddress) (*types.Plan, error) { plan, found := k.GetPlan(ctx, planId) if !found { return nil, types.ErrPlanNotFound @@ -270,17 +270,22 @@ func (k Keeper) GetTradeableIRO(ctx sdk.Context, planId string, trader string) ( } // Validate start time started (unless the trader is the owner) - if ctx.BlockTime().Before(plan.StartTime) && k.rk.MustGetRollapp(ctx, plan.RollappId).Owner != trader { + owner := k.rk.MustGetRollappOwner(ctx, plan.RollappId) + if ctx.BlockTime().Before(plan.StartTime) && !owner.Equals(trader) { return nil, errorsmod.Wrapf(types.ErrPlanNotStarted, "planId: %d", plan.Id) } return &plan, nil } -// chargeTakerFee charges taker fee from the sender -// takerFee sent to the txfees module -func (k Keeper) chargeTakerFee(ctx sdk.Context, takerFee sdk.Coin, sender sdk.AccAddress) error { - return k.BK.SendCoinsFromAccountToModule(ctx, sender, txfeestypes.ModuleName, sdk.NewCoins(takerFee)) +// chargeTakerFee charges taker fee from the sender. +// The fee is sent to the txfees module and the beneficiary if presented. +func (k Keeper) chargeTakerFee(ctx sdk.Context, takerFeeCoin sdk.Coin, sender sdk.AccAddress, beneficiary *sdk.AccAddress) error { + err := k.tk.ChargeFees(ctx, sender, takerFeeCoin, beneficiary) + if err != nil { + return fmt.Errorf("charge fees: sender: %s: fee: %s: %w", sender, takerFeeCoin, err) + } + return nil } // ApplyTakerFee applies taker fee to the cost diff --git a/x/iro/keeper/trade_test.go b/x/iro/keeper/trade_test.go index eb62d5723..8a9e97a02 100644 --- a/x/iro/keeper/trade_test.go +++ b/x/iro/keeper/trade_test.go @@ -5,8 +5,7 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/osmosis-labs/osmosis/v15/x/txfees" + "github.com/cosmos/gogoproto/proto" "github.com/dymensionxyz/dymension/v3/testutil/sample" "github.com/dymensionxyz/dymension/v3/x/iro/types" @@ -25,6 +24,7 @@ func (s *KeeperTestSuite) TestBuy() { rollapp, _ := s.App.RollappKeeper.GetRollapp(s.Ctx, rollappId) planId, err := k.CreatePlan(s.Ctx, totalAllocation, startTime, startTime.Add(time.Hour), rollapp, curve, incentives) s.Require().NoError(err) + initialOwnerBalance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.RollappKeeper.MustGetRollappOwner(s.Ctx, rollappId)) plan := k.MustGetPlan(s.Ctx, planId) reservedTokens := plan.SoldAmt @@ -71,12 +71,20 @@ func (s *KeeperTestSuite) TestBuy() { s.Require().NoError(err) s.Assert().True(expectedCost2.GT(expectedCost)) + // extract taker fee from buy event + takerFeeAmt := s.TakerFeeAmtAfterBuy() + // assert balance buyerFinalBalance := s.App.BankKeeper.GetAllBalances(s.Ctx, buyer) - takerFee := s.App.BankKeeper.GetAllBalances(s.Ctx, authtypes.NewModuleAddress(txfees.ModuleName)) - expectedBalance := buyersFunds.AmountOf("adym").Sub(expectedCost).Sub(takerFee.AmountOf("adym")) + expectedBalance := buyersFunds.AmountOf("adym").Sub(expectedCost).Sub(takerFeeAmt) s.Require().Equal(expectedBalance, buyerFinalBalance.AmountOf("adym")) s.Require().Equal(buyAmt, buyerFinalBalance.AmountOf(plan.GetIRODenom())) + + // assert owner is incentivized: it must get 50% of taker fee + currentOwnerBalance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.RollappKeeper.MustGetRollappOwner(s.Ctx, rollappId)) + ownerBalanceChange := currentOwnerBalance.Sub(initialOwnerBalance...) + ownerRevenue := takerFeeAmt.QuoRaw(2) + s.Require().Equal(ownerRevenue, ownerBalanceChange.AmountOf("adym")) } func (s *KeeperTestSuite) TestTradeAfterSettled() { @@ -150,9 +158,11 @@ func (s *KeeperTestSuite) TestTakerFee() { err = k.Buy(s.Ctx, planId, buyer, buyAmt, buyAmt.Add(expectedTakerFee)) s.Require().NoError(err) + // Extract taker fee from buy event + takerFeeAmtBuy := s.TakerFeeAmtAfterBuy() + // Check taker fee - takerFee := s.App.BankKeeper.GetAllBalances(s.Ctx, authtypes.NewModuleAddress(txfees.ModuleName)) - s.Require().Equal(expectedTakerFee, takerFee.AmountOf("adym")) + s.Require().Equal(expectedTakerFee, takerFeeAmtBuy) } func (s *KeeperTestSuite) TestSell() { @@ -168,6 +178,7 @@ func (s *KeeperTestSuite) TestSell() { rollapp, _ := s.App.RollappKeeper.GetRollapp(s.Ctx, rollappId) planId, err := k.CreatePlan(s.Ctx, totalAllocation, startTime, startTime.Add(time.Hour), rollapp, curve, incentives) s.Require().NoError(err) + initialOwnerBalance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.RollappKeeper.MustGetRollappOwner(s.Ctx, rollappId)) s.Ctx = s.Ctx.WithBlockTime(startTime.Add(time.Minute)) buyer := sample.Acc() @@ -180,16 +191,29 @@ func (s *KeeperTestSuite) TestSell() { err = k.Buy(s.Ctx, planId, buyer, buyAmt, maxAmt) s.Require().NoError(err) + // Extract taker fee from buy event + takerFeeAmtBuy := s.TakerFeeAmtAfterBuy() + // Sell tokens sellAmt := sdk.NewInt(500).MulRaw(1e18) minReceive := sdk.NewInt(1) // Set a very low minReceive for testing purposes err = k.Sell(s.Ctx, planId, buyer, sellAmt, minReceive) s.Require().NoError(err) + // Extract taker fee from sell event + takerFeeAmtSell := s.TakerFeeAmtAfterSell() + // Check balances after sell balances := s.App.BankKeeper.GetAllBalances(s.Ctx, buyer) s.Require().Equal(buyAmt.Sub(sellAmt), balances.AmountOf(k.MustGetPlan(s.Ctx, planId).GetIRODenom())) + // Assert owner is incentivized: it must get 50% of taker fee + currentOwnerBalance := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.RollappKeeper.MustGetRollappOwner(s.Ctx, rollappId)) + ownerBalanceChange := currentOwnerBalance.Sub(initialOwnerBalance...) + // ownerRevenue = (takerFeeBuy + takerFeeSell) / 2 + ownerRevenue := takerFeeAmtBuy.Add(takerFeeAmtSell).QuoRaw(2) + s.Require().Equal(ownerRevenue, ownerBalanceChange.AmountOf("adym")) + // Attempt to sell more than owned - should fail err = k.Sell(s.Ctx, planId, buyer, buyAmt, minReceive) s.Require().Error(err) @@ -199,3 +223,35 @@ func (s *KeeperTestSuite) TestSell() { err = k.Sell(s.Ctx, planId, buyer, sellAmt, highMinReceive) s.Require().Error(err) } + +func (s *KeeperTestSuite) TakerFeeAmtAfterSell() sdk.Int { + // Extract taker fee from event + eventName := proto.MessageName(new(types.EventSell)) + takerFeeAmt, found := s.ExtractTakerFeeAmtFromEvents(s.Ctx.EventManager().Events(), eventName) + s.Require().True(found) + return takerFeeAmt +} + +func (s *KeeperTestSuite) TakerFeeAmtAfterBuy() sdk.Int { + // Extract taker fee from event + eventName := proto.MessageName(new(types.EventBuy)) + takerFeeAmt, found := s.ExtractTakerFeeAmtFromEvents(s.Ctx.EventManager().Events(), eventName) + s.Require().True(found) + return takerFeeAmt +} + +func (s *KeeperTestSuite) ExtractTakerFeeAmtFromEvents(events []sdk.Event, eventName string) (sdk.Int, bool) { + event, found := s.FindLastEventOfType(events, eventName) + if !found { + return sdk.Int{}, false + } + attrs := s.ExtractAttributes(event) + for key, value := range attrs { + if key == "taker_fee" { + fee, ok := sdk.NewIntFromString(value) + s.Require().True(ok) + return fee, true + } + } + return sdk.ZeroInt(), false +} diff --git a/x/iro/types/expected_keepers.go b/x/iro/types/expected_keepers.go index b27ec2c97..a6f143abd 100644 --- a/x/iro/types/expected_keepers.go +++ b/x/iro/types/expected_keepers.go @@ -1,7 +1,7 @@ package types import ( - time "time" + "time" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -56,5 +56,9 @@ type PoolManagerKeeper interface { type RollappKeeper interface { GetRollapp(ctx sdk.Context, rollappId string) (rollapp rollapptypes.Rollapp, found bool) SetIROPlanToRollapp(ctx sdk.Context, rollapp *rollapptypes.Rollapp, iro Plan) error - MustGetRollapp(ctx sdk.Context, rollappId string) rollapptypes.Rollapp + MustGetRollappOwner(ctx sdk.Context, rollappID string) sdk.AccAddress +} + +type TxFeesKeeper interface { + ChargeFees(ctx sdk.Context, sender sdk.AccAddress, takerFeeCoin sdk.Coin, beneficiary *sdk.AccAddress) error } diff --git a/x/rollapp/keeper/rollapp.go b/x/rollapp/keeper/rollapp.go index 27646343e..c9c53f17a 100644 --- a/x/rollapp/keeper/rollapp.go +++ b/x/rollapp/keeper/rollapp.go @@ -274,6 +274,11 @@ func (k Keeper) GetRollappOwnerByDenom(ctx sdk.Context, denom string) (sdk.AccAd return owner, nil } +func (k Keeper) MustGetRollappOwner(ctx sdk.Context, rollappID string) sdk.AccAddress { + ra := k.MustGetRollapp(ctx, rollappID) + return sdk.MustAccAddressFromBech32(ra.Owner) +} + func (k Keeper) MustGetRollapp(ctx sdk.Context, rollappId string) types.Rollapp { ret, found := k.GetRollapp(ctx, rollappId) if !found { From 272f355bb51153b9de114f29091d2b4765e5c8f8 Mon Sep 17 00:00:00 2001 From: keruch Date: Fri, 1 Nov 2024 15:33:35 +0100 Subject: [PATCH 4/6] fix tests --- app/keepers/keepers.go | 3 ++- go.mod | 4 ++-- go.sum | 6 ++---- ibctesting/bridging_fee_test.go | 4 ++-- x/bridgingfee/ibc_module.go | 14 +++++--------- x/gamm/amm_test.go | 6 +++--- x/incentives/keeper/msg_server.go | 11 +++++------ x/incentives/keeper/msg_server_test.go | 8 ++++---- x/incentives/types/expected_keepers.go | 1 + x/iro/keeper/trade.go | 2 +- x/iro/types/expected_keepers.go | 2 +- 11 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 4e87aed19..c3a5fbcf8 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -313,7 +313,6 @@ func (a *AppKeepers) InitKeepers( a.AccountKeeper, a.BankKeeper, a.DistrKeeper, - a.RollappKeeper, ) a.GAMMKeeper = &gammKeeper @@ -366,6 +365,8 @@ func (a *AppKeepers) InitKeepers( nil, ) + a.GAMMKeeper.SetRollapp(a.RollappKeeper) + a.SequencerKeeper = *sequencermodulekeeper.NewKeeper( appCodec, a.keys[sequencermoduletypes.StoreKey], diff --git a/go.mod b/go.mod index 3cec47b75..0284e70b1 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/decred/dcrd/dcrec/edwards v1.0.0 github.com/dustin/go-humanize v1.0.1 github.com/dymensionxyz/gerr-cosmos v1.1.0 - github.com/dymensionxyz/sdk-utils v0.2.10 + github.com/dymensionxyz/sdk-utils v0.2.12 github.com/ethereum/go-ethereum v1.10.26 github.com/evmos/ethermint v0.22.0 github.com/gogo/protobuf v1.3.3 @@ -241,7 +241,7 @@ replace ( github.com/evmos/ethermint => github.com/dymensionxyz/ethermint v0.22.0-dymension-v0.4.1.0.20241013112411-5ef491708a2d github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/osmosis-labs/osmosis/osmomath => github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 - github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241030075435-24ccb7025a59 + github.com/osmosis-labs/osmosis/v15 => ../dym-osmo // broken goleveldb github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index c578484c6..77975ba22 100644 --- a/go.sum +++ b/go.sum @@ -506,10 +506,8 @@ github.com/dymensionxyz/gerr-cosmos v1.1.0 h1:IW/P7HCB/iP9kgk3VXaWUoMoyx3vD76YO6 github.com/dymensionxyz/gerr-cosmos v1.1.0/go.mod h1:n+0olxPogzWqFKba45mCpvrHLGmeS8W9UZjggHnWk6c= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 h1:EskhZ6ILN3vwJ6l8gPWPZ49RFSB52WghT5v+pmzrNCI= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43/go.mod h1:SdGCL9CZb14twRAJUSzb7bRE0OoopRpF2Hnd1UhJpFU= -github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241030075435-24ccb7025a59 h1:xuo5OCex6XT3HmL8O9l/+jsbT0D+Ib0LzTXQbNrDOOQ= -github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241030075435-24ccb7025a59/go.mod h1:2rsnXAdjYfXtyEw0mNwAdOiAccALYjAPvINGUf9Qg7Y= -github.com/dymensionxyz/sdk-utils v0.2.10 h1:dvUVzIpsdWGr5Ex+ltl/x1Abi+7sliwsGM1xAx/0p0k= -github.com/dymensionxyz/sdk-utils v0.2.10/go.mod h1:it9owYOpnIe17+ftTATQNDN4z+mBQx20/2Jm8SK15Rk= +github.com/dymensionxyz/sdk-utils v0.2.12 h1:wrcof+IP0AJQ7vvMRVpSekNNwa6B7ghAspHRjp/k+Lk= +github.com/dymensionxyz/sdk-utils v0.2.12/go.mod h1:it9owYOpnIe17+ftTATQNDN4z+mBQx20/2Jm8SK15Rk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= diff --git a/ibctesting/bridging_fee_test.go b/ibctesting/bridging_fee_test.go index dcc031414..b80b4f709 100644 --- a/ibctesting/bridging_fee_test.go +++ b/ibctesting/bridging_fee_test.go @@ -105,8 +105,8 @@ func (s *bridgingFeeSuite) TestBridgingFee() { finalBalance := s.hubApp().BankKeeper.SpendableCoins(s.hubCtx(), recipient) s.Equal(expectedBalance, finalBalance) - // check fees + // check fees are burned addr := s.hubApp().AccountKeeper.GetModuleAccount(s.hubCtx(), txfees.ModuleName) txFeesBalance := s.hubApp().BankKeeper.GetBalance(s.hubCtx(), addr.GetAddress(), denom) - s.Equal(expectedFee, txFeesBalance.Amount) + s.True(txFeesBalance.IsZero()) } diff --git a/x/bridgingfee/ibc_module.go b/x/bridgingfee/ibc_module.go index 00764e88c..c246d0db4 100644 --- a/x/bridgingfee/ibc_module.go +++ b/x/bridgingfee/ibc_module.go @@ -10,6 +10,7 @@ import ( channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" "github.com/dymensionxyz/sdk-utils/utils/uevent" + "github.com/dymensionxyz/sdk-utils/utils/uibc" txfeeskeeper "github.com/osmosis-labs/osmosis/v15/x/txfees/keeper" commontypes "github.com/dymensionxyz/dymension/v3/x/common/types" @@ -98,15 +99,10 @@ func (w *IBCModule) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, re fee = sdk.ZeroInt() } else { // Charge the fee from the txfees module account: construct the IBC denom and use it for the fee coin. - // The IBC denom construction is inherited from here: - // https://github.com/cosmos/ibc-go/blob/v7.5.1/modules/apps/transfer/keeper/relay.go#L244-L259 - var ( - sourcePrefix = transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) - prefixedDenom = sourcePrefix + feeData.Denom - denomTrace = transfertypes.ParseDenomTrace(prefixedDenom) - feeCoin = sdk.NewCoin(denomTrace.IBCDenom(), fee) - ) - err = w.txFeesKeeper.ChargeFeesFromModuleAcc(ctx, feeCoin) + denomTrace := uibc.GetForeignDenomTrace(packet.GetDestChannel(), feeData.Denom) + feeCoin := sdk.NewCoin(denomTrace.IBCDenom(), fee) + + err = w.txFeesKeeper.ChargeFees(ctx, feeCoin, nil, transfer.Receiver) if err != nil { // We continue as we don't want the fee charge to fail the transfer in any case. // Also, the fee was already successfully sent to x/txfees and charging will be retried at the epoch end. diff --git a/x/gamm/amm_test.go b/x/gamm/amm_test.go index deec4d7b6..3d680067d 100644 --- a/x/gamm/amm_test.go +++ b/x/gamm/amm_test.go @@ -4,11 +4,11 @@ import ( "fmt" "testing" + cometbftproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer" "github.com/stretchr/testify/suite" - cometbftproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/dymensionxyz/dymension/v3/app/apptesting" "github.com/dymensionxyz/dymension/v3/testutil/sample" ) @@ -47,9 +47,9 @@ func (s *KeeperTestSuite) TestSwapsRevenue() { expRevenue bool }{ { - name: "1% swap fee, 1% taker fee", + name: "1% swap fee, 0.9% taker fee", swapFee: sdk.NewDecWithPrec(1, 2), // 1% - takerFee: sdk.NewDecWithPrec(1, 2), // 1% + takerFee: sdk.NewDecWithPrec(9, 3), // 0.9% expRevenue: true, }, { diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index 22110e00d..38cab4039 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/osmosis-labs/osmosis/v15/osmoutils" - txfeestypes "github.com/osmosis-labs/osmosis/v15/x/txfees/types" "github.com/dymensionxyz/dymension/v3/x/incentives/types" ) @@ -41,7 +40,7 @@ func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateG params := server.keeper.GetParams(ctx) fee := params.CreateGaugeBaseFee.Add(params.AddDenomFee.MulRaw(int64(len(msg.Coins)))) if err = server.keeper.ChargeGaugesFee(ctx, owner, fee, msg.Coins); err != nil { - return nil, err + return nil, fmt.Errorf("charge gauge fee: %w", err) } gaugeID, err := server.keeper.CreateGauge(ctx, msg.IsPerpetual, owner, msg.Coins, msg.DistributeTo, msg.StartTime, msg.NumEpochsPaidOver) @@ -78,7 +77,7 @@ func (server msgServer) AddToGauge(goCtx context.Context, msg *types.MsgAddToGau params := server.keeper.GetParams(ctx) fee := params.AddToGaugeBaseFee.Add(params.AddDenomFee.MulRaw(int64(len(msg.Rewards) + len(gauge.Coins)))) if err = server.keeper.ChargeGaugesFee(ctx, owner, fee, msg.Rewards); err != nil { - return nil, err + return nil, fmt.Errorf("charge gauge fee: %w", err) } err = server.keeper.AddToGaugeRewards(ctx, owner, msg.Rewards, gauge) @@ -100,7 +99,7 @@ func (server msgServer) AddToGauge(goCtx context.Context, msg *types.MsgAddToGau // balance that is less than fee + amount of the coin from gaugeCoins that is of base denom. // gaugeCoins might not have a coin of tx base denom. In that case, fee is only compared to balance. // The fee is sent to the txfees module, to be burned. -func (k Keeper) ChargeGaugesFee(ctx sdk.Context, address sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) (err error) { +func (k Keeper) ChargeGaugesFee(ctx sdk.Context, payer sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) (err error) { var feeDenom string if k.tk == nil { feeDenom, err = sdk.GetBaseDenom() @@ -112,11 +111,11 @@ func (k Keeper) ChargeGaugesFee(ctx sdk.Context, address sdk.AccAddress, fee sdk } totalCost := gaugeCoins.AmountOf(feeDenom).Add(fee) - accountBalance := k.bk.GetBalance(ctx, address, feeDenom).Amount + accountBalance := k.bk.GetBalance(ctx, payer, feeDenom).Amount if accountBalance.LT(totalCost) { return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "account's balance is less than the total cost of the message. Balance: %s %s, Total Cost: %s", feeDenom, accountBalance, totalCost) } - return k.bk.SendCoinsFromAccountToModule(ctx, address, txfeestypes.ModuleName, sdk.NewCoins(sdk.NewCoin(feeDenom, fee))) + return k.tk.ChargeFeesFromPayer(ctx, payer, sdk.NewCoin(feeDenom, fee), nil) } diff --git a/x/incentives/keeper/msg_server_test.go b/x/incentives/keeper/msg_server_test.go index 3d88aacc3..577d344ce 100644 --- a/x/incentives/keeper/msg_server_test.go +++ b/x/incentives/keeper/msg_server_test.go @@ -192,9 +192,9 @@ func (suite *KeeperTestSuite) TestCreateGauge() { finalAccountBalance := accountBalance.Sub(fee...) suite.Require().Equal(finalAccountBalance.String(), balanceAmount.String(), "test: %v", tc.name) - // test fee charged to txfees module account + // test fee charged to txfees module account and burned txfeesBalanceAfter := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(txfees.ModuleName), "stake") - suite.Require().Equal(txfeesBalanceBefore.Amount.Add(feeRaw), txfeesBalanceAfter.Amount, "test: %v", tc.name) + suite.Require().Equal(txfeesBalanceBefore.Amount, txfeesBalanceAfter.Amount, "test: %v", tc.name) } }) } @@ -369,9 +369,9 @@ func (suite *KeeperTestSuite) TestAddToGauge() { finalAccountBalance := accountBalance.Sub(fee...) suite.Require().Equal(finalAccountBalance.String(), bal.String(), "test: %v", tc.name) - // test fee charged to txfees module account + // test fee charged to txfees module account and burned txfeesBalanceAfter := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(txfees.ModuleName), "stake") - suite.Require().Equal(txfeesBalanceBefore.Amount.Add(feeRaw), txfeesBalanceAfter.Amount, "test: %v", tc.name) + suite.Require().Equal(txfeesBalanceBefore.Amount, txfeesBalanceAfter.Amount, "test: %v", tc.name) } }) } diff --git a/x/incentives/types/expected_keepers.go b/x/incentives/types/expected_keepers.go index d9881e46e..c9c0b5998 100644 --- a/x/incentives/types/expected_keepers.go +++ b/x/incentives/types/expected_keepers.go @@ -39,6 +39,7 @@ type EpochKeeper interface { // TxFeesKeeper defines the expected interface needed to managing transaction fees. type TxFeesKeeper interface { GetBaseDenom(ctx sdk.Context) (denom string, err error) + ChargeFeesFromPayer(ctx sdk.Context, payer sdk.AccAddress, takerFeeCoin sdk.Coin, beneficiary *sdk.AccAddress) error } type RollappKeeper interface { diff --git a/x/iro/keeper/trade.go b/x/iro/keeper/trade.go index eb3be38e6..4cb61f94f 100644 --- a/x/iro/keeper/trade.go +++ b/x/iro/keeper/trade.go @@ -281,7 +281,7 @@ func (k Keeper) GetTradeableIRO(ctx sdk.Context, planId string, trader sdk.AccAd // chargeTakerFee charges taker fee from the sender. // The fee is sent to the txfees module and the beneficiary if presented. func (k Keeper) chargeTakerFee(ctx sdk.Context, takerFeeCoin sdk.Coin, sender sdk.AccAddress, beneficiary *sdk.AccAddress) error { - err := k.tk.ChargeFees(ctx, sender, takerFeeCoin, beneficiary) + err := k.tk.ChargeFeesFromPayer(ctx, sender, takerFeeCoin, beneficiary) if err != nil { return fmt.Errorf("charge fees: sender: %s: fee: %s: %w", sender, takerFeeCoin, err) } diff --git a/x/iro/types/expected_keepers.go b/x/iro/types/expected_keepers.go index a6f143abd..c4e38e838 100644 --- a/x/iro/types/expected_keepers.go +++ b/x/iro/types/expected_keepers.go @@ -60,5 +60,5 @@ type RollappKeeper interface { } type TxFeesKeeper interface { - ChargeFees(ctx sdk.Context, sender sdk.AccAddress, takerFeeCoin sdk.Coin, beneficiary *sdk.AccAddress) error + ChargeFeesFromPayer(ctx sdk.Context, payer sdk.AccAddress, takerFeeCoin sdk.Coin, beneficiary *sdk.AccAddress) error } From 603b46e9110a3d1fc7f1112974c7c3dc3d9cd0fe Mon Sep 17 00:00:00 2001 From: keruch Date: Sun, 3 Nov 2024 14:12:27 +0100 Subject: [PATCH 5/6] updated with respect to new changes --- app/keepers/keepers.go | 1 + x/iro/keeper/keeper_test.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index c3a5fbcf8..b8fde53b7 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -331,6 +331,7 @@ func (a *AppKeepers) InitKeepers( a.BankKeeper, a.PoolManagerKeeper, a.GAMMKeeper, + a.DistrKeeper, ) a.TxFeesKeeper = &txFeesKeeper diff --git a/x/iro/keeper/keeper_test.go b/x/iro/keeper/keeper_test.go index 90f7d8b79..19a0fbc5e 100644 --- a/x/iro/keeper/keeper_test.go +++ b/x/iro/keeper/keeper_test.go @@ -44,6 +44,10 @@ func (suite *KeeperTestSuite) SetupTest() { rollapp := suite.App.RollappKeeper.MustGetRollapp(suite.Ctx, rollappId) funds := suite.App.IROKeeper.GetParams(suite.Ctx).CreationFee.Mul(math.NewInt(10)) // 10 times the creation fee suite.FundAcc(sdk.MustAccAddressFromBech32(rollapp.Owner), sdk.NewCoins(sdk.NewCoin(appparams.BaseDenom, funds))) + + // set txfees basedenom + err := suite.App.TxFeesKeeper.SetBaseDenom(suite.Ctx, "adym") + suite.Require().NoError(err) } // BuySomeTokens buys some tokens from the plan From b4b301a8f695df046386d28aa01523ff016d3177 Mon Sep 17 00:00:00 2001 From: keruch Date: Mon, 4 Nov 2024 16:28:29 +0100 Subject: [PATCH 6/6] removed osmo replace --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0284e70b1..b69a81796 100644 --- a/go.mod +++ b/go.mod @@ -241,7 +241,7 @@ replace ( github.com/evmos/ethermint => github.com/dymensionxyz/ethermint v0.22.0-dymension-v0.4.1.0.20241013112411-5ef491708a2d github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/osmosis-labs/osmosis/osmomath => github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 - github.com/osmosis-labs/osmosis/v15 => ../dym-osmo + github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241104151037-91342c9a4f57 // broken goleveldb github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 77975ba22..e7d34c04f 100644 --- a/go.sum +++ b/go.sum @@ -506,6 +506,8 @@ github.com/dymensionxyz/gerr-cosmos v1.1.0 h1:IW/P7HCB/iP9kgk3VXaWUoMoyx3vD76YO6 github.com/dymensionxyz/gerr-cosmos v1.1.0/go.mod h1:n+0olxPogzWqFKba45mCpvrHLGmeS8W9UZjggHnWk6c= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 h1:EskhZ6ILN3vwJ6l8gPWPZ49RFSB52WghT5v+pmzrNCI= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43/go.mod h1:SdGCL9CZb14twRAJUSzb7bRE0OoopRpF2Hnd1UhJpFU= +github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241104151037-91342c9a4f57 h1:OOf6LO3dyMp6eJTJM/of6HAVVNBR9daW9MuycPQzmfk= +github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241104151037-91342c9a4f57/go.mod h1:sXttKj99Ke160CvjID+5hvOG3TEF/K1k/Eqa37EhRCc= github.com/dymensionxyz/sdk-utils v0.2.12 h1:wrcof+IP0AJQ7vvMRVpSekNNwa6B7ghAspHRjp/k+Lk= github.com/dymensionxyz/sdk-utils v0.2.12/go.mod h1:it9owYOpnIe17+ftTATQNDN4z+mBQx20/2Jm8SK15Rk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=