-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: bridging fee middleware (#899)
- Loading branch information
Showing
23 changed files
with
469 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package ibctesting_test | ||
|
||
import ( | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" | ||
clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" | ||
ibctesting "github.com/cosmos/ibc-go/v6/testing" | ||
"github.com/osmosis-labs/osmosis/v15/x/txfees" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type BridgingFeeTestSuite struct { | ||
IBCTestUtilSuite | ||
} | ||
|
||
func TestBridgingFeeTestSuite(t *testing.T) { | ||
suite.Run(t, new(BridgingFeeTestSuite)) | ||
} | ||
|
||
func (suite *BridgingFeeTestSuite) SetupTest() { | ||
suite.IBCTestUtilSuite.SetupTest() | ||
} | ||
|
||
func (suite *BridgingFeeTestSuite) TestNotRollappNoBridgingFee() { | ||
// setup between cosmosChain and hubChain | ||
path := suite.NewTransferPath(suite.hubChain, suite.cosmosChain) | ||
suite.coordinator.Setup(path) | ||
hubEndpoint := path.EndpointA | ||
cosmosEndpoint := path.EndpointB | ||
|
||
timeoutHeight := clienttypes.NewHeight(100, 110) | ||
amount, ok := sdk.NewIntFromString("10000000000000000000") // 10DYM | ||
suite.Require().True(ok) | ||
coinToSendToB := sdk.NewCoin(sdk.DefaultBondDenom, amount) | ||
|
||
// send from cosmosChain to hubChain | ||
msg := types.NewMsgTransfer(cosmosEndpoint.ChannelConfig.PortID, cosmosEndpoint.ChannelID, coinToSendToB, cosmosEndpoint.Chain.SenderAccount.GetAddress().String(), hubEndpoint.Chain.SenderAccount.GetAddress().String(), timeoutHeight, 0, "") | ||
res, err := cosmosEndpoint.Chain.SendMsgs(msg) | ||
suite.Require().NoError(err) // message committed | ||
packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) | ||
suite.Require().NoError(err) | ||
err = path.RelayPacket(packet) | ||
suite.Require().NoError(err) // relay committed | ||
|
||
denom := suite.GetRollappToHubIBCDenomFromPacket(packet) | ||
finalBalance := ConvertToApp(suite.hubChain).BankKeeper.GetBalance(suite.hubChain.GetContext(), suite.hubChain.SenderAccount.GetAddress(), denom) | ||
suite.Assert().Equal(sdk.NewCoin(denom, coinToSendToB.Amount), finalBalance) | ||
} | ||
|
||
func (suite *BridgingFeeTestSuite) TestBridgingFee() { | ||
path := suite.NewTransferPath(suite.hubChain, suite.rollappChain) | ||
suite.coordinator.Setup(path) | ||
|
||
rollappEndpoint := path.EndpointB | ||
rollappIBCKeeper := suite.rollappChain.App.GetIBCKeeper() | ||
|
||
suite.CreateRollapp() | ||
suite.RegisterSequencer() | ||
suite.GenesisEvent(path.EndpointA.ChannelID) | ||
|
||
// Update rollapp state | ||
currentRollappBlockHeight := uint64(suite.rollappChain.GetContext().BlockHeight()) | ||
suite.UpdateRollappState(currentRollappBlockHeight) | ||
|
||
timeoutHeight := clienttypes.NewHeight(100, 110) | ||
amount, ok := sdk.NewIntFromString("10000000000000000000") // 10DYM | ||
suite.Require().True(ok) | ||
coinToSendToB := sdk.NewCoin(sdk.DefaultBondDenom, amount) | ||
|
||
/* --------------------- initiating transfer on rollapp --------------------- */ | ||
msg := types.NewMsgTransfer(rollappEndpoint.ChannelConfig.PortID, rollappEndpoint.ChannelID, coinToSendToB, suite.rollappChain.SenderAccount.GetAddress().String(), suite.hubChain.SenderAccount.GetAddress().String(), timeoutHeight, 0, "") | ||
res, err := suite.rollappChain.SendMsgs(msg) | ||
suite.Require().NoError(err) // message committed | ||
packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) | ||
suite.Require().NoError(err) | ||
found := rollappIBCKeeper.ChannelKeeper.HasPacketCommitment(rollappEndpoint.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) | ||
suite.Require().True(found) | ||
err = path.RelayPacket(packet) | ||
suite.Require().Error(err) // expecting error as no AcknowledgePacket expected to return | ||
|
||
// check balance before finalization | ||
denom := suite.GetRollappToHubIBCDenomFromPacket(packet) | ||
transferredCoins := sdk.NewCoin(denom, coinToSendToB.Amount) | ||
recipient := suite.hubChain.SenderAccount.GetAddress() | ||
initialBalance := ConvertToApp(suite.hubChain).BankKeeper.SpendableCoins(suite.hubChain.GetContext(), recipient) | ||
suite.Require().Equal(initialBalance.AmountOf(denom), sdk.ZeroInt()) | ||
|
||
// Finalize the rollapp state | ||
currentRollappBlockHeight = uint64(suite.rollappChain.GetContext().BlockHeight()) | ||
_, err = suite.FinalizeRollappState(1, currentRollappBlockHeight) | ||
suite.Require().NoError(err) | ||
|
||
// check balance after finalization | ||
expectedFee := ConvertToApp(suite.hubChain).DelayedAckKeeper.BridgingFeeFromAmt(suite.hubChain.GetContext(), transferredCoins.Amount) | ||
expectedBalance := initialBalance.Add(transferredCoins).Sub(sdk.NewCoin(denom, expectedFee)) | ||
finalBalance := ConvertToApp(suite.hubChain).BankKeeper.SpendableCoins(suite.hubChain.GetContext(), recipient) | ||
suite.Assert().Equal(expectedBalance, finalBalance) | ||
|
||
// check fees | ||
addr := ConvertToApp(suite.hubChain).AccountKeeper.GetModuleAccount(suite.hubChain.GetContext(), txfees.ModuleName) | ||
txFeesBalance := ConvertToApp(suite.hubChain).BankKeeper.GetBalance(suite.hubChain.GetContext(), addr.GetAddress(), denom) | ||
suite.Assert().Equal(expectedFee, txFeesBalance.Amount) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package bridging_fee | ||
|
||
const ( | ||
EventTypeBridgingFee = "bridging_fee" | ||
AttributeKeyFee = "fee" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package bridging_fee | ||
|
||
import ( | ||
errorsmod "cosmossdk.io/errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
transfer "github.com/cosmos/ibc-go/v6/modules/apps/transfer" | ||
transferkeeper "github.com/cosmos/ibc-go/v6/modules/apps/transfer/keeper" | ||
channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" | ||
porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" | ||
"github.com/cosmos/ibc-go/v6/modules/core/exported" | ||
|
||
delayedaackkeeper "github.com/dymensionxyz/dymension/v3/x/delayedack/keeper" | ||
) | ||
|
||
const ( | ||
ModuleName = "bridging_fee" | ||
) | ||
|
||
var _ porttypes.Middleware = &BridgingFeeMiddleware{} | ||
|
||
// BridgingFeeMiddleware implements the ICS26 callbacks | ||
// The middleware is responsible for charging a bridging fee on transfers coming from rollapps | ||
// The actual charge happens on the packet finalization | ||
// based on ADR: https://www.notion.so/dymension/ADR-x-Bridging-Fee-Middleware-7ba8c191373f43ce81782fc759913299?pvs=4 | ||
type BridgingFeeMiddleware struct { | ||
transfer.IBCModule | ||
porttypes.ICS4Wrapper | ||
|
||
delayedAckKeeper delayedaackkeeper.Keeper | ||
transferKeeper transferkeeper.Keeper | ||
feeModuleAddr sdk.AccAddress | ||
} | ||
|
||
// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application | ||
func NewIBCMiddleware(transfer transfer.IBCModule, channelKeeper porttypes.ICS4Wrapper, keeper delayedaackkeeper.Keeper, transferKeeper transferkeeper.Keeper, feeModuleAddr sdk.AccAddress) *BridgingFeeMiddleware { | ||
return &BridgingFeeMiddleware{ | ||
IBCModule: transfer, | ||
ICS4Wrapper: channelKeeper, | ||
delayedAckKeeper: keeper, | ||
transferKeeper: transferKeeper, | ||
feeModuleAddr: feeModuleAddr, | ||
} | ||
} | ||
|
||
// GetFeeRecipient returns the address that will receive the bridging fee | ||
func (im BridgingFeeMiddleware) GetFeeRecipient() sdk.AccAddress { | ||
return im.feeModuleAddr | ||
} | ||
|
||
func (im *BridgingFeeMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { | ||
if !im.delayedAckKeeper.IsRollappsEnabled(ctx) { | ||
return im.IBCModule.OnRecvPacket(ctx, packet, relayer) | ||
} | ||
logger := ctx.Logger().With( | ||
"module", ModuleName, | ||
"packet_source", packet.SourcePort, | ||
"packet_destination", packet.DestinationPort, | ||
"packet_sequence", packet.Sequence) | ||
|
||
rollappPortOnHub, rollappChannelOnHub := packet.DestinationPort, packet.DestinationChannel | ||
rollappID, transferPacketData, err := im.delayedAckKeeper.ExtractRollappIDAndTransferPacket(ctx, packet, rollappPortOnHub, rollappChannelOnHub) | ||
if err != nil { | ||
logger.Error("Failed to extract rollapp id from packet", "err", err) | ||
return channeltypes.NewErrorAcknowledgement(err) | ||
} | ||
|
||
if rollappID == "" { | ||
logger.Debug("Skipping IBC transfer OnRecvPacket for non-rollapp chain") | ||
return im.IBCModule.OnRecvPacket(ctx, packet, relayer) | ||
} | ||
|
||
// parse the transfer amount | ||
transferAmount, ok := sdk.NewIntFromString(transferPacketData.Amount) | ||
if !ok { | ||
err = errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "parse transfer amount into math.Int") | ||
return channeltypes.NewErrorAcknowledgement(err) | ||
} | ||
|
||
// get fee | ||
fee := im.delayedAckKeeper.BridgingFeeFromAmt(ctx, transferAmount) | ||
|
||
// update packet data for the fee charge | ||
feePacket := *transferPacketData | ||
feePacket.Amount = fee.String() | ||
feePacket.Receiver = im.GetFeeRecipient().String() | ||
|
||
// No event emitted, as we called the transfer keeper directly (vs the transfer middleware) | ||
err = im.transferKeeper.OnRecvPacket(ctx, packet, feePacket) | ||
if err != nil { | ||
logger.Error("Failed to charge bridging fee", "err", err) | ||
// we continue as we don't want the fee charge to fail the transfer in any case | ||
fee = sdk.ZeroInt() | ||
} else { | ||
logger.Debug("Charged bridging fee", "fee", fee) | ||
ctx.EventManager().EmitEvent( | ||
sdk.NewEvent( | ||
EventTypeBridgingFee, | ||
sdk.NewAttribute(AttributeKeyFee, fee.String()), | ||
sdk.NewAttribute(sdk.AttributeKeySender, transferPacketData.Sender), | ||
), | ||
) | ||
} | ||
|
||
// transfer the remaining amount | ||
transferPacketData.Amount = transferAmount.Sub(fee).String() | ||
packet.Data = transferPacketData.GetBytes() | ||
return im.IBCModule.OnRecvPacket(ctx, packet, relayer) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.