Skip to content

Commit 312c332

Browse files
committed
add fee sponsorship for BTCT transfer
1 parent 26718fc commit 312c332

File tree

14 files changed

+1509
-228
lines changed

14 files changed

+1509
-228
lines changed

api/side/btcbridge/params.pulsar.go

Lines changed: 757 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ante.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package app
2+
3+
import (
4+
errorsmod "cosmossdk.io/errors"
5+
storetypes "cosmossdk.io/store/types"
6+
feegrantkeeper "cosmossdk.io/x/feegrant/keeper"
7+
txsigning "cosmossdk.io/x/tx/signing"
8+
sdk "github.com/cosmos/cosmos-sdk/types"
9+
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
10+
"github.com/cosmos/cosmos-sdk/types/tx/signing"
11+
"github.com/cosmos/cosmos-sdk/x/auth/ante"
12+
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
13+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
14+
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
15+
16+
btcbridgeante "github.com/sideprotocol/side/x/btcbridge/ante"
17+
btcbridgekeeper "github.com/sideprotocol/side/x/btcbridge/keeper"
18+
)
19+
20+
// HandlerOptions defines the options required for constructing an AnteHandler.
21+
type HandlerOptions struct {
22+
AccountKeeper *authkeeper.AccountKeeper
23+
BankKeeper bankkeeper.Keeper
24+
FeegrantKeeper *feegrantkeeper.Keeper
25+
BtcBridgeKeeper *btcbridgekeeper.Keeper
26+
ExtensionOptionChecker ante.ExtensionOptionChecker
27+
SignModeHandler *txsigning.HandlerMap
28+
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
29+
TxFeeChecker ante.TxFeeChecker
30+
}
31+
32+
// NewAnteHandler returns an AnteHandler similar to the default SDK AnteHandler except that fee sponsorship is supported
33+
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
34+
if options.AccountKeeper == nil {
35+
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder")
36+
}
37+
38+
if options.BankKeeper == nil {
39+
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder")
40+
}
41+
42+
if options.BtcBridgeKeeper == nil {
43+
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "btcbridge keeper is required for ante builder")
44+
}
45+
46+
if options.SignModeHandler == nil {
47+
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
48+
}
49+
50+
anteDecorators := []sdk.AnteDecorator{
51+
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
52+
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
53+
ante.NewValidateBasicDecorator(),
54+
ante.NewTxTimeoutHeightDecorator(),
55+
ante.NewValidateMemoDecorator(options.AccountKeeper),
56+
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
57+
btcbridgeante.NewFeeSponsorshipDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.BtcBridgeKeeper, options.TxFeeChecker),
58+
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
59+
ante.NewValidateSigCountDecorator(options.AccountKeeper),
60+
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
61+
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
62+
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
63+
}
64+
65+
return sdk.ChainAnteDecorators(anteDecorators...), nil
66+
}

app/app.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import (
88
"path/filepath"
99
"sort"
1010

11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/spf13/cast"
13+
14+
"github.com/CosmWasm/wasmd/x/wasm"
15+
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
16+
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
17+
18+
abci "github.com/cometbft/cometbft/abci/types"
19+
1120
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
1221
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
1322
"cosmossdk.io/client/v2/autocli"
@@ -24,7 +33,6 @@ import (
2433
"cosmossdk.io/x/upgrade"
2534
upgradekeeper "cosmossdk.io/x/upgrade/keeper"
2635
upgradetypes "cosmossdk.io/x/upgrade/types"
27-
abci "github.com/cometbft/cometbft/abci/types"
2836
dbm "github.com/cosmos/cosmos-db"
2937
"github.com/cosmos/cosmos-sdk/baseapp"
3038
"github.com/cosmos/cosmos-sdk/client"
@@ -45,7 +53,6 @@ import (
4553
"github.com/cosmos/cosmos-sdk/types/module"
4654
"github.com/cosmos/cosmos-sdk/version"
4755
"github.com/cosmos/cosmos-sdk/x/auth"
48-
"github.com/cosmos/cosmos-sdk/x/auth/ante"
4956
authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec"
5057
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
5158
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
@@ -119,12 +126,6 @@ import (
119126
ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper"
120127
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
121128
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
122-
"github.com/prometheus/client_golang/prometheus"
123-
"github.com/spf13/cast"
124-
125-
"github.com/CosmWasm/wasmd/x/wasm"
126-
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
127-
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
128129

129130
"github.com/sideprotocol/side/bitcoin"
130131
"github.com/sideprotocol/side/docs"
@@ -237,6 +238,7 @@ var (
237238
wasmtypes.ModuleName: {authtypes.Burner},
238239
tsstypes.ModuleName: nil,
239240
btcbridgetypes.ModuleName: {authtypes.Minter, authtypes.Burner},
241+
btcbridgetypes.FeeSponsorName: nil,
240242
incentivetypes.ModuleName: nil,
241243
liquidationtypes.ModuleName: nil,
242244
dlctypes.ModuleName: nil,
@@ -1048,12 +1050,13 @@ func New(
10481050
app.MountMemoryStores(memKeys)
10491051

10501052
// create ante handler
1051-
anteHandler, err := ante.NewAnteHandler(
1052-
ante.HandlerOptions{
1053-
AccountKeeper: app.AccountKeeper,
1053+
anteHandler, err := NewAnteHandler(
1054+
HandlerOptions{
1055+
AccountKeeper: &app.AccountKeeper,
10541056
BankKeeper: app.BankKeeper,
1057+
FeegrantKeeper: &app.FeeGrantKeeper,
1058+
BtcBridgeKeeper: &app.BtcBridgeKeeper,
10551059
SignModeHandler: txConfig.SignModeHandler(),
1056-
FeegrantKeeper: app.FeeGrantKeeper,
10571060
SigGasConsumer: bitcoin.DefaultSigVerificationGasConsumer,
10581061
},
10591062
)
@@ -1326,6 +1329,7 @@ func BlockedAddresses() map[string]bool {
13261329

13271330
// allow the following addresses to receive funds
13281331
delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String())
1332+
delete(modAccAddrs, authtypes.NewModuleAddress(btcbridgetypes.FeeSponsorName).String())
13291333
delete(modAccAddrs, authtypes.NewModuleAddress(incentivetypes.ModuleName).String())
13301334
delete(modAccAddrs, authtypes.NewModuleAddress(farmingtypes.ModuleName).String())
13311335

proto/side/btcbridge/params.proto

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ message Params {
7777
IBCParams ibc_params = 15 [(gogoproto.nullable) = false];
7878
// Rate limit params
7979
RateLimitParams rate_limit_params = 16 [(gogoproto.nullable) = false];
80+
// Fee sponsorship params
81+
FeeSponsorshipParams fee_sponsorship_params = 17 [(gogoproto.nullable) = false];
8082
}
8183

8284
// AssetType defines the type of asset
@@ -171,3 +173,12 @@ message IBCParams {
171173
// Timeout duration relative to the current time
172174
google.protobuf.Duration timeout_duration = 2 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
173175
}
176+
177+
// FeeSponsorshipParams defines the params related to fee sponsorship
178+
message FeeSponsorshipParams {
179+
// Maximum sponsorship fee per tx
180+
repeated cosmos.base.v1beta1.Coin max_sponsor_fee = 1 [
181+
(gogoproto.nullable) = false,
182+
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
183+
];
184+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ante
2+
3+
import (
4+
"context"
5+
6+
sdk "github.com/cosmos/cosmos-sdk/types"
7+
)
8+
9+
// BtcBridgeKeeper defines the expected btcbridge keeper interface
10+
type BtcBridgeKeeper interface {
11+
BtcDenom(ctx sdk.Context) string
12+
13+
FeeSponsorshipEnabled(ctx sdk.Context) bool
14+
MaxSponsorFee(ctx sdk.Context) sdk.Coins
15+
}
16+
17+
// FeeGrantKeeper defines the expected feegrant keeper.
18+
type FeeGrantKeeper interface {
19+
UseGrantedFees(ctx context.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error
20+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package ante
2+
3+
import (
4+
errorsmod "cosmossdk.io/errors"
5+
sdk "github.com/cosmos/cosmos-sdk/types"
6+
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
7+
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
8+
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
9+
10+
"github.com/sideprotocol/side/x/btcbridge/types"
11+
)
12+
13+
// FeeSponsorshipDecorator implements fee sponsorship
14+
// The gas fee is sponsored if the tx satisfies the sponsorship requirement
15+
// Fallback to the default DeductFeeDecorator otherwise
16+
type FeeSponsorshipDecorator struct {
17+
accountKeeper types.AccountKeeper
18+
bankKeeper types.BankKeeper
19+
feegrantKeeper FeeGrantKeeper
20+
btcbridgeKeeper BtcBridgeKeeper
21+
22+
txFeeChecker authante.TxFeeChecker
23+
24+
defaultFeeDecorator authante.DeductFeeDecorator
25+
}
26+
27+
// NewFeeSponsorshipDecorator creates a new decorator for fee sponsorship
28+
func NewFeeSponsorshipDecorator(authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, feegrantKeeper FeeGrantKeeper, btcbridgeKeeper BtcBridgeKeeper, txFeeChecker authante.TxFeeChecker) FeeSponsorshipDecorator {
29+
if txFeeChecker == nil {
30+
txFeeChecker = checkTxFeeWithValidatorMinGasPrices
31+
}
32+
33+
return FeeSponsorshipDecorator{
34+
accountKeeper: authKeeper,
35+
bankKeeper: bankKeeper,
36+
feegrantKeeper: feegrantKeeper,
37+
btcbridgeKeeper: btcbridgeKeeper,
38+
txFeeChecker: txFeeChecker,
39+
defaultFeeDecorator: authante.NewDeductFeeDecorator(authKeeper, bankKeeper, feegrantKeeper, txFeeChecker),
40+
}
41+
}
42+
43+
// AnteHandle implements sdk.AnteDecorator
44+
func (fsd FeeSponsorshipDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
45+
feeTx, ok := tx.(sdk.FeeTx)
46+
if !ok {
47+
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
48+
}
49+
50+
if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
51+
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
52+
}
53+
54+
var (
55+
priority int64
56+
err error
57+
)
58+
59+
fee := feeTx.GetFee()
60+
if !simulate {
61+
fee, priority, err = fsd.txFeeChecker(ctx, tx)
62+
if err != nil {
63+
return ctx, err
64+
}
65+
}
66+
67+
if !fsd.checkSponsorship(ctx, tx, fee) {
68+
// fallback to default fee decorator
69+
return fsd.defaultFeeDecorator.AnteHandle(ctx, tx, simulate, next)
70+
}
71+
72+
// sponsor fee
73+
if err := fsd.sponsorFee(ctx, fee); err != nil {
74+
return ctx, err
75+
}
76+
77+
newCtx := ctx.WithPriority(priority)
78+
79+
return next(newCtx, tx, simulate)
80+
}
81+
82+
// sponsorFee performs the fee sponsorship for the given tx
83+
func (fsd FeeSponsorshipDecorator) sponsorFee(ctx sdk.Context, fee sdk.Coins) error {
84+
sponsorAddress := fsd.getFeeSponsorAddress()
85+
86+
sponsorAccount := fsd.accountKeeper.GetAccount(ctx, sponsorAddress)
87+
if sponsorAccount == nil {
88+
return sdkerrors.ErrUnknownAddress.Wrapf("fee sponsor address: %s does not exist", sponsorAddress)
89+
}
90+
91+
// deduct fees from the sponsor account
92+
if !fee.IsZero() {
93+
err := authante.DeductFees(fsd.bankKeeper, ctx, sponsorAccount, fee)
94+
if err != nil {
95+
return err
96+
}
97+
}
98+
99+
events := sdk.Events{
100+
sdk.NewEvent(
101+
sdk.EventTypeTx,
102+
sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()),
103+
sdk.NewAttribute(sdk.AttributeKeyFeePayer, sponsorAddress.String()),
104+
),
105+
}
106+
ctx.EventManager().EmitEvents(events)
107+
108+
return nil
109+
}
110+
111+
// checkSponsorship returns true if the given tx should be sponsored, false otherwise
112+
// NOTE: The following requirements must be satisfied for fee sponsorship
113+
// 1. Fee sponsorship is enabled
114+
// 2. The tx is a BTCT transfer tx
115+
// 3. The gas fee does not exceed the maximum sponsorship fee
116+
// 4. The fee sponsor account has sufficient balances
117+
func (fsd FeeSponsorshipDecorator) checkSponsorship(ctx sdk.Context, tx sdk.Tx, fee sdk.Coins) bool {
118+
if !fsd.btcbridgeKeeper.FeeSponsorshipEnabled(ctx) {
119+
return false
120+
}
121+
122+
if !fsd.isSponsorableTx(ctx, tx) {
123+
return false
124+
}
125+
126+
if !fee.IsAllLTE(fsd.btcbridgeKeeper.MaxSponsorFee(ctx)) {
127+
return false
128+
}
129+
130+
if !fsd.bankKeeper.SpendableCoins(ctx, fsd.getFeeSponsorAddress()).IsAllGTE(fee) {
131+
return false
132+
}
133+
134+
return true
135+
}
136+
137+
// isSponsorableTx returns true if the given tx is sponsorable, false otherwise
138+
func (fsd FeeSponsorshipDecorator) isSponsorableTx(ctx sdk.Context, tx sdk.Tx) bool {
139+
for _, m := range tx.GetMsgs() {
140+
switch msg := m.(type) {
141+
case *banktypes.MsgSend:
142+
denoms := msg.Amount.Denoms()
143+
if len(denoms) != 1 || denoms[0] != fsd.btcbridgeKeeper.BtcDenom(ctx) {
144+
return false
145+
}
146+
147+
case *banktypes.MsgMultiSend:
148+
denoms := msg.Inputs[0].Coins.Denoms()
149+
if len(denoms) != 1 || denoms[0] != fsd.btcbridgeKeeper.BtcDenom(ctx) {
150+
return false
151+
}
152+
153+
default:
154+
return false
155+
}
156+
}
157+
158+
return true
159+
}
160+
161+
// getFeeSponsorAddress gets the fee sponsor address
162+
func (fsd FeeSponsorshipDecorator) getFeeSponsorAddress() sdk.AccAddress {
163+
return fsd.accountKeeper.GetModuleAddress(types.FeeSponsorName)
164+
}

0 commit comments

Comments
 (0)