Skip to content

Commit

Permalink
feat: rollapp royalties (#1368)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Tsitrin <michael@dymension.xyz>
  • Loading branch information
keruch and mtsitrin authored Nov 5, 2024
1 parent 76aa1e1 commit a6f9c55
Show file tree
Hide file tree
Showing 27 changed files with 353 additions and 57 deletions.
11 changes: 9 additions & 2 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ func (a *AppKeepers) InitKeepers(
appCodec, a.keys[gammtypes.StoreKey],
a.GetSubspace(gammtypes.ModuleName),
a.AccountKeeper,
a.BankKeeper, a.DistrKeeper,
a.BankKeeper,
a.DistrKeeper,
)
a.GAMMKeeper = &gammKeeper

Expand All @@ -330,6 +331,7 @@ func (a *AppKeepers) InitKeepers(
a.BankKeeper,
a.PoolManagerKeeper,
a.GAMMKeeper,
a.DistrKeeper,
)
a.TxFeesKeeper = &txFeesKeeper

Expand Down Expand Up @@ -359,10 +361,13 @@ func (a *AppKeepers) InitKeepers(
a.IBCKeeper.ClientKeeper,
nil,
a.BankKeeper,
a.TransferKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
nil,
)

a.GAMMKeeper.SetRollapp(a.RollappKeeper)

a.SequencerKeeper = sequencermodulekeeper.NewKeeper(
appCodec,
a.keys[sequencermoduletypes.StoreKey],
Expand Down Expand Up @@ -419,6 +424,7 @@ func (a *AppKeepers) InitKeepers(
a.GAMMKeeper,
a.IncentivesKeeper,
a.PoolManagerKeeper,
a.TxFeesKeeper,
)

a.SponsorshipKeeper = sponsorshipkeeper.NewKeeper(
Expand Down Expand Up @@ -530,10 +536,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,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,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 => 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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +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/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=
Expand Down
4 changes: 2 additions & 2 deletions ibctesting/bridging_fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
1 change: 1 addition & 0 deletions testutil/keeper/dymns.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
4 changes: 3 additions & 1 deletion testutil/keeper/iro.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion testutil/keeper/rollapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
37 changes: 37 additions & 0 deletions utils/denom/ibc.go
Original file line number Diff line number Diff line change
@@ -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
}
40 changes: 40 additions & 0 deletions utils/denom/ibc_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
26 changes: 21 additions & 5 deletions x/bridgingfee/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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"
delayedackkeeper "github.com/dymensionxyz/dymension/v3/x/delayedack/keeper"
Expand All @@ -29,22 +31,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,
}
}

Expand Down Expand Up @@ -90,9 +95,20 @@ 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.
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.
w.logger(ctx, packet, "OnRecvPacket").Error("Charge bridging fee from x/txfees account.", "err", err)
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeBridgingFee,
Expand Down
1 change: 1 addition & 0 deletions x/dymns/keeper/keeper_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (s *KeeperTestSuite) SetupTest() {
rollappParamsSubspace,
nil, nil, nil, nil,
bk,
nil,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
nil,
)
Expand Down
6 changes: 3 additions & 3 deletions x/gamm/amm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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,
},
{
Expand Down
11 changes: 5 additions & 6 deletions x/incentives/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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)
}
8 changes: 4 additions & 4 deletions x/incentives/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
Expand Down Expand Up @@ -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)
}
})
}
Expand Down
1 change: 1 addition & 0 deletions x/incentives/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions x/iro/keeper/create_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,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 := fmt.Sprintf("%s_%s", types.IROTokenPrefix, rollappId)
displayDenom := fmt.Sprintf("%s_%s", types.IROTokenPrefix, rollappTokenSymbol)
baseDenom := types.IRODenom(rollappId)
displayDenom := types.IRODenom(rollappTokenSymbol)
metadata := banktypes.Metadata{
Description: fmt.Sprintf("Future token for rollapp %s", rollappId),
DenomUnits: []*banktypes.DenomUnit{
Expand Down
2 changes: 1 addition & 1 deletion x/iro/keeper/create_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading

0 comments on commit a6f9c55

Please sign in to comment.