Last active
September 21, 2024 06:28
-
-
Save Jesserc/91d3f687976207db4cbbe23aec26900b to your computer and use it in GitHub Desktop.
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
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