Skip to content

Instantly share code, notes, and snippets.

@Jesserc
Last active September 21, 2024 06:28
Show Gist options
  • Save Jesserc/91d3f687976207db4cbbe23aec26900b to your computer and use it in GitHub Desktop.
Save Jesserc/91d3f687976207db4cbbe23aec26900b to your computer and use it in GitHub Desktop.
package keeper
import (
"math/big"
sdkerrors "cosmossdk.io/errors"
"cosmossdk.io/math"
"github.com/cometbft/cometbft/libs/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/polymerdao/monomer/x/rollup/types"
)
const (
L1FeeCollectorAccount = "l1_fee_collector"
)
// L1FeeDecorator calculates and deducts L1 fees for all transactions
type L1FeeDecorator struct {
keeper *Keeper
}
// Ensure L1FeeDecorator implements the sdk.AnteDecorator interface
var _ sdk.AnteDecorator = &L1FeeDecorator{}
func NewL1FeeDecorator(keeper *Keeper) L1FeeDecorator {
return L1FeeDecorator{
keeper: keeper,
}
}
// AnteHandle handles L1 fee deduction and validation
func (lfd *L1FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(errors.ErrTxDecode, "Tx must be a FeeTx")
}
txBytes, err := json.Marshal(tx) // TODO: we're using txBytes only for `calculateRollupDataGas`, so is this okay?
if err != nil {
return ctx, sdkerrors.Wrap(errors.ErrTxDecode, "failed to serialize transaction")
}
// Calculate L1 fee
l1Fee, err := lfd.CalculateL1Fee(ctx, txBytes)
if err != nil {
return ctx, sdkerrors.Wrapf(errors.ErrInsufficientFee, "failed to calculate L1 fee: %s", err)
}
// fees
providedFee := feeTx.GetFee()
requiredFee := sdk.NewCoins(l1Fee).Add(providedFee...)
if providedFee.IsAllLT(requiredFee) {
return ctx, sdkerrors.Wrapf(errors.ErrInsufficientFee, "insufficient fee; got: %s required: %s", providedFee, requiredFee)
}
// Deduct L1 fee and send it to the L1FeeCollectorAccount
err = lfd.CollectL1Fee(ctx, feeTx.FeePayer(), l1Fee)
if err != nil {
return ctx, sdkerrors.Wrapf(errors.ErrInsufficientFunds, "failed to collect L1 fee: %s", err)
}
return next(ctx, tx, simulate)
}
// CalculateL1Fee calculates the L1 fee for a given transaction
func (lfd *L1FeeDecorator) CalculateL1Fee(ctx sdk.Context, txBytes []byte) (sdk.Coin, error) {
// Formula:
// rollupDataGas = zeroes * 4 + ones * 16
// (rollupDataGas + l1FeeOverhead) * l1BaseFee * l1FeeScalar / 1e6
store := lfd.keeper.storeService.OpenKVStore(ctx)
l1BlockInfoBz, err := store.Get([]byte(types.KeyL1BlockInfo))
if err != nil {
return sdk.Coin{}, err
}
var l1BlockInfo derive.L1BlockInfo
if err := json.Unmarshal(l1BlockInfoBz, &l1BlockInfo); err != nil {
return sdk.Coin{}, err
}
rollupDataGas := calculateRollupDataGas(txBytes)
var (
L1FeeOverhead = big.NewInt(0)
L1FeeScalar = big.NewInt(0)
)
L1FeeOverhead.SetBytes(l1BlockInfo.L1FeeOverhead[:])
totalGas := rollupDataGas.Add(math.NewIntFromBigInt(L1FeeOverhead))
L1FeeScalar.SetBytes(l1BlockInfo.L1FeeScalar[:])
l1Cost := totalGas.
Mul(math.NewIntFromBigInt(l1BlockInfo.BaseFee)).
Mul(math.NewInt(L1FeeScalar.Int64())).
Quo(math.NewInt(1_000_000))
return sdk.NewCoin("ETH", l1Cost), nil
}
// CollectL1Fee collects the L1 fee and sends it to the L1FeeCollectorAccount
func (lfd *L1FeeDecorator) CollectL1Fee(ctx sdk.Context, feePayer sdk.AccAddress, fee sdk.Coin) error {
return lfd.keeper.bankkeeper.SendCoinsFromAccountToModule(ctx, feePayer, L1FeeCollectorAccount, sdk.NewCoins(fee))
}
func calculateRollupDataGas(txBytes []byte) math.Int {
zeros, ones := countZerosAndOnes(txBytes)
return math.NewInt(int64(zeros*4 + ones*16))
}
func countZerosAndOnes(data []byte) (zeros, ones int) {
for _, b := range data {
if b == 0 {
zeros++
} else {
ones++
}
}
return
}
// Test-related code and variables
var (
dec = NewL1FeeDecorator(&Keeper{})
decos = []sdk.AnteDecorator{&dec}
)
// THINGS TO CONSIDER
// 1. What if the TX fails, but we've already charged for L1 fees in the AnteHandler...// L1 Fee Non-Refundability?
/*
solution???
- call next AnteHandler first, before deducting l1 fees
- only deduct fee if the next passes
*/
// ctx.GasMeter().ConsumeGas(rollupDataGas.Uint64(), "L1 gas fee")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment