Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: go packages #1111

Merged
merged 2 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions go/utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# utils

This package is meant to hold utilities used by
[Optimistic Ethereum](https://github.com/ethereum-optimism/optimism) written in
Golang.

## Packages

### Fees

Package fees includes helpers for dealing with fees on Optimistic Ethereum

#### `EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int`

Encodes `tx.gasLimit` based on the variables that are used to determine it.

`data` - Calldata of the transaction being sent. This data should *not* include the full signed RLP transaction.

`l1GasPrice` - gas price on L1 in wei

`l2GasLimit` - amount of gas provided for execution in L2. Notably, accounts are charged for execution based on this gasLimit, even if the gasUsed ends up being less.

`l2GasPrice` - gas price on L2 in wei

#### `DecodeL2GasLimit(gasLimit *big.Int) *big.Int`

Accepts the return value of `eth_estimateGas` and decodes the L2 gas limit that
is encoded in the return value. This is the gas limit that is passed to the user
contract within the OVM.
113 changes: 113 additions & 0 deletions go/utils/fees/rollup_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package fees

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)

// overhead represents the fixed cost of batch submission of a single
// transaction in gas.
const overhead uint64 = 4200 + 200*params.TxDataNonZeroGasEIP2028

// feeScalar is used to scale the calculations in EncodeL2GasLimit
// to prevent them from being too large
const feeScalar uint64 = 10_000_000

// TxGasPrice is a constant that determines the result of `eth_gasPrice`
// It is scaled upwards by 50%
// tx.gasPrice is hard coded to 1500 * wei and all transactions must set that
// gas price.
const TxGasPrice uint64 = feeScalar + (feeScalar / 2)

// BigTxGasPrice is the L2GasPrice as type big.Int
var BigTxGasPrice = new(big.Int).SetUint64(TxGasPrice)
var bigFeeScalar = new(big.Int).SetUint64(feeScalar)

const tenThousand = 10000

var BigTenThousand = new(big.Int).SetUint64(tenThousand)

// EncodeTxGasLimit computes the `tx.gasLimit` based on the L1/L2 gas prices and
// the L2 gas limit. The L2 gas limit is encoded inside of the lower order bits
// of the number like so: [ | l2GasLimit ]
// [ tx.gaslimit ]
// The lower order bits must be large enough to fit the L2 gas limit, so 10**8
// is chosen. If higher order bits collide with any bits from the L2 gas limit,
// the L2 gas limit will not be able to be decoded.
// An explicit design goal of this scheme was to make the L2 gas limit be human
// readable. The entire number is interpreted as the gas limit when computing
// the fee, so increasing the L2 Gas limit will increase the fee paid.
// The calculation is:
// l1GasLimit = zero_count(data) * 4 + non_zero_count(data) * 16 + overhead
// roundedL2GasLimit = ceilmod(l2GasLimit, 10_000)
// l1Fee = l1GasPrice * l1GasLimit
// l2Fee = l2GasPrice * roundedL2GasLimit
// sum = l1Fee + l2Fee
// scaled = sum / scalar
// rounded = ceilmod(scaled, tenThousand)
// roundedScaledL2GasLimit = roundedL2GasLimit / tenThousand
// result = rounded + roundedScaledL2GasLimit
// Note that for simplicity purposes, only the calldata is passed into this
// function when in reality the RLP encoded transaction should be. The
// additional cost is added to the overhead constant to prevent the need to RLP
// encode transactions during calls to `eth_estimateGas`
func EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int {
l1GasLimit := calculateL1GasLimit(data, overhead)
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
l1Fee := new(big.Int).Mul(l1GasPrice, l1GasLimit)
l2Fee := new(big.Int).Mul(l2GasPrice, roundedL2GasLimit)
sum := new(big.Int).Add(l1Fee, l2Fee)
scaled := new(big.Int).Div(sum, bigFeeScalar)
rounded := Ceilmod(scaled, BigTenThousand)
roundedScaledL2GasLimit := new(big.Int).Div(roundedL2GasLimit, BigTenThousand)
result := new(big.Int).Add(rounded, roundedScaledL2GasLimit)
return result
}

func Ceilmod(a, b *big.Int) *big.Int {
remainder := new(big.Int).Mod(a, b)
if remainder.Cmp(common.Big0) == 0 {
return a
}
sum := new(big.Int).Add(a, b)
rounded := new(big.Int).Sub(sum, remainder)
return rounded
}

// DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit
func DecodeL2GasLimit(gasLimit *big.Int) *big.Int {
scaled := new(big.Int).Mod(gasLimit, BigTenThousand)
return new(big.Int).Mul(scaled, BigTenThousand)
}

func DecodeL2GasLimitU64(gasLimit uint64) uint64 {
scaled := gasLimit % tenThousand
return scaled * tenThousand
}

// calculateL1GasLimit computes the L1 gasLimit based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
// under standard network conditions.
func calculateL1GasLimit(data []byte, overhead uint64) *big.Int {
zeroes, ones := zeroesAndOnes(data)
zeroesCost := zeroes * params.TxDataZeroGas
onesCost := ones * params.TxDataNonZeroGasEIP2028
gasLimit := zeroesCost + onesCost + overhead
return new(big.Int).SetUint64(gasLimit)
}

func zeroesAndOnes(data []byte) (uint64, uint64) {
var zeroes uint64
var ones uint64
for _, byt := range data {
if byt == 0 {
zeroes++
} else {
ones++
}
}
return zeroes, ones
}
104 changes: 104 additions & 0 deletions go/utils/fees/rollup_fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package fees

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/params"
)

var l1GasLimitTests = map[string]struct {
data []byte
overhead uint64
expect *big.Int
}{
"simple": {[]byte{}, 0, big.NewInt(0)},
"simple-overhead": {[]byte{}, 10, big.NewInt(10)},
"zeros": {[]byte{0x00, 0x00, 0x00, 0x00}, 10, big.NewInt(26)},
"ones": {[]byte{0x01, 0x02, 0x03, 0x04}, 200, big.NewInt(16*4 + 200)},
}

func TestL1GasLimit(t *testing.T) {
for name, tt := range l1GasLimitTests {
t.Run(name, func(t *testing.T) {
got := calculateL1GasLimit(tt.data, tt.overhead)
if got.Cmp(tt.expect) != 0 {
t.Fatal("Calculated gas limit does not match")
}
})
}
}

var feeTests = map[string]struct {
dataLen int
l1GasPrice uint64
l2GasLimit uint64
l2GasPrice uint64
}{
"simple": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 437118,
l2GasPrice: params.GWei,
},
"zero-l2-gasprice": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 196205,
l2GasPrice: 0,
},
"one-l2-gasprice": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 196205,
l2GasPrice: 1,
},
"zero-l1-gasprice": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 196205,
l2GasPrice: params.GWei,
},
"one-l1-gasprice": {
dataLen: 10,
l1GasPrice: 1,
l2GasLimit: 23255,
l2GasPrice: params.GWei,
},
"zero-gasprices": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 23255,
l2GasPrice: 0,
},
"max-gaslimit": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 99_970_000,
l2GasPrice: params.GWei,
},
"larger-divisor": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 10,
l2GasPrice: 0,
},
}

func TestCalculateRollupFee(t *testing.T) {
for name, tt := range feeTests {
t.Run(name, func(t *testing.T) {
data := make([]byte, tt.dataLen)
l1GasPrice := new(big.Int).SetUint64(tt.l1GasPrice)
l2GasLimit := new(big.Int).SetUint64(tt.l2GasLimit)
l2GasPrice := new(big.Int).SetUint64(tt.l2GasPrice)

fee := EncodeTxGasLimit(data, l1GasPrice, l2GasLimit, l2GasPrice)
decodedGasLimit := DecodeL2GasLimit(fee)
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
if roundedL2GasLimit.Cmp(decodedGasLimit) != 0 {
t.Errorf("rollup fee check failed: expected %d, got %d", l2GasLimit.Uint64(), decodedGasLimit)
}
})
}
}
5 changes: 5 additions & 0 deletions go/utils/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/ethereum-optimism/optimism/go/utils

go 1.15

require github.com/ethereum/go-ethereum v1.9.10
Loading