Skip to content

Commit

Permalink
update tests, improve (native) fuzzing (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
holiman authored May 7, 2024
1 parent 4152654 commit 7aa449e
Show file tree
Hide file tree
Showing 8 changed files with 738 additions and 1,228 deletions.
17 changes: 17 additions & 0 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,23 @@ func BenchmarkRsh(bench *testing.B) {
bench.Run("n_gt_0/uint256", benchmark_Rsh_Bit_N_GT_0)
}

// bigExp implements exponentiation by squaring.
// The result is truncated to 256 bits.
func bigExp(result, base, exponent *big.Int) *big.Int {
result.SetUint64(1)

for _, word := range exponent.Bits() {
for i := 0; i < wordBits; i++ {
if word&1 == 1 {
u256(result.Mul(result, base))
}
u256(base.Mul(base, base))
word >>= 1
}
}
return result
}

func benchmark_Exp_Big(bench *testing.B) {
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
Expand Down
275 changes: 275 additions & 0 deletions binary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
// uint256: Fixed size 256-bit math library
// Copyright 2020 uint256 Authors
// SPDX-License-Identifier: BSD-3-Clause

package uint256

import (
"fmt"
"math/big"
"testing"
)

type opDualArgFunc func(*Int, *Int, *Int) *Int
type bigDualArgFunc func(*big.Int, *big.Int, *big.Int) *big.Int

type opCmpArgFunc func(*Int, *Int) bool
type bigCmpArgFunc func(*big.Int, *big.Int) bool

type binaryOpEntry struct {
name string
u256Fn opDualArgFunc
bigFn bigDualArgFunc
}

var binaryOpFuncs = []binaryOpEntry{
{"Add", (*Int).Add, (*big.Int).Add},
{"Sub", (*Int).Sub, (*big.Int).Sub},
{"Mul", (*Int).Mul, (*big.Int).Mul},
{"Div", (*Int).Div, bigDiv},
{"Mod", (*Int).Mod, bigMod},
{"SDiv", (*Int).SDiv, bigSDiv},
{"SMod", (*Int).SMod, bigSMod},
{"And", (*Int).And, (*big.Int).And},
{"Or", (*Int).Or, (*big.Int).Or},
{"Xor", (*Int).Xor, (*big.Int).Xor},
{"Exp", (*Int).Exp, func(b1, b2, b3 *big.Int) *big.Int { return b1.Exp(b2, b3, bigtt256) }},
{"Lsh", u256Lsh, bigLsh},
{"Rsh", u256Rsh, bigRsh},
{"SRsh", u256SRsh, bigSRsh},
{"DivModDiv", divModDiv, bigDiv},
{"DivModMod", divModMod, bigMod},
{"udivremDiv", udivremDiv, bigDiv},
{"udivremMod", udivremMod, bigMod},
{"ExtendSign", (*Int).ExtendSign, bigExtendSign},
}

var cmpOpFuncs = []struct {
name string
u256Fn opCmpArgFunc
bigFn bigCmpArgFunc
}{
{"Eq", (*Int).Eq, func(a, b *big.Int) bool { return a.Cmp(b) == 0 }},
{"Lt", (*Int).Lt, func(a, b *big.Int) bool { return a.Cmp(b) < 0 }},
{"Gt", (*Int).Gt, func(a, b *big.Int) bool { return a.Cmp(b) > 0 }},
{"Slt", (*Int).Slt, func(a, b *big.Int) bool { return S256(a).Cmp(S256(b)) < 0 }},
{"Sgt", (*Int).Sgt, func(a, b *big.Int) bool { return S256(a).Cmp(S256(b)) > 0 }},
{"CmpEq", func(a, b *Int) bool { return a.Cmp(b) == 0 }, func(a, b *big.Int) bool { return a.Cmp(b) == 0 }},
{"CmpLt", func(a, b *Int) bool { return a.Cmp(b) < 0 }, func(a, b *big.Int) bool { return a.Cmp(b) < 0 }},
{"CmpGt", func(a, b *Int) bool { return a.Cmp(b) > 0 }, func(a, b *big.Int) bool { return a.Cmp(b) > 0 }},
{"LtUint64", func(a, b *Int) bool { return a.LtUint64(b.Uint64()) }, func(a, b *big.Int) bool { return a.Cmp(new(big.Int).SetUint64(b.Uint64())) < 0 }},
{"GtUint64", func(a, b *Int) bool { return a.GtUint64(b.Uint64()) }, func(a, b *big.Int) bool { return a.Cmp(new(big.Int).SetUint64(b.Uint64())) > 0 }},
}

func lookupBinary(name string) binaryOpEntry {
for _, tc := range binaryOpFuncs {
if tc.name == name {
return tc
}
}
panic(fmt.Sprintf("%v not found", name))
}

func checkBinaryOperation(t *testing.T, opName string, op opDualArgFunc, bigOp bigDualArgFunc, x, y Int) {
var (
b1 = x.ToBig()
b2 = y.ToBig()
f1 = x.Clone()
f2 = y.Clone()
operation = fmt.Sprintf("op: %v ( %v, %v ) ", opName, x.Hex(), y.Hex())
want, _ = FromBig(bigOp(new(big.Int), b1, b2))
have = op(new(Int), f1, f2)
)
// Compare result with big.Int.
if !have.Eq(want) {
t.Fatalf("%v\nwant : %#x\nhave : %#x\n", operation, want, have)
}

// Check if arguments are unmodified.
if !f1.Eq(x.Clone()) {
t.Fatalf("%v\nfirst argument had been modified: %x", operation, f1)
}
if !f2.Eq(y.Clone()) {
t.Fatalf("%v\nsecond argument had been modified: %x", operation, f2)
}

// Check if reusing args as result works correctly.
have = op(f1, f1, y.Clone())
if have != f1 {
t.Fatalf("%v\nunexpected pointer returned: %p, expected: %p\n", operation, have, f1)
}
if !have.Eq(want) {
t.Fatalf("%v\non argument reuse x.op(x,y)\nwant : %#x\nhave : %#x\n", operation, want, have)
}
have = op(f2, x.Clone(), f2)
if have != f2 {
t.Fatalf("%v\nunexpected pointer returned: %p, expected: %p\n", operation, have, f2)
}
if !have.Eq(want) {
t.Fatalf("%v\n on argument reuse x.op(y,x)\nwant : %#x\nhave : %#x\n", operation, want, have)
}
}

func TestBinaryOperations(t *testing.T) {
for _, tc := range binaryOpFuncs {
for _, inputs := range binTestCases {
f1 := MustFromHex(inputs[0])
f2 := MustFromHex(inputs[1])
checkBinaryOperation(t, tc.name, tc.u256Fn, tc.bigFn, *f1, *f2)
}
}
}

func Test10KRandomBinaryOperations(t *testing.T) {
for _, tc := range binaryOpFuncs {
for i := 0; i < 10000; i++ {
f1 := randNum()
f2 := randNum()
checkBinaryOperation(t, tc.name, tc.u256Fn, tc.bigFn, *f1, *f2)
}
}
}

func FuzzBinaryOperations(f *testing.F) {
f.Fuzz(func(t *testing.T, x0, x1, x2, x3, y0, y1, y2, y3 uint64) {
x := Int{x0, x1, x2, x3}
y := Int{y0, y1, y2, y3}
for _, tc := range binaryOpFuncs {
checkBinaryOperation(t, tc.name, tc.u256Fn, tc.bigFn, x, y)
}
})
}

func u256Rsh(z, x, y *Int) *Int {
return z.Rsh(x, uint(y.Uint64()&0x1FF))
}
func bigRsh(z, x, y *big.Int) *big.Int {
return z.Rsh(x, uint(y.Uint64()&0x1FF))
}

func u256Lsh(z, x, y *Int) *Int {
return z.Lsh(x, uint(y.Uint64()&0x1FF))
}
func u256SRsh(z, x, y *Int) *Int {
return z.SRsh(x, uint(y.Uint64()&0x1FF))
}

func bigLsh(z, x, y *big.Int) *big.Int {
return z.Lsh(x, uint(y.Uint64()&0x1FF))
}

func bigSRsh(z, x, y *big.Int) *big.Int {
return z.Rsh(S256(x), uint(y.Uint64()&0x1FF))
}

func bigExtendSign(result, num, byteNum *big.Int) *big.Int {
if byteNum.Cmp(big.NewInt(31)) >= 0 {
return result.Set(num)
}
bit := uint(byteNum.Uint64()*8 + 7)
mask := byteNum.Lsh(big.NewInt(1), bit)
mask.Sub(mask, big.NewInt(1))
if num.Bit(int(bit)) > 0 {
result.Or(num, mask.Not(mask))
} else {
result.And(num, mask)
}
return result
}

// bigDiv implements uint256/EVM compatible division for big.Int: returns 0 when dividing by 0
func bigDiv(z, x, y *big.Int) *big.Int {
if y.Sign() == 0 {
return z.SetUint64(0)
}
return z.Div(x, y)
}

// bigMod implements uint256/EVM compatible mod for big.Int: returns 0 when dividing by 0
func bigMod(z, x, y *big.Int) *big.Int {
if y.Sign() == 0 {
return z.SetUint64(0)
}
return z.Mod(x, y)
}

// bigSDiv implements EVM-compatible SDIV operation on big.Int
func bigSDiv(result, x, y *big.Int) *big.Int {
if y.Sign() == 0 {
return result.SetUint64(0)
}
sx := S256(x)
sy := S256(y)

n := new(big.Int)
if sx.Sign() == sy.Sign() {
n.SetInt64(1)
} else {
n.SetInt64(-1)
}
result.Div(sx.Abs(sx), sy.Abs(sy))
result.Mul(result, n)
return result
}

// bigSMod implements EVM-compatible SMOD operation on big.Int
func bigSMod(result, x, y *big.Int) *big.Int {
if y.Sign() == 0 {
return result.SetUint64(0)
}

sx := S256(x)
sy := S256(y)
neg := sx.Sign() < 0

result.Mod(sx.Abs(sx), sy.Abs(sy))
if neg {
result.Neg(result)
}
return u256(result)
}

func checkCompareOperation(t *testing.T, opName string, op opCmpArgFunc, bigOp bigCmpArgFunc, x, y Int) {
var (
f1orig = x.Clone()
f2orig = y.Clone()
b1 = x.ToBig()
b2 = y.ToBig()
f1 = new(Int).Set(f1orig)
f2 = new(Int).Set(f2orig)
operation = fmt.Sprintf("op: %v ( %v, %v ) ", opName, x.Hex(), y.Hex())
want = bigOp(b1, b2)
have = op(f1, f2)
)
// Compare result with big.Int.
if have != want {
t.Fatalf("%v\nwant : %v\nhave : %v\n", operation, want, have)
}
// Check if arguments are unmodified.
if !f1.Eq(f1orig) {
t.Fatalf("%v\nfirst argument had been modified: %x", operation, f1)
}
if !f2.Eq(f2orig) {
t.Fatalf("%v\nsecond argument had been modified: %x", operation, f2)
}
}

func TestCompareOperations(t *testing.T) {
for _, tc := range cmpOpFuncs {
for _, inputs := range binTestCases {
f1 := MustFromHex(inputs[0])
f2 := MustFromHex(inputs[1])
checkCompareOperation(t, tc.name, tc.u256Fn, tc.bigFn, *f1, *f2)
}
}
}

func FuzzCompareOperations(f *testing.F) {
f.Fuzz(func(t *testing.T, x0, x1, x2, x3, y0, y1, y2, y3 uint64) {
x := Int{x0, x1, x2, x3}
y := Int{y0, y1, y2, y3}
for _, tc := range cmpOpFuncs {
checkCompareOperation(t, tc.name, tc.u256Fn, tc.bigFn, x, y)
}
})
}
34 changes: 25 additions & 9 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ commands:
jobs:

go121:
go122:
docker:
- image: cimg/go:1.21
- image: cimg/go:1.22
steps:
- run:
name: "Install tools"
command: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.1
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.58.0
- checkout
- run:
name: "Lint"
Expand All @@ -44,8 +44,14 @@ jobs:
- run:
name: "Fuzzing"
command: |
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzBase10StringCompare -fuzztime 30s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzDecimal -fuzztime 30s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzBinaryOperations -fuzztime 20s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzCompareOperations -fuzztime 20s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzTernaryOperations -fuzztime 20s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzBase10StringCompare -fuzztime 10s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzDecimal -fuzztime 10s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzFloat64 -fuzztime 10s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzLog10 -fuzztime 10s
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz FuzzSetString -fuzztime 10s
- save_cache:
key: corpus-v3-{{ epoch }}
paths:
Expand All @@ -65,7 +71,7 @@ jobs:

bigendian:
docker:
- image: circleci/buildpack-deps:bullseye
- image: cimg/base:current
steps:
- run:
name: "Install QEMU"
Expand All @@ -76,6 +82,13 @@ jobs:
name: "Test (PPC64 emulation)"
command: qemu-ppc64-static uint256.test.ppc64 -test.v

go121:
docker:
- image: cimg/go:1.21
steps:
- checkout
- test

go120:
docker:
- image: cimg/go:1.20
Expand All @@ -96,9 +109,12 @@ workflows:
version: 2
uint256:
jobs:
- go121
- go120
- go119
- bigendian:
- go120
- go121
- go122:
requires:
- go121
- bigendian:
requires:
- go122
Loading

0 comments on commit 7aa449e

Please sign in to comment.