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

update tests, improve (native) fuzzing #163

Merged
merged 11 commits into from
May 7, 2024
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
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