Skip to content

Commit

Permalink
core/vm, protocol_params: implement eip-2565 modexp repricing (ethere…
Browse files Browse the repository at this point in the history
  • Loading branch information
gzliudan committed Sep 26, 2024
1 parent 4f9501f commit 0ed603b
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 25 deletions.
99 changes: 81 additions & 18 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
Expand All @@ -75,7 +75,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
Expand All @@ -91,14 +91,27 @@ var PrecompiledContractsXDCv2 = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
}

var PrecompiledContractsEIP1559 = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
}

var (
PrecompiledAddressesEIP1559 []common.Address
PrecompiledAddressesXDCv2 []common.Address
PrecompiledAddressesIstanbul []common.Address
PrecompiledAddressesByzantium []common.Address
Expand All @@ -118,11 +131,16 @@ func init() {
for k := range PrecompiledContractsXDCv2 {
PrecompiledAddressesXDCv2 = append(PrecompiledAddressesXDCv2, k)
}
for k := range PrecompiledContractsEIP1559 {
PrecompiledAddressesEIP1559 = append(PrecompiledAddressesEIP1559, k)
}
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsEIP1559:
return PrecompiledAddressesEIP1559
case rules.IsXDCxDisable:
return PrecompiledAddressesXDCv2
case rules.IsIstanbul:
Expand Down Expand Up @@ -244,14 +262,19 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
}

// bigModExp implements a native big integer exponential modular operation.
type bigModExp struct{}
type bigModExp struct {
eip2565 bool
}

var (
big0 = big.NewInt(0)
big1 = big.NewInt(1)
big3 = big.NewInt(3)
big4 = big.NewInt(4)
big7 = big.NewInt(7)
big8 = big.NewInt(8)
big16 = big.NewInt(16)
big20 = big.NewInt(20)
big32 = big.NewInt(32)
big64 = big.NewInt(64)
big96 = big.NewInt(96)
Expand All @@ -261,6 +284,35 @@ var (
big199680 = big.NewInt(199680)
)

// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
//
// def mult_complexity(x):
//
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int {
switch {
case x.Cmp(big64) <= 0:
x.Mul(x, x) // x ** 2
case x.Cmp(big1024) <= 0:
// (x ** 2 // 4 ) + ( 96 * x - 3072)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big4),
new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072),
)
default:
// (x ** 2 // 16) + (480 * x - 199680)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big16),
new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680),
)
}
return x
}

// RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 {
var (
Expand Down Expand Up @@ -295,25 +347,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
adjExpLen.Mul(big8, adjExpLen)
}
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))

// Calculate the gas cost of the operation
gas := new(big.Int).Set(math.BigMax(modLen, baseLen))
switch {
case gas.Cmp(big64) <= 0:
if c.eip2565 {
// EIP-2565 has three changes
// 1. Different multComplexity (inlined here)
// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
//
// def mult_complexity(x):
// ceiling(x/8)^2
//
//where is x is max(length_of_MODULUS, length_of_BASE)
gas = gas.Add(gas, big7)
gas = gas.Div(gas, big8)
gas.Mul(gas, gas)
case gas.Cmp(big1024) <= 0:
gas = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(gas, gas), big4),
new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072),
)
default:
gas = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(gas, gas), big16),
new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680),
)

gas.Mul(gas, math.BigMax(adjExpLen, big1))
// 2. Different divisor (`GQUADDIVISOR`) (3)
gas.Div(gas, big3)
if gas.BitLen() > 64 {
return math.MaxUint64
}
// 3. Minimum price of 200 gas
if gas.Uint64() < 200 {
return 200
}
return gas.Uint64()
}
gas = modexpMultComplexity(gas)
gas.Mul(gas, math.BigMax(adjExpLen, big1))
gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv))
gas.Div(gas, big20)

if gas.BitLen() > 64 {
return math.MaxUint64
Expand Down
65 changes: 59 additions & 6 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package vm

import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"os"
"reflect"
"testing"

Expand Down Expand Up @@ -48,6 +50,25 @@ type precompiledFailureTest struct {
name string
}

// allPrecompiles does not map to the actual set of precompiles, as it also contains
// repriced versions of precompiles at certain slots
var allPrecompiles = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{30}): &ringSignatureVerifier{},
common.BytesToAddress([]byte{40}): &bulletproofVerifier{},
common.BytesToAddress([]byte{41}): &XDCxLastPrice{},
common.BytesToAddress([]byte{42}): &XDCxEpochPrice{},
}

// modexpTests are the test and benchmark data for the modexp precompiled contract.
var modexpTests = []precompiledTest{
{
Expand Down Expand Up @@ -490,7 +511,7 @@ var blake2FTests = []precompiledTest{
}

func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) {
Expand All @@ -509,7 +530,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {

func testXDCxPrecompiled(addr string, test precompiledTest, t *testing.T) {
contractAddr := common.HexToAddress(addr)
p := PrecompiledContractsByzantium[contractAddr]
p := allPrecompiles[contractAddr]
in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) {
Expand All @@ -523,7 +544,7 @@ func testXDCxPrecompiled(addr string, test precompiledTest, t *testing.T) {

func testPrecompiledWithEmptyTradingState(addr string, test precompiledTest, t *testing.T) {
contractAddr := common.HexToAddress(addr)
p := PrecompiledContractsByzantium[contractAddr]
p := allPrecompiles[contractAddr]
in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("testPrecompiledWithEmptyTradingState-%s-Gas=%d", test.name, gas), func(t *testing.T) {
Expand All @@ -536,7 +557,7 @@ func testPrecompiledWithEmptyTradingState(addr string, test precompiledTest, t *
}

func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in) - 1

Expand All @@ -554,7 +575,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
}

func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in)
t.Run(test.name, func(t *testing.T) {
Expand All @@ -574,7 +595,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
if test.noBenchmark {
return
}
p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
reqGas := p.RequiredGas(in)

Expand Down Expand Up @@ -657,6 +678,9 @@ func BenchmarkPrecompiledModExp(bench *testing.B) {
}
}

func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) }
func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) }

// Tests the sample inputs from the elliptic curve addition EIP 213.
func TestPrecompiledBn256Add(t *testing.T) {
for _, test := range bn256AddTests {
Expand Down Expand Up @@ -800,5 +824,34 @@ func TestPrecompiledEcrecover(t *testing.T) {
for _, test := range ecRecoverTests {
testPrecompiled("01", test, t)
}
}

func testJson(name, addr string, t *testing.T) {
tests, err := loadJson(name)
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
testPrecompiled(addr, test, t)
}
}

func benchJson(name, addr string, b *testing.B) {
tests, err := loadJson(name)
if err != nil {
b.Fatal(err)
}
for _, test := range tests {
benchmarkPrecompiled(addr, test, b)
}
}

func loadJson(name string) ([]precompiledTest, error) {
data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
if err != nil {
return nil, err
}
var testcases []precompiledTest
err = json.Unmarshal(data, &testcases)
return testcases, err
}
Loading

0 comments on commit 0ed603b

Please sign in to comment.