From 4400f9d753ec5cdc579273547374ca1afde6764a Mon Sep 17 00:00:00 2001 From: jinoosss <112360739+jinoosss@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:37:22 +0900 Subject: [PATCH 01/28] refactor(p/int256): changes to the int256 package implementation --- contract/p/gnoswap/int256/absolute.gno | 38 - contract/p/gnoswap/int256/absolute_test.gno | 184 ----- contract/p/gnoswap/int256/arithmetic.gno | 372 +++------- contract/p/gnoswap/int256/arithmetic_test.gno | 483 +------------ contract/p/gnoswap/int256/bitwise.gno | 101 --- contract/p/gnoswap/int256/bitwise_test.gno | 204 ------ contract/p/gnoswap/int256/cmp.gno | 117 ---- contract/p/gnoswap/int256/cmp_test.gno | 316 --------- contract/p/gnoswap/int256/conversion.gno | 271 +++++--- contract/p/gnoswap/int256/conversion_test.gno | 540 --------------- contract/p/gnoswap/int256/int256.gno | 652 ++++++++++++++---- contract/p/gnoswap/int256/int256_test.gno | 131 ++-- .../p/gnoswap/int256/runtime_metrics_test.gno | 165 ----- 13 files changed, 863 insertions(+), 2711 deletions(-) delete mode 100644 contract/p/gnoswap/int256/absolute.gno delete mode 100644 contract/p/gnoswap/int256/absolute_test.gno delete mode 100644 contract/p/gnoswap/int256/bitwise.gno delete mode 100644 contract/p/gnoswap/int256/bitwise_test.gno delete mode 100644 contract/p/gnoswap/int256/cmp.gno delete mode 100644 contract/p/gnoswap/int256/cmp_test.gno delete mode 100644 contract/p/gnoswap/int256/conversion_test.gno delete mode 100644 contract/p/gnoswap/int256/runtime_metrics_test.gno diff --git a/contract/p/gnoswap/int256/absolute.gno b/contract/p/gnoswap/int256/absolute.gno deleted file mode 100644 index 584be440c..000000000 --- a/contract/p/gnoswap/int256/absolute.gno +++ /dev/null @@ -1,38 +0,0 @@ -package int256 - -import ( - "gno.land/p/gnoswap/uint256" -) - -// Abs returns the absolute value of z. -func (z *Int) Abs() *uint256.Uint { - return z.abs.Clone() -} - -// AbsGt returns true if the absolute value of z is greater than x. -func (z *Int) AbsGt(x *uint256.Uint) bool { - return z.abs.Gt(x) -} - -// AbsLt returns true if the absolute value of z is less than x. -func (z *Int) AbsLt(x *uint256.Uint) bool { - return z.abs.Lt(x) -} - -// AbsOverflow sets z to the absolute value of x and returns z and whether overflow occurred. -// Overflow occurs when x is the minimum int256 value (-2^255), as its absolute value (2^255) -// cannot be represented in a signed 256-bit integer. -func (z *Int) AbsOverflow(x *Int) (*Int, bool) { - z = z.initiateAbs() - - // overflow can be happen when negating a minimum of int256 value - if x.neg && x.abs.Eq(MinInt256().abs) { - z.Set(x) // keep the original value - return z, true - } - - z.abs.Set(x.abs) - z.neg = false - - return z, false -} diff --git a/contract/p/gnoswap/int256/absolute_test.gno b/contract/p/gnoswap/int256/absolute_test.gno deleted file mode 100644 index 2162d626d..000000000 --- a/contract/p/gnoswap/int256/absolute_test.gno +++ /dev/null @@ -1,184 +0,0 @@ -package int256 - -import ( - "testing" - - "gno.land/p/gnoswap/uint256" -) - -func TestAbs(t *testing.T) { - tests := []struct { - x, want string - }{ - {"0", "0"}, - {"1", "1"}, - {"-1", "1"}, - {"-2", "2"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.Abs() - - if got.ToString() != tc.want { - t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) - } - } -} - -func TestAbsGt(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "0", "false"}, - {"1", "0", "true"}, - {"-1", "0", "true"}, - {"-1", "1", "false"}, - {"-2", "1", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.AbsGt(y) - - if got != (tc.want == "true") { - t.Errorf("AbsGt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestAbsLt(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "0", "false"}, - {"1", "0", "false"}, - {"-1", "0", "false"}, - {"-1", "1", "false"}, - {"-2", "1", "false"}, - {"-5", "10", "true"}, - {"31330", "31337", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "false"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "false"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.AbsLt(y) - - if got != (tc.want == "true") { - t.Errorf("AbsLt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestInt_AbsOverflow(t *testing.T) { - tests := []struct { - name string - x *Int - wantResult string - wantOverflow bool - }{ - { - name: "zero", - x: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "positive number", - x: NewInt(100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "negative number", - x: NewInt(-100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "max_int256", - x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967"), - wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - wantOverflow: false, - }, - { - name: "min_int256", - x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968"), - wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", - wantOverflow: true, - }, - { - name: "min_int256 + 1", - x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819967"), - wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.AbsOverflow(tt.x) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if gotResult == nil { - t.Error("unexpected nil result") - return - } - - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - - // abs value must be GTE 0 (if there is no overflow) - if !tt.wantOverflow && gotResult.neg && !gotResult.IsZero() { - t.Error("absolute value cannot be negative") - } - - // original value must not be modified - originalValue := tt.x.ToString() - if tt.x.ToString() != originalValue { - t.Errorf("original value was modified: got %v, want %v", - tt.x.ToString(), originalValue) - } - }) - } -} diff --git a/contract/p/gnoswap/int256/arithmetic.gno b/contract/p/gnoswap/int256/arithmetic.gno index 606bd015c..7907ab308 100644 --- a/contract/p/gnoswap/int256/arithmetic.gno +++ b/contract/p/gnoswap/int256/arithmetic.gno @@ -1,319 +1,145 @@ package int256 -import "gno.land/p/gnoswap/uint256" - -// Add sets z to the sum x+y and returns z. -func (z *Int) Add(x, y *Int) *Int { - z = z.initiateAbs() - - if x.neg == y.neg { - // If both numbers have the same sign, add their absolute values - z.abs.Add(x.abs, y.abs) - z.neg = x.neg - } else { - // If signs are different, subtract the smaller absolute value from the larger - if x.abs.Cmp(y.abs) >= 0 { - z.abs.Sub(x.abs, y.abs) - z.neg = x.neg - } else { - z.abs.Sub(y.abs, x.abs) - z.neg = y.neg +import "math/bits" + +func udivrem(quot, u []uint64, d *Int) (rem Int) { + var dLen int + for i := len(d) - 1; i >= 0; i-- { + if d[i] != 0 { + dLen = i + 1 + break } } - // Ensure zero is always positive - if z.abs.IsZero() { - z.neg = false - } - - return z -} - -// AddOverflow sets z to the sum x+y and returns z and whether overflow occurred. -// Overflow occurs when the result exceeds the int256 range [-2^255, 2^255-1]. -func (z *Int) AddOverflow(x, y *Int) (*Int, bool) { - z = z.initiateAbs() - - if x.neg == y.neg { - // same sign - var overflow bool - z.abs, overflow = z.abs.AddOverflow(x.abs, y.abs) - z.neg = x.neg - - if overflow { - return z, true - } - - // check int256 range - if z.neg { - if z.abs.Cmp(MinInt256().abs) > 0 { - return z, true - } - } else { - if z.abs.Cmp(MaxInt256().abs) > 0 { - return z, true - } - } - } else { - // handle different sign by subtracting absolute values - if x.abs.Cmp(y.abs) >= 0 { - z.abs.Sub(x.abs, y.abs) - z.neg = x.neg - } else { - z.abs.Sub(y.abs, x.abs) - z.neg = y.neg - } - } + shift := uint(bits.LeadingZeros64(d[dLen-1])) - // overflow can be happen when result is 0 - if z.abs.IsZero() { - z.neg = false + var dnStorage Int + dn := dnStorage[:dLen] + for i := dLen - 1; i > 0; i-- { + dn[i] = (d[i] << shift) | (d[i-1] >> (64 - shift)) } + dn[0] = d[0] << shift - return z, false -} - -// AddUint256 sets z to the sum x+y, where y is a uint256, and returns z. -func (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int { - z = z.initiateAbs() - - if x.neg { - if x.abs.Gt(y) { - z.abs.Sub(x.abs, y) - z.neg = true - } else { - z.abs.Sub(y, x.abs) - z.neg = false + var uLen int + for i := len(u) - 1; i >= 0; i-- { + if u[i] != 0 { + uLen = i + 1 + break } - } else { - z.abs.Add(x.abs, y) - z.neg = false - } - return z -} - -// AddDelta adds a signed int256 value y to an unsigned uint256 value x and stores the result in z. -func AddDelta(z, x *uint256.Uint, y *Int) { - if y.neg { - z.Sub(x, y.abs) - } else { - z.Add(x, y.abs) } -} -// AddDeltaOverflow adds a signed int256 value y to an unsigned uint256 value x, stores the result in z, -// and returns true if overflow occurs. -func AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool { - var overflow bool - if y.neg { - _, overflow = z.SubOverflow(x, y.abs) - } else { - _, overflow = z.AddOverflow(x, y.abs) + if uLen < dLen { + copy(rem[:], u) + return rem } - return overflow -} - -// Sub sets z to the difference x-y and returns z. -func (z *Int) Sub(x, y *Int) *Int { - z = z.initiateAbs() - if x.neg != y.neg { - // If sign are different, add the absolute values - z.abs.Add(x.abs, y.abs) - z.neg = x.neg - } else { - // If signs are the same, subtract the smaller absolute value from the larger - if x.abs.Cmp(y.abs) >= 0 { - z.abs = z.abs.Sub(x.abs, y.abs) - z.neg = x.neg - } else { - z.abs.Sub(y.abs, x.abs) - z.neg = !x.neg - } + var unStorage [9]uint64 + un := unStorage[:uLen+1] + un[uLen] = u[uLen-1] >> (64 - shift) + for i := uLen - 1; i > 0; i-- { + un[i] = (u[i] << shift) | (u[i-1] >> (64 - shift)) } + un[0] = u[0] << shift - // Ensure zero is always positive - if z.abs.IsZero() { - z.neg = false + if dLen == 1 { + r := udivremBy1(quot, un, dn[0]) + rem.SetUint64(r >> shift) + return rem } - return z -} -// SubUint256 sets z to the difference x-y, where y is a uint256, and returns z. -func (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int { - z = z.initiateAbs() + udivremKnuth(quot, un, dn) - if x.neg { - z.abs.Add(x.abs, y) - z.neg = true - } else { - if x.abs.Lt(y) { - z.abs.Sub(y, x.abs) - z.neg = true - } else { - z.abs.Sub(x.abs, y) - z.neg = false - } + for i := 0; i < dLen-1; i++ { + rem[i] = (un[i] >> shift) | (un[i+1] << (64 - shift)) } - return z -} - -// SubOverflow sets z to the difference x-y and returns z and whether overflow occurred. -// Overflow occurs when subtracting a positive number from the minimum int256 value -// or a negative number from the maximum int256 value. -func (z *Int) SubOverflow(x, y *Int) (*Int, bool) { - z = z.initiateAbs() - - // must keep the original value of y - negY := y.Clone() - negY.neg = !y.neg && !y.IsZero() // reverse sign if y is not zero + rem[dLen-1] = un[dLen-1] >> shift - // x + (-y) - return z.AddOverflow(x, negY) + return rem } -// Mul sets z to the product x*y and returns z. -func (z *Int) Mul(x, y *Int) *Int { - z = z.initiateAbs() - - z.abs = z.abs.Mul(x.abs, y.abs) - z.neg = x.neg != y.neg && !z.abs.IsZero() // 0 has no sign - return z -} - -// MulUint256 sets z to the product x*y, where y is a uint256, and returns z. -func (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int { - z = z.initiateAbs() - - z.abs.Mul(x.abs, y) - if z.abs.IsZero() { - z.neg = false - } else { - z.neg = x.neg +func udivremBy1(quot, u []uint64, d uint64) (rem uint64) { + reciprocal := reciprocal2by1(d) + rem = u[len(u)-1] + for j := len(u) - 2; j >= 0; j-- { + quot[j], rem = udivrem2by1(rem, u[j], d, reciprocal) } - return z + return rem } -// MulOverflow sets z to the product x*y and returns z and whether overflow occurred. -// Multiplication frequently overflows when multiplying large numbers or when -// the product exceeds the int256 range [-2^255, 2^255-1]. -func (z *Int) MulOverflow(x, y *Int) (*Int, bool) { - z = z.initiateAbs() +func udivremKnuth(quot, u, d []uint64) { + dh := d[len(d)-1] + dl := d[len(d)-2] + reciprocal := reciprocal2by1(dh) - // always 0. no need to check overflow - if x.IsZero() || y.IsZero() { - z.abs.Clear() - z.neg = false - return z, false - } - - // multiply with absolute values - absResult, overflow := z.abs.MulOverflow(x.abs, y.abs) - z.abs = absResult - - // calculate the result's sign - z.neg = x.neg != y.neg - - if overflow { - return z, true - } + for j := len(u) - len(d) - 1; j >= 0; j-- { + u2 := u[j+len(d)] + u1 := u[j+len(d)-1] + u0 := u[j+len(d)-2] - if z.neg { - if z.abs.Cmp(MinInt256().abs) > 0 { - return z, true - } - } else { - if z.abs.Cmp(MaxInt256().abs) > 0 { - return z, true + var qhat, rhat uint64 + if u2 >= dh { + qhat = ^uint64(0) + } else { + qhat, rhat = udivrem2by1(u2, u1, dh, reciprocal) + ph, pl := bits.Mul64(qhat, dl) + if ph > rhat || (ph == rhat && pl > u0) { + qhat-- + } } - } - - return z, false -} -// Div sets z to the quotient x/y for y != 0 and returns z. -// Panics if y == 0. -func (z *Int) Div(x, y *Int) *Int { - z = z.initiateAbs() + borrow := subMulTo(u[j:], d, qhat) + u[j+len(d)] = u2 - borrow + if u2 < borrow { + qhat-- + u[j+len(d)] += addTo(u[j:], d) + } - if y.abs.IsZero() { - panic("division by zero") + quot[j] = qhat } - - z.abs.Div(x.abs, y.abs) - z.neg = (x.neg != y.neg) && !z.abs.IsZero() // 0 has no sign - - return z } -// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z. -// If y == 0, z is set to 0. -func (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int { - z = z.initiateAbs() - - z.abs.Div(x.abs, y) - if z.abs.IsZero() { - z.neg = false - } else { - z.neg = x.neg - } - return z +func reciprocal2by1(d uint64) uint64 { + reciprocal, _ := bits.Div64(^d, ^uint64(0), d) + return reciprocal } -// Quo sets z to the quotient x/y for y != 0 and returns z. -// It implements truncated division (like Go). Panics if y == 0. -// This differs from mempooler int256 which requires manual panic handling. -func (z *Int) Quo(x, y *Int) *Int { - if y.IsZero() { - panic("division by zero") - } - - z = z.initiateAbs() +func udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) { + qh, ql := bits.Mul64(reciprocal, uh) + ql, carry := bits.Add64(ql, ul, 0) + qh += uh + carry + qh++ - z.abs = z.abs.Div(x.abs, y.abs) - z.neg = !(z.abs.IsZero()) && x.neg != y.neg // 0 has no sign - return z -} + r := ul - qh*d -// Rem sets z to the remainder x%y for y != 0 and returns z. -// It implements truncated modulus (like Go). Panics if y == 0. -// This differs from mempooler int256 which requires manual panic handling. -func (z *Int) Rem(x, y *Int) *Int { - if y.IsZero() { - panic("division by zero") + if r > ql { + qh-- + r += d } - z = z.initiateAbs() + if r >= d { + qh++ + r -= d + } - z.abs.Mod(x.abs, y.abs) - z.neg = z.abs.Sign() > 0 && x.neg // 0 has no sign - return z + return qh, r } -// Mod sets z to the modulus x%y for y != 0 and returns z. -// If y == 0, z is set to 0 (differs from big.Int behavior). -func (z *Int) Mod(x, y *Int) *Int { - z = z.initiateAbs() - - if x.neg { - z.abs.Div(x.abs, y.abs) - z.abs.Add(z.abs, one) - z.abs.Mul(z.abs, y.abs) - z.abs.Sub(z.abs, x.abs) - z.abs.Mod(z.abs, y.abs) - } else { - z.abs.Mod(x.abs, y.abs) +func subMulTo(x, y []uint64, multiplier uint64) uint64 { + var borrow uint64 + for i := 0; i < len(y); i++ { + s, carry1 := bits.Sub64(x[i], borrow, 0) + ph, pl := bits.Mul64(y[i], multiplier) + t, carry2 := bits.Sub64(s, pl, 0) + x[i] = t + borrow = ph + carry1 + carry2 } - z.neg = false - return z -} - -// MaxInt256 returns the maximum value for a 256-bit signed integer (2^255 - 1). -func MaxInt256() *Int { - return MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") + return borrow } -// MinInt256 returns the minimum value for a 256-bit signed integer (-2^255). -func MinInt256() *Int { - return MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968") +func addTo(x, y []uint64) uint64 { + var carry uint64 + for i := 0; i < len(y); i++ { + x[i], carry = bits.Add64(x[i], y[i], carry) + } + return carry } diff --git a/contract/p/gnoswap/int256/arithmetic_test.gno b/contract/p/gnoswap/int256/arithmetic_test.gno index efc4fa399..ebd1e0e91 100644 --- a/contract/p/gnoswap/int256/arithmetic_test.gno +++ b/contract/p/gnoswap/int256/arithmetic_test.gno @@ -1,10 +1,6 @@ package int256 -import ( - "testing" - - "gno.land/p/gnoswap/uint256" -) +import "testing" func TestAdd(t *testing.T) { tests := []struct { @@ -54,133 +50,6 @@ func TestAdd(t *testing.T) { } } -func TestAddUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "1"}, - {"1", "0", "1"}, - {"1", "1", "2"}, - {"1", "2", "3"}, - {"-1", "1", "0"}, - {"-1", "3", "2"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "1"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639934", "-1"}, - // OVERFLOW - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "0"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.AddUint256(x, y) - - if got.Neq(want) { - t.Errorf("AddUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestAddDelta(t *testing.T) { - tests := []struct { - z, x, y, want string - }{ - {"0", "0", "0", "0"}, - {"0", "0", "1", "1"}, - {"0", "1", "0", "1"}, - {"0", "1", "1", "2"}, - {"1", "2", "3", "5"}, - {"5", "10", "-3", "7"}, - // underflow - {"1", "2", "-3", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - } - - for _, tc := range tests { - z, err := uint256.FromDecimal(tc.z) - if err != nil { - t.Error(err) - continue - } - - x, err := uint256.FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := uint256.FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - AddDelta(z, x, y) - - if z.Neq(want) { - t.Errorf("AddDelta(%s, %s, %s) = %v, want %v", tc.z, tc.x, tc.y, z.ToString(), want.ToString()) - } - } -} - -func TestAddDeltaOverflow(t *testing.T) { - tests := []struct { - z, x, y string - want bool - }{ - {"0", "0", "0", false}, - // underflow - {"1", "2", "-3", true}, - } - - for _, tc := range tests { - z, err := uint256.FromDecimal(tc.z) - if err != nil { - t.Error(err) - continue - } - - x, err := uint256.FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - result := AddDeltaOverflow(z, x, y) - if result != tc.want { - t.Errorf("AddDeltaOverflow(%s, %s, %s) = %v, want %v", tc.z, tc.x, tc.y, result, tc.want) - } - } -} - func TestAddOverflow(t *testing.T) { maxInt256 := MaxInt256() minInt256 := MinInt256() @@ -339,50 +208,6 @@ func TestSub(t *testing.T) { } } -func TestSubUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "-1"}, - {"1", "0", "1"}, - {"1", "1", "0"}, - {"1", "2", "-1"}, - {"-1", "1", "-2"}, - {"-1", "3", "-4"}, - // underflow - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "-0"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "-1"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "3", "-2"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.SubUint256(x, y) - - if got.Neq(want) { - t.Errorf("SubUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - func TestMul(t *testing.T) { tests := []struct { x, y, want string @@ -529,50 +354,6 @@ func TestInt_SubOverflow(t *testing.T) { } } -func TestMulUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"1", "0", "0"}, - {"1", "1", "1"}, - {"1", "2", "2"}, - {"-1", "1", "-1"}, - {"-1", "3", "-3"}, - {"3", "4", "12"}, - {"-3", "4", "-12"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-115792089237316195423570985008687907853269984665640564039457584007913129639932"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "115792089237316195423570985008687907853269984665640564039457584007913129639932"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.MulUint256(x, y) - - if got.Neq(want) { - t.Errorf("MulUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - func TestInt_MulOverflow(t *testing.T) { maxInt256 := MaxInt256() minInt256 := MinInt256() @@ -752,265 +533,3 @@ func TestDiv(t *testing.T) { Zero().Div(x, y) }) } - -func TestDivUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"1", "0", "0"}, - {"1", "1", "1"}, - {"1", "2", "0"}, - {"-1", "1", "-1"}, - {"-1", "3", "0"}, - {"4", "3", "1"}, - {"25", "5", "5"}, - {"25", "4", "6"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.DivUint256(x, y) - - if got.Neq(want) { - t.Errorf("DivUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestQuo(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"0", "-1", "0"}, - {"10", "1", "10"}, - {"10", "-1", "-10"}, - {"-10", "1", "-10"}, - {"-10", "-1", "10"}, - {"10", "-3", "-3"}, - {"10", "3", "3"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.Quo(x, y) - - if got.Neq(want) { - t.Errorf("Quo(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestRem(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"0", "-1", "0"}, - {"10", "1", "0"}, - {"10", "-1", "0"}, - {"-10", "1", "0"}, - {"-10", "-1", "0"}, - {"10", "3", "1"}, - {"10", "-3", "1"}, - {"-10", "3", "-1"}, - {"-10", "-3", "-1"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.Rem(x, y) - - if got.Neq(want) { - t.Errorf("Rem(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestMod(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"0", "-1", "0"}, - {"10", "0", "0"}, - {"10", "1", "0"}, - {"10", "-1", "0"}, - {"-10", "0", "0"}, - {"-10", "1", "0"}, - {"-10", "-1", "0"}, - {"10", "3", "1"}, - {"10", "-3", "1"}, - {"-10", "3", "2"}, - {"-10", "-3", "2"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.Mod(x, y) - - if got.Neq(want) { - t.Errorf("Mod(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestNilInitialization(t *testing.T) { - tests := []struct { - name string - setup func() (*Int, error) - wantStr string - }{ - { - name: "AddUint256 with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := uint256.NewUint(5) - return z.AddUint256(z, x), nil - }, - wantStr: "5", - }, - { - name: "SubUint256 with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := uint256.NewUint(5) - return z.SubUint256(z, x), nil - }, - wantStr: "-5", - }, - { - name: "MulUint256 with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := uint256.NewUint(5) - return z.MulUint256(z, x), nil - }, - wantStr: "0", - }, - { - name: "DivUint256 with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := uint256.NewUint(5) - return z.DivUint256(z, x), nil - }, - wantStr: "0", - }, - { - name: "Mod with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := MustFromDecimal("5") - defer func() { - if r := recover(); r != nil { - t.Errorf("Mod with nil abs panicked: %v", r) - } - }() - return z.Mod(z, x), nil - }, - wantStr: "0", - }, - { - name: "Chained operations with nil abs", - setup: func() (*Int, error) { - z := new(Int) - x := uint256.NewUint(5) - y := uint256.NewUint(3) - // (0 + 5) * 3 - return z.AddUint256(z, x).MulUint256(z, y), nil - }, - wantStr: "15", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := tt.setup() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - got := result.ToString() - if got != tt.wantStr { - t.Errorf("%s: got %v, want %v", tt.name, got, tt.wantStr) - } - }) - } -} diff --git a/contract/p/gnoswap/int256/bitwise.gno b/contract/p/gnoswap/int256/bitwise.gno deleted file mode 100644 index ef0bd9514..000000000 --- a/contract/p/gnoswap/int256/bitwise.gno +++ /dev/null @@ -1,101 +0,0 @@ -package int256 - -import ( - "gno.land/p/gnoswap/uint256" -) - -// Or sets z to the bitwise OR of x and y and returns z. -// The operation handles two's complement representation for negative numbers. -func (z *Int) Or(x, y *Int) *Int { - if x.neg == y.neg { - if x.neg { - // (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) & (y-1)) == -(((x-1) & (y-1)) + 1) - x1 := new(uint256.Uint).Sub(x.abs, one) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.And(x1, y1), one) - z.neg = true // z cannot be zero if x and y are negative - return z - } - - // x | y == x | y - z.abs = z.abs.Or(x.abs, y.abs) - z.neg = false - return z - } - - // x.neg != y.neg - if x.neg { - x, y = y, x // | is symmetric - } - - // x | (-y) == x | ^(y-1) == ^((y-1) &^ x) == -(^((y-1) &^ x) + 1) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one) - z.neg = true // z cannot be zero if one of x or y is negative - - return z -} - -// And sets z to the bitwise AND of x and y and returns z. -// The operation handles two's complement representation for negative numbers. -func (z *Int) And(x, y *Int) *Int { - if x.neg == y.neg { - if x.neg { - // (-x) & (-y) == ^(x-1) & ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1) - x1 := new(uint256.Uint).Sub(x.abs, one) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.Or(x1, y1), one) - z.neg = true // z cannot be zero if x and y are negative - return z - } - - // x & y == x & y - z.abs = z.abs.And(x.abs, y.abs) - z.neg = false - return z - } - - // x.neg != y.neg - // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 - if x.neg { - x, y = y, x // & is symmetric - } - - // x & (-y) == x & ^(y-1) == x &^ (y-1) - y1 := new(uint256.Uint).Sub(y.abs, uint256.One()) - z.abs = z.abs.AndNot(x.abs, y1) - z.neg = false - return z -} - -// Rsh sets z to x right-shifted by n bits and returns z. -// It performs arithmetic right shift, preserving the sign bit. -// This differs from the original implementation which used math/big. -func (z *Int) Rsh(x *Int, n uint) *Int { - if !x.neg { - z.abs.Rsh(x.abs, n) - z.neg = x.neg - return z - } - - // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 - t := NewInt(0).Sub(FromUint256(x.abs), NewInt(1)) - t = t.Rsh(t, n) - - _tmp := t.Add(t, NewInt(1)) - z.abs = _tmp.Abs() - z.neg = true - - return z -} - -// Lsh sets z to x left-shifted by n bits and returns z. -func (z *Int) Lsh(x *Int, n uint) *Int { - z.abs.Lsh(x.abs, n) - if z.abs.IsZero() { - z.neg = false - } else { - z.neg = x.neg - } - return z -} diff --git a/contract/p/gnoswap/int256/bitwise_test.gno b/contract/p/gnoswap/int256/bitwise_test.gno deleted file mode 100644 index 282de5b03..000000000 --- a/contract/p/gnoswap/int256/bitwise_test.gno +++ /dev/null @@ -1,204 +0,0 @@ -package int256 - -import ( - "testing" - - "gno.land/p/nt/uassert" - - "gno.land/p/gnoswap/uint256" -) - -func TestOr(t *testing.T) { - tests := []struct { - name string - x, y, want Int - }{ - { - name: "all zeroes", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - { - name: "mixed", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := New() - got.Or(&tc.x, &tc.y) - - if got.Neq(&tc.want) { - t.Errorf("Or(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) - } - }) - } -} - -func TestAnd(t *testing.T) { - tests := []struct { - name string - x, y, want Int - }{ - { - name: "all zeroes", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - { - name: "mixed", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "mixed 2", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "mixed 3", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "one operand zero", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "one operand all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := New() - got.And(&tc.x, &tc.y) - - if got.Neq(&tc.want) { - t.Errorf("And(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) - } - }) - } -} - -func TestRsh(t *testing.T) { - tests := []struct { - x string - n uint - want string - }{ - {"1024", 0, "1024"}, - {"1024", 1, "512"}, - {"1024", 2, "256"}, - {"1024", 10, "1"}, - {"1024", 11, "0"}, - {"18446744073709551615", 0, "18446744073709551615"}, - {"18446744073709551615", 1, "9223372036854775807"}, - {"18446744073709551615", 62, "3"}, - {"18446744073709551615", 63, "1"}, - {"18446744073709551615", 64, "0"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 0, "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 1, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 128, "340282366920938463463374607431768211455"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 255, "1"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 256, "0"}, - {"-1024", 0, "-1024"}, - {"-1024", 1, "-512"}, - {"-1024", 2, "-256"}, - {"-1024", 10, "-1"}, - {"-1024", 10, "-1"}, - {"-9223372036854775808", 0, "-9223372036854775808"}, - {"-9223372036854775808", 1, "-4611686018427387904"}, - {"-9223372036854775808", 62, "-2"}, - {"-9223372036854775808", 63, "-1"}, - {"-9223372036854775808", 64, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 0, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 1, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 253, "-4"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 254, "-2"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 256, "-1"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.Rsh(x, tc.n) - - if got.ToString() != tc.want { - t.Errorf("Rsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) - } - } -} - -func TestLsh(t *testing.T) { - tests := []struct { - x string - n uint - want string - expectPanic bool - }{ - {"1", 0, "1", false}, - {"1", 1, "2", false}, - {"1", 2, "4", false}, - {"2", 0, "2", false}, - {"2", 1, "4", false}, - {"2", 2, "8", false}, - {"-2", 0, "-2", false}, - {"-4", 0, "-4", false}, - {"-8", 0, "-8", false}, - {"-1", 255, "-57896044618658097711785492504343953926634992332820282019728792003956564819968", false}, - {"-1", 256, "0", true}, - {"-2", 255, "0", true}, - {"-4", 254, "0", true}, - {"-8", 253, "0", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := New() - if tc.expectPanic { - uassert.PanicsWithMessage(t, "Lsh: overflow", func() { - got.Lsh(x, tc.n) - }) - } else { - got.Lsh(x, tc.n) - uassert.Equal(t, tc.want, got.ToString()) - } - } -} diff --git a/contract/p/gnoswap/int256/cmp.gno b/contract/p/gnoswap/int256/cmp.gno deleted file mode 100644 index 5a891773f..000000000 --- a/contract/p/gnoswap/int256/cmp.gno +++ /dev/null @@ -1,117 +0,0 @@ -package int256 - -// Eq returns true if z equals x. -// Panics if either z or x is nil. -func (z *Int) Eq(x *Int) bool { - if z == nil || x == nil { - panic("int256: comparing with nil") - } - return (z.neg == x.neg) && z.abs.Eq(x.abs) -} - -// Neq returns true if z does not equal x. -// Panics if either z or x is nil. -func (z *Int) Neq(x *Int) bool { - if z == nil || x == nil { - panic("int256: comparing with nil") - } - return !z.Eq(x) -} - -// Cmp compares z and x and returns -1 if z < x, 0 if z == x, or +1 if z > x. -// Panics if either z or x is nil. -func (z *Int) Cmp(x *Int) (r int) { - if z == nil || x == nil { - panic("int256: comparing with nil") - } - // x cmp y == x cmp y - // x cmp (-y) == x - // (-x) cmp y == y - // (-x) cmp (-y) == -(x cmp y) - switch { - case z == x: - // nothing to do - case z.neg == x.neg: - r = z.abs.Cmp(x.abs) - if z.neg { - r = -r - } - case z.neg: - r = -1 - default: - r = 1 - } - return -} - -// IsZero returns true if z equals 0. -func (z *Int) IsZero() bool { - return z.abs.IsZero() -} - -// IsNeg returns true if z is negative. -func (z *Int) IsNeg() bool { - return z.neg -} - -// Lt returns true if z is less than x. -// Panics if either z or x is nil. -func (z *Int) Lt(x *Int) bool { - if z == nil || x == nil { - panic("int256: comparing with nil") - } - if z.neg { - if x.neg { - return z.abs.Gt(x.abs) - } else { - return true - } - } else { - if x.neg { - return false - } else { - return z.abs.Lt(x.abs) - } - } -} - -// Lte returns true if z is less than or equal to x. -func (z *Int) Lte(x *Int) bool { - return z.Lt(x) || z.Eq(x) -} - -// Gt returns true if z is greater than x. -// Panics if either z or x is nil. -func (z *Int) Gt(x *Int) bool { - if z == nil || x == nil { - panic("int256: comparing with nil") - } - if z.neg { - if x.neg { - return z.abs.Lt(x.abs) - } else { - return false - } - } else { - if x.neg { - return true - } else { - return z.abs.Gt(x.abs) - } - } -} - -// Gte returns true if z is greater than or equal to x. -func (z *Int) Gte(x *Int) bool { - return z.Gt(x) || z.Eq(x) -} - -// Clone creates a new Int identical to z. -func (z *Int) Clone() *Int { - return &Int{z.abs.Clone(), z.neg} -} - -// IsOverflow returns true if z's absolute value has overflowed. -func (z *Int) IsOverflow() bool { - return z.abs.IsOverflow() -} diff --git a/contract/p/gnoswap/int256/cmp_test.gno b/contract/p/gnoswap/int256/cmp_test.gno deleted file mode 100644 index b6bb6a8ee..000000000 --- a/contract/p/gnoswap/int256/cmp_test.gno +++ /dev/null @@ -1,316 +0,0 @@ -package int256 - -import ( - "testing" -) - -func TestEq(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", true}, - {"0", "1", false}, - {"1", "0", false}, - {"-1", "0", false}, - {"0", "-1", false}, - {"1", "1", true}, - {"-1", "-1", true}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Eq(y) - if got != tc.want { - t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestNeq(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", true}, - {"1", "0", true}, - {"-1", "0", true}, - {"0", "-1", true}, - {"1", "1", false}, - {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Neq(y) - if got != tc.want { - t.Errorf("Neq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestCmp(t *testing.T) { - tests := []struct { - x, y string - want int - }{ - {"0", "0", 0}, - {"0", "1", -1}, - {"1", "0", 1}, - {"-1", "0", -1}, - {"0", "-1", 1}, - {"1", "1", 0}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Cmp(y) - if got != tc.want { - t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestIsZero(t *testing.T) { - tests := []struct { - x string - want bool - }{ - {"0", true}, - {"-0", true}, - {"1", false}, - {"-1", false}, - {"10", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.IsZero() - if got != tc.want { - t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) - } - } -} - -func TestIsNeg(t *testing.T) { - tests := []struct { - x string - want bool - }{ - {"0", false}, - {"-0", true}, - {"1", false}, - {"-1", true}, - {"10", false}, - {"-10", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.IsNeg() - if got != tc.want { - t.Errorf("IsNeg(%s) = %v, want %v", tc.x, got, tc.want) - } - } -} - -func TestLt(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", true}, - {"1", "0", false}, - {"-1", "0", true}, - {"0", "-1", false}, - {"1", "1", false}, - {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Lt(y) - if got != tc.want { - t.Errorf("Lt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestGt(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", false}, - {"1", "0", true}, - {"-1", "0", false}, - {"0", "-1", true}, - {"1", "1", false}, - {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Gt(y) - if got != tc.want { - t.Errorf("Gt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestClone(t *testing.T) { - tests := []struct { - x string - }{ - {"0"}, - {"-0"}, - {"1"}, - {"-1"}, - {"10"}, - {"-10"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y := x.Clone() - - if x.Cmp(y) != 0 { - t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) - } - } -} - -func TestNilChecks(t *testing.T) { - validInt := NewInt(123) - - tests := []struct { - name string - fn func() - wantPanic string - }{ - { - name: "Eq with nil", - fn: func() { validInt.Eq(nil) }, - wantPanic: "int256: comparing with nil", - }, - { - name: "Neq with nil", - fn: func() { validInt.Neq(nil) }, - wantPanic: "int256: comparing with nil", - }, - { - name: "Cmp with nil", - fn: func() { validInt.Cmp(nil) }, - wantPanic: "int256: comparing with nil", - }, - { - name: "Lt with nil", - fn: func() { validInt.Lt(nil) }, - wantPanic: "int256: comparing with nil", - }, - { - name: "Gt with nil", - fn: func() { validInt.Gt(nil) }, - wantPanic: "int256: comparing with nil", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - r := recover() - if r == nil { - t.Errorf("%s: expected panic but got none", tt.name) - return - } - if r.(string) != tt.wantPanic { - t.Errorf("%s: got panic %v, want %v", tt.name, r, tt.wantPanic) - } - }() - - tt.fn() - }) - } -} diff --git a/contract/p/gnoswap/int256/conversion.gno b/contract/p/gnoswap/int256/conversion.gno index 75527da51..2e1c2c6d8 100644 --- a/contract/p/gnoswap/int256/conversion.gno +++ b/contract/p/gnoswap/int256/conversion.gno @@ -1,120 +1,213 @@ package int256 import ( - "gno.land/p/gnoswap/uint256" + "errors" + "math/bits" + "strconv" ) -// SetInt64 sets z to x and returns z. -// -// This implementation uses two's complement to handle the edge case of math.MinInt64. -// When x = math.MinInt64 (-2^63), negating it would cause an overflow since 2^63 -// cannot be represented as a positive int64. By converting to uint64 first and then -// applying two's complement (^u + 1) when negative, we correctly handle all int64 -// values including the minimum value without overflow. -func (z *Int) SetInt64(x int64) *Int { - z = z.initiateAbs() - if z.abs == nil { - panic("int256_SetInt64(): abs is nil") - } - u := uint64(x) - neg := x < 0 - if neg { - u = ^u + 1 // |x| = two's complement magnitude - } - z.abs = z.abs.SetUint64(u) - z.neg = neg && u != 0 // prevent -0 - return z -} +const ( + maxAbsI256Dec = "57896044618658097711785492504343953926634992332820282019728792003956564819968" + maxWords = 256 / bits.UintSize +) -// SetUint64 sets z to x and returns z. -func (z *Int) SetUint64(x uint64) *Int { - z = z.initiateAbs() +var ( + ErrOverflow = errors.New("int256: overflow") + ErrEmptyString = errors.New("int256: empty string") + ErrInvalidCharacter = errors.New("int256: invalid character") + ErrInvalidSignInMiddleOfNumber = errors.New("int256: invalid sign in middle of number") + + multipliers = [5]*Int{ + nil, + {0x8ac7230489e80000, 0, 0, 0}, + {0x98a224000000000, 0x4b3b4ca85a86c47a, 0, 0}, + {0x4a00000000000000, 0xebfdcb54864ada83, 0x28c87cb5c89a2571, 0}, + {0, 0x7775a5f171951000, 0x764b4abe8652979, 0x161bcca7119915b5}, + } +) - if z.abs == nil { - panic("int256_SetUint64(): abs is nil") +func FromDecimal(decimal string) (*Int, error) { + z, err := new(Int).SetString(decimal) + if err != nil { + return nil, err } - z.abs = z.abs.SetUint64(x) - z.neg = false - return z -} -// Uint64 returns the lower 64 bits of z as a uint64. -func (z *Int) Uint64() uint64 { - return z.abs.Uint64() + return z, nil } -// Int64 returns the lower 64 bits of z, interpreted as a signed int64 (two's complement). -// -// Since int64 already uses two's complement representation internally, -// we can simply apply two's complement to the unsigned magnitude when negative and cast -// to int64. This approach correctly handles all edge cases including when the magnitude -// equals 2^63 (which represents math.MinInt64 when negative) without special casing. -func (z *Int) Int64() int64 { - u := z.abs.Uint64() // lower 64 bits of magnitude - if z.neg { - u = ^u + 1 // apply two's complement for negative sign - } - return int64(u) // reinterpret as two's complement int64 +func MustFromDecimal(decimal string) *Int { + z, err := FromDecimal(decimal) + if err != nil { + panic(err) + } + return z } -// Neg sets z to -x and returns z. -func (z *Int) Neg(x *Int) *Int { - z.abs.Set(x.abs) - if z.abs.IsZero() { - z.neg = false +func (z *Int) ToString() string { + s := z.Sign() + if s == 0 { + return "0" + } + if z.IsInt64() { + return strconv.FormatInt(z.Int64(), 10) + } + y := new(Int) + if s > 0 { + y.Set(z) } else { - z.neg = !x.neg + y.Neg(z) } - return z + var ( + out = []byte("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + divisor = new(Int).SetUint64(10000000000000000000) + pos = len(out) + buf = make([]byte, 0, 19) + ) + + for { + var quot Int + rem := udivrem(quot[:], y[:], divisor) + y.Set(") + buf = strconv.AppendUint(buf[:0], rem.Uint64(), 10) + copy(out[pos-len(buf):], buf) + if y.IsZero() { + break + } + pos -= 19 + } + + var res string + if s < 0 { + res = "-" + } + res += string(out[pos-len(buf):]) + return res } -// NegOverflow sets z to -x and returns z and whether overflow occurred. -// Overflow occurs when negating the minimum int256 value (-2^255). -func (z *Int) NegOverflow(x *Int) (*Int, bool) { - z = z.initiateAbs() +func (z *Int) SetString(s string) (*Int, error) { + if len(s) == 0 { + return nil, ErrEmptyString + } - if x.IsZero() { - z.abs.Clear() - z.neg = false - return z, false + isNeg := false + switch s[0] { + case '+': + s = s[1:] + case '-': + isNeg = true + s = s[1:] } - if x.neg && x.abs.Eq(MinInt256().abs) { - z.Set(x) // must preserve the original value - return z, true + if len(s) == 0 { + return nil, ErrEmptyString } - z.abs.Set(x.abs) - z.neg = !x.neg + // Parallel comparison technique for validation + // Process in 8-byte chunks for optimal performance + sLen := len(s) + i := 0 + + // Process 8 bytes at a time + for i+7 < sLen { + // Access up to s[i+7] is safe, then we can reduce the number of bounds checks + _ = s[i+7] + + // Convert 8 bytes into a single uint64 + // This method processes bytes directly, so no endianness issues + chunk := uint64(s[i]) | uint64(s[i+1])<<8 + chunk |= uint64(s[i+2])<<16 | uint64(s[i+3])<<24 + chunk |= uint64(s[i+4])<<32 | uint64(s[i+5])<<40 + chunk |= uint64(s[i+6])<<48 | uint64(s[i+7])<<56 + + // Check for '+' (0x2B) using SWAR technique + // Subtracting 0x2B from each byte makes '+' bytes become 0 + // Subtracting 0x01 makes bytes in ASCII range (0-127) have 0 in their highest bit + // Therefore, AND with 0x80 to check for zero bytes + plusTest := ((chunk ^ 0x2B2B2B2B2B2B2B2B) - 0x0101010101010101) & 0x8080808080808080 + + // Check for '-' (0x2D) using SWAR technique + minusTest := ((chunk ^ 0x2D2D2D2D2D2D2D2D) - 0x0101010101010101) & 0x8080808080808080 + + // If either test is non-zero, a sign character exists + if (plusTest | minusTest) != 0 { + return nil, ErrInvalidSignInMiddleOfNumber + } + + i += 8 + } - return z, false -} + // Process remaining bytes + for ; i < sLen; i++ { + if s[i] == '+' || s[i] == '-' { + return nil, ErrInvalidSignInMiddleOfNumber + } + } -// Set sets z to x and returns z. -func (z *Int) Set(x *Int) *Int { - z.abs.Set(x.abs) - z.neg = x.neg - return z -} + // Strip leading zeros + if len(s) > 0 && s[0] == '0' { + idx := 0 + for idx < len(s) && s[idx] == '0' { + idx++ + } + s = s[idx:] + // If all characters were zeros, set to "0" + if len(s) == 0 { + s = "0" + } + } -// SetUint256 sets z to the value of x and returns z. -func (z *Int) SetUint256(x *uint256.Uint) *Int { - z.abs.Set(x) - z.neg = false - return z + // Check for overflow + if len(s) > len(maxAbsI256Dec) || + (len(s) == len(maxAbsI256Dec) && s > maxAbsI256Dec) || + (s == maxAbsI256Dec && !isNeg) { + return nil, ErrOverflow + } + + if err := z.fromDecimal(s); err != nil { + return nil, err + } + + if isNeg { + z.Neg(z) + } + + return z, nil } -// ToString returns the decimal string representation of z. -// Panics if z is nil. -// This differs from the original mempooler int256 implementation. -func (z *Int) ToString() string { - if z == nil { - panic("int256: nil pointer to ToString()") +func (z *Int) fromDecimal(bs string) error { + z.Clear() + var ( + num uint64 + err error + remaining = len(bs) + ) + + if remaining == 0 { + return errors.New("EOF") } - t := z.abs.Dec() - if z.neg { - return "-" + t + for i, mult := range multipliers { + if remaining <= 0 { + return nil + } + if remaining > 19 { + num, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64) + } else { + num, err = strconv.ParseUint(bs, 10, 64) + } + if err != nil { + return err + } + if i == 0 { + z.SetUint64(num) + } else { + base := new(Int).SetUint64(num) + z.Add(z, base.Mul(base, mult)) + } + if remaining > 19 { + bs = bs[0 : remaining-19] + } + remaining -= 19 } - return t + return nil } diff --git a/contract/p/gnoswap/int256/conversion_test.gno b/contract/p/gnoswap/int256/conversion_test.gno deleted file mode 100644 index aae2b8226..000000000 --- a/contract/p/gnoswap/int256/conversion_test.gno +++ /dev/null @@ -1,540 +0,0 @@ -package int256 - -import ( - "testing" - - "gno.land/p/nt/ufmt" - - "gno.land/p/gnoswap/uint256" -) - -func TestSetInt64(t *testing.T) { - tests := []struct { - x int64 - want string - }{ - {0, "0"}, - {1, "1"}, - {-1, "-1"}, - {9223372036854775807, "9223372036854775807"}, - {-9223372036854775808, "-9223372036854775808"}, - } - - for _, tc := range tests { - var z Int - z.SetInt64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) - } - } -} - -func TestSetInt64MinValueOverflow(t *testing.T) { - const minInt64 = -9223372036854775808 // -2^63 - const maxInt64 = 9223372036854775807 // 2^63 - 1 - - tests := []struct { - name string - x int64 - want string - }{ - { - name: "MinInt64 should not cause overflow", - x: minInt64, - want: "-9223372036854775808", - }, - { - name: "MaxInt64 works correctly", - x: maxInt64, - want: "9223372036854775807", - }, - { - name: "MinInt64 + 1", - x: minInt64 + 1, - want: "-9223372036854775807", - }, - { - name: "Negative one", - x: -1, - want: "-1", - }, - { - name: "Zero", - x: 0, - want: "0", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - var z Int - z.SetInt64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) - } - - // Verify the internal representation is correct - if tc.x < 0 { - if !z.neg { - t.Errorf("SetInt64(%d): expected neg=true, got neg=false", tc.x) - } - // Check magnitude for MinInt64 - if tc.x == minInt64 { - expectedMag := uint64(1 << 63) // 2^63 - gotMag := z.abs.Uint64() - if gotMag != expectedMag { - t.Errorf("SetInt64(%d): magnitude = %d, want %d", tc.x, gotMag, expectedMag) - } - } - } else if tc.x > 0 { - if z.neg { - t.Errorf("SetInt64(%d): expected neg=false, got neg=true", tc.x) - } - } else { // tc.x == 0 - if z.neg { - t.Errorf("SetInt64(0): expected neg=false (no -0), got neg=true") - } - } - }) - } -} - -func TestSetUint64(t *testing.T) { - tests := []struct { - x uint64 - want string - }{ - {0, "0"}, - {1, "1"}, - } - - for _, tc := range tests { - var z Int - z.SetUint64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) - } - } -} - -func TestUint64(t *testing.T) { - tests := []struct { - x string - want uint64 - }{ - {"0", 0}, - {"1", 1}, - {"9223372036854775807", 9223372036854775807}, - {"9223372036854775808", 9223372036854775808}, - {"18446744073709551615", 18446744073709551615}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, - {"-1", 1}, - {"-18446744073709551615", 18446744073709551615}, - {"-18446744073709551616", 0}, - {"-18446744073709551617", 1}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - - got := z.Uint64() - if got != tc.want { - t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) - } - } -} - -func TestInt64(t *testing.T) { - tests := []struct { - x string - want int64 - }{ - {"0", 0}, - {"1", 1}, - {"-1", -1}, - {"9223372036854775807", 9223372036854775807}, - {"-9223372036854775808", -9223372036854775808}, - {"9223372036854775808", -9223372036854775808}, - {"-9223372036854775809", 9223372036854775807}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, - {"18446744073709551615", -1}, - {"-18446744073709551615", 1}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - - got := z.Int64() - if got != tc.want { - t.Errorf("Int64(%s) = %d, want %d", tc.x, got, tc.want) - } - } -} - -func TestInt64EdgeCases(t *testing.T) { - const minInt64 = -9223372036854775808 // -2^63 - const maxInt64 = 9223372036854775807 // 2^63 - 1 - - tests := []struct { - name string - setupInt func() *Int - want int64 - description string - }{ - { - name: "MinInt64 from SetInt64", - setupInt: func() *Int { - z := new(Int) - return z.SetInt64(minInt64) - }, - want: minInt64, - description: "SetInt64(MinInt64) should round-trip correctly", - }, - { - name: "MaxInt64 from SetInt64", - setupInt: func() *Int { - z := new(Int) - return z.SetInt64(maxInt64) - }, - want: maxInt64, - description: "SetInt64(MaxInt64) should round-trip correctly", - }, - { - name: "Magnitude 2^63 with negative sign", - setupInt: func() *Int { - // Create Int with magnitude = 2^63 and neg = true - z := new(Int) - z.abs = uint256.NewUint(1 << 63) - z.neg = true - return z - }, - want: minInt64, - description: "Magnitude 2^63 with neg=true should return MinInt64", - }, - { - name: "Magnitude 2^63 with positive sign", - setupInt: func() *Int { - // Create Int with magnitude = 2^63 and neg = false - z := new(Int) - z.abs = uint256.NewUint(1 << 63) - z.neg = false - return z - }, - want: minInt64, // Wraps around due to two's complement - description: "Magnitude 2^63 with neg=false wraps to MinInt64", - }, - { - name: "Large positive value wrapping", - setupInt: func() *Int { - // 2^64 - 1 (max uint64) - z := new(Int) - z.abs = uint256.NewUint(18446744073709551615) - z.neg = false - return z - }, - want: -1, - description: "Max uint64 wraps to -1 in int64", - }, - { - name: "Negative large value", - setupInt: func() *Int { - // -(2^64 - 1) - z := new(Int) - z.abs = uint256.NewUint(18446744073709551615) - z.neg = true - return z - }, - want: 1, - description: "-(Max uint64) becomes 1 due to two's complement", - }, - { - name: "Zero", - setupInt: func() *Int { - return Zero() - }, - want: 0, - description: "Zero should return 0", - }, - { - name: "Negative zero prevention", - setupInt: func() *Int { - z := new(Int) - z.abs = uint256.NewUint(0) - z.neg = true // This should be normalized to false - return z - }, - want: 0, - description: "Negative zero should return 0", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - z := tc.setupInt() - got := z.Int64() - - if got != tc.want { - t.Errorf("%s: got %d, want %d", tc.description, got, tc.want) - } - }) - } -} - -// TestInt64RoundTrip verifies that SetInt64 and Int64 work correctly together -func TestInt64RoundTrip(t *testing.T) { - // Test all interesting int64 values - values := []int64{ - 0, 1, -1, - 127, -128, // int8 boundaries - 32767, -32768, // int16 boundaries - 2147483647, -2147483648, // int32 boundaries - 9223372036854775807, -9223372036854775808, // int64 boundaries (MaxInt64, MinInt64) - 1234567890, -1234567890, - } - - for _, v := range values { - t.Run(ufmt.Sprintf("RoundTrip_%d", v), func(t *testing.T) { - z := new(Int) - z.SetInt64(v) - - got := z.Int64() - if got != v { - t.Errorf("Round trip failed: SetInt64(%d).Int64() = %d", v, got) - } - }) - } -} - -func TestNeg(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "-1"}, - {"-1", "1"}, - {"9223372036854775807", "-9223372036854775807"}, - {"-18446744073709551615", "18446744073709551615"}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - z.Neg(z) - - got := z.ToString() - if got != tc.want { - t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) - } - } -} - -func TestInt_NegOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - - negMaxInt256 := New().Neg(maxInt256) - - tests := []struct { - name string - x *Int - wantResult string - wantOverflow bool - }{ - { - name: "negate zero", - x: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "negate positive", - x: NewInt(100), - wantResult: "-100", - wantOverflow: false, - }, - { - name: "negate negative", - x: NewInt(-100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "negate max_int256", - x: maxInt256, - wantResult: negMaxInt256.ToString(), - wantOverflow: false, - }, - { - name: "negate min_int256", - x: minInt256, - wantResult: minInt256.ToString(), // must preserve the original value - wantOverflow: true, - }, - { - name: "negate (min_int256 + 1)", - x: new(Int).Add(minInt256, One()), - wantResult: new(Int).Sub(maxInt256, Zero()).ToString(), - wantOverflow: false, - }, - { - name: "negate (max_int256 - 1)", - x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819966"), // max_int256 - 1 - wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819966", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.NegOverflow(tt.x) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if gotResult == nil { - t.Error("unexpected nil result") - return - } - - if gotResult.ToString() != tt.wantResult { - // use almost equal comparison to handle the precision issue - diff := new(Int).Sub(gotResult, MustFromDecimal(tt.wantResult)) - if diff.Abs().Cmp(uint256.NewUint(1)) > 0 { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - // double negation test (only if there is no overflow) - if !tt.wantOverflow { - doubleNegResult, doubleNegOverflow := new(Int).NegOverflow(gotResult) - if doubleNegOverflow { - t.Error("unexpected overflow in double negation") - } - if doubleNegResult.ToString() != tt.x.ToString() { - t.Errorf("double negation result = %v, want %v", - doubleNegResult.ToString(), tt.x.ToString()) - } - } - }) - } -} - -func TestSet(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "1"}, - {"-1", "-1"}, - {"9223372036854775807", "9223372036854775807"}, - {"-18446744073709551615", "-18446744073709551615"}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - z.Set(z) - - got := z.ToString() - if got != tc.want { - t.Errorf("set(%s) = %s, want %s", tc.x, got, tc.want) - } - } -} - -func TestSetUint256(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "1"}, - {"9223372036854775807", "9223372036854775807"}, - {"18446744073709551615", "18446744073709551615"}, - } - - for _, tc := range tests { - got := New() - - z := uint256.MustFromDecimal(tc.x) - got.SetUint256(z) - - if got.ToString() != tc.want { - t.Errorf("SetUint256(%s) = %s, want %s", tc.x, got.ToString(), tc.want) - } - } -} - -func TestToString(t *testing.T) { - tests := []struct { - name string - setup func() *Int - expected string - }{ - { - name: "Zero from subtraction", - setup: func() *Int { - minusThree := MustFromDecimal("-3") - three := MustFromDecimal("3") - return Zero().Add(minusThree, three) - }, - expected: "0", - }, - { - name: "Zero from right shift", - setup: func() *Int { - return Zero().Rsh(One(), 1234) - }, - expected: "0", - }, - { - name: "Positive number", - setup: func() *Int { - return MustFromDecimal("42") - }, - expected: "42", - }, - { - name: "Negative number", - setup: func() *Int { - return MustFromDecimal("-42") - }, - expected: "-42", - }, - { - name: "Large positive number", - setup: func() *Int { - return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") - }, - expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", - }, - { - name: "Large negative number", - setup: func() *Int { - return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") - }, - expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := tt.setup() - result := z.ToString() - if result != tt.expected { - t.Errorf("ToString() = %s, want %s", result, tt.expected) - } - }) - } -} diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 3cd68d291..6f7a34d52 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -1,182 +1,602 @@ package int256 import ( + "encoding/binary" "errors" - - "gno.land/p/gnoswap/uint256" + "math" + "math/bits" ) +type Int [4]uint64 + var ( - errEmptyString = errors.New("empty string") - errStringContainsOnlySign = errors.New("string contains only sign") - errInvalidSignInMiddleOfNumber = errors.New("invalid sign in middle of number") -) + MinI256 = &Int{0, 0, 0, 0x8000000000000000} + MaxI256 = &Int{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff} -var one = uint256.NewUint(1) + ErrZeroDivision = errors.New("zero division") + ErrNegativeNum = errors.New("negative number") +) -// Int represents a 256-bit signed integer. -// It uses a sign-magnitude representation with abs storing the absolute value -// and neg indicating whether the number is negative. -type Int struct { - abs *uint256.Uint - neg bool +func NewInt(val int64) *Int { + z := &Int{} + z.SetInt64(val) + return z } -// Zero returns a new Int set to 0. -func Zero() *Int { - return NewInt(0) +func (z *Int) Set(x *Int) *Int { + z[0], z[1], z[2], z[3] = x[0], x[1], x[2], x[3] + return z } -// One returns a new Int set to 1. -func One() *Int { - return NewInt(1) +func (z *Int) SetInt64(x int64) *Int { + if x >= 0 { + z[3], z[2], z[1], z[0] = 0, 0, 0, uint64(x) + return z + } + + z[3], z[2], z[1], z[0] = 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, uint64(x) + return z } -// Sign returns the sign of x. -// It returns -1 if x < 0, 0 if x == 0, and +1 if x > 0. -func (z *Int) Sign() int { - z.initiateAbs() +func (z *Int) IsInt64() bool { + return ((z[1]|z[2]|z[3]) == 0 && z[0] <= 0x7fffffffffffffff) || // zero or positive int64 + ((z[1]&z[2]&z[3]) == 0xffffffffffffffff && z[0] >= 0x8000000000000000) // negative int64 +} - if z.abs.IsZero() { +func (z *Int) Int64() int64 { + s := z.Sign() + if s == 0 { return 0 } - if z.neg { - return -1 + if s > 0 { + // overflow when z[0] > math.MaxInt64 + return int64(z[0]) } - return 1 + // -(2^64 - z[0]) + return -int64(math.MaxUint64 - z[0] + 1) } -// New returns a new Int set to 0. -func New() *Int { - return &Int{ - abs: new(uint256.Uint), +func (z *Int) SetUint64(x uint64) *Int { + z[3], z[2], z[1], z[0] = 0, 0, 0, x + return z +} + +func (z *Int) IsUint64() bool { + return (z[1] | z[2] | z[3]) == 0 +} + +func (z *Int) Uint64() uint64 { + return z[0] +} + +func (z *Int) Abs() *Int { + if z.IsNegative() { + if z[3] == 0x8000000000000000 && z[2] == 0 && z[1] == 0 && z[0] == 0 { + panic("int256: overflow") + } + return z.Clone().Neg(z) + } + + return z +} + +func (z *Int) Sign() int { + if z.IsZero() { + return 0 } + if z[3]&0x8000000000000000 == 0 { + return 1 + } + return -1 +} + +func (z *Int) IsZero() bool { + return (z[0] | z[1] | z[2] | z[3]) == 0 +} + +func (z *Int) IsOne() bool { + return (z[0] == 1) && (z[1]|z[2]|z[3]) == 0 +} + +func (z *Int) IsNegative() bool { + return z[3]&0x8000000000000000 != 0 +} + +func (z *Int) IsPositive() bool { + return (z[3]&0x8000000000000000) == 0 && (z[3]|z[2]|z[1]|z[0]) != 0 } -// NewInt allocates and returns a new Int set to x. -func NewInt(x int64) *Int { - return New().SetInt64(x) +func (z *Int) IsMinI256() bool { + return (z[3] == 0x8000000000000000) && ((z[2] | z[1] | z[0]) == 0) } -// FromDecimal returns a new Int from a decimal string and an error if the string is not valid. -func FromDecimal(s string) (*Int, error) { - return new(Int).SetString(s) +func (z *Int) Neg(x *Int) *Int { + var carry uint64 + z[0], z[1], z[2], z[3] = ^x[0], ^x[1], ^x[2], ^x[3] + z[0], carry = bits.Add64(z[0], 1, 0) + z[1], carry = bits.Add64(z[1], 0, carry) + z[2], carry = bits.Add64(z[2], 0, carry) + z[3] += carry + return z +} + +func (z *Int) Eq(x *Int) bool { + return (z[0] == x[0]) && (z[1] == x[1]) && (z[2] == x[2]) && (z[3] == x[3]) +} + +func (z *Int) Add(x, y *Int) *Int { + var carry uint64 + z[0], carry = bits.Add64(x[0], y[0], 0) + z[1], carry = bits.Add64(x[1], y[1], carry) + z[2], carry = bits.Add64(x[2], y[2], carry) + z[3] = x[3] + y[3] + carry + return z } -// MustFromDecimal returns a new Int from a decimal string. -// Panics if the string is not a valid decimal. -func MustFromDecimal(s string) *Int { - z, err := FromDecimal(s) - if err != nil { - panic(err) +func (z *Int) AddOverflow(x, y *Int) (*Int, bool) { + var carry uint64 + z[0], carry = bits.Add64(x[0], y[0], 0) + z[1], carry = bits.Add64(x[1], y[1], carry) + z[2], carry = bits.Add64(x[2], y[2], carry) + z[3] = x[3] + y[3] + carry + var overflow bool + signX, signY, signZ := x.Sign(), y.Sign(), z.Sign() + if (signX == signY) && (signX != signZ) { + overflow = true } + return z, overflow +} + +func (z *Int) Sub(x, y *Int) *Int { + var carry uint64 + z[0], carry = bits.Sub64(x[0], y[0], 0) + z[1], carry = bits.Sub64(x[1], y[1], carry) + z[2], carry = bits.Sub64(x[2], y[2], carry) + z[3] = x[3] - y[3] - carry return z } -// SetString sets z to the value of s and returns z and an error. -// It uses a bit masking technique for efficient sign character detection, -// based on "Bit Twiddling Hacks" by Sean Eron Anderson. -// This approach provides significantly better performance than string scanning methods. -func (z *Int) SetString(s string) (*Int, error) { - if len(s) == 0 { - return nil, errEmptyString +func (z *Int) SubOverflow(x, y *Int) (*Int, bool) { + var carry uint64 + z[0], carry = bits.Sub64(x[0], y[0], 0) + z[1], carry = bits.Sub64(x[1], y[1], carry) + z[2], carry = bits.Sub64(x[2], y[2], carry) + z[3] = x[3] - y[3] - carry + var overflow bool + signX, signY, signZ := x.Sign(), y.Sign(), z.Sign() + if (signX == 0 && y.IsMinI256()) || ((signX != 0) && (signX != signY) && (signX != signZ)) { + overflow = true } + return z, overflow +} + +func (z *Int) Mul(x, y *Int) *Int { + var ( + res Int + carry uint64 + res1, res2, res3 uint64 + ) - // check sign only in the first character - neg := false - switch s[0] { - case '+': - s = s[1:] - case '-': - neg = true - s = s[1:] + carry, res[0] = bits.Mul64(x[0], y[0]) + carry, res1 = umulHop(carry, x[1], y[0]) + carry, res2 = umulHop(carry, x[2], y[0]) + res3 = x[3]*y[0] + carry + + carry, res[1] = umulHop(res1, x[0], y[1]) + carry, res2 = umulStep(res2, x[1], y[1], carry) + res3 = res3 + x[2]*y[1] + carry + + carry, res[2] = umulHop(res2, x[0], y[2]) + res3 = res3 + x[1]*y[2] + carry + + res[3] = res3 + x[0]*y[3] + + return z.Set(&res) +} + +func (z *Int) MulOverflow(x, y *Int) (*Int, bool) { + if (x.IsMinI256() && y.IsOne()) || (x.IsOne() && y.IsMinI256()) { + return z.Set(MinI256), false + } + + var flipSign bool + xSign, ySign := x.Sign(), y.Sign() + if xSign*ySign == -1 { + flipSign = true } - // check if the string is empty after removing the sign - if len(s) == 0 { - return nil, errStringContainsOnlySign + xCopy := x.Clone() + yCopy := y.Clone() + + if xSign < 0 { + xCopy.Neg(xCopy) } + if ySign < 0 { + yCopy.Neg(yCopy) + } + + p := umul(xCopy, yCopy) + z[0], z[1], z[2], z[3] = p[0], p[1], p[2], p[3] + + var overflow bool + if ((p[4] | p[5] | p[6] | p[7]) != 0) || z.IsNegative() { + overflow = true + } + + if flipSign { + z.Neg(z) + } + + return z, overflow +} + +func umul(x, y *Int) [8]uint64 { + var ( + res [8]uint64 + carry, carry4, carry5, carry6 uint64 + res1, res2, res3, res4, res5 uint64 + ) + + carry, res[0] = bits.Mul64(x[0], y[0]) + carry, res1 = umulHop(carry, x[1], y[0]) + carry, res2 = umulHop(carry, x[2], y[0]) + carry4, res3 = umulHop(carry, x[3], y[0]) + + carry, res[1] = umulHop(res1, x[0], y[1]) + carry, res2 = umulStep(res2, x[1], y[1], carry) + carry, res3 = umulStep(res3, x[2], y[1], carry) + carry5, res4 = umulStep(carry4, x[3], y[1], carry) + + carry, res[2] = umulHop(res2, x[0], y[2]) + carry, res3 = umulStep(res3, x[1], y[2], carry) + carry, res4 = umulStep(res4, x[2], y[2], carry) + carry6, res5 = umulStep(carry5, x[3], y[2], carry) + + carry, res[3] = umulHop(res3, x[0], y[3]) + carry, res[4] = umulStep(res4, x[1], y[3], carry) + carry, res[5] = umulStep(res5, x[2], y[3], carry) + res[7], res[6] = umulStep(carry6, x[3], y[3], carry) + + return res +} + +func umulStep(z, x, y, carry uint64) (hi, lo uint64) { + hi, lo = bits.Mul64(x, y) + lo, carry = bits.Add64(lo, carry, 0) + hi += carry + lo, carry = bits.Add64(lo, z, 0) + hi += carry + return hi, lo +} + +func umulHop(z, x, y uint64) (hi, lo uint64) { + hi, lo = bits.Mul64(x, y) + lo, carry := bits.Add64(lo, z, 0) + hi += carry + return hi, lo +} - // Parallel comparison technique for sign detection - // Process in 8-byte chunks - sLen := len(s) - i := 0 +func (z *Int) Clear() *Int { + z[0], z[1], z[2], z[3] = 0, 0, 0, 0 + return z +} - // Process 8 bytes at a time - for i+7 < sLen { - // Convert 8 bytes into a single uint64 - // This method processes bytes directly, so no endianness issues - // - // access up to s[i+7] is safe, then we can reduce the number of bounds checks - _ = s[i+7] - chunk := uint64(s[i]) | uint64(s[i+1])<<8 - chunk |= uint64(s[i+2])<<16 | uint64(s[i+3])<<24 - chunk |= uint64(s[i+4])<<32 | uint64(s[i+5])<<40 - chunk |= uint64(s[i+6])<<48 | uint64(s[i+7])<<56 +func (z *Int) SetOne() *Int { + z[3], z[2], z[1], z[0] = 0, 0, 0, 1 + return z +} - // Operation to check for '+' (0x2B) - // Subtracting 0x2B from each byte makes '+' bytes become 0 - // Subtracting 0x01 makes bytes in ASCII range (0-127) have 0 in their highest bit - // Therefore, AND with 0x80 to check for zero bytes - plusTest := ((chunk ^ 0x2B2B2B2B2B2B2B2B) - 0x0101010101010101) & 0x8080808080808080 +func (z *Int) SetAllBitsOne() *Int { + z[0], z[1], z[2], z[3] = 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff + return z +} - // check for '-' (0x2D) - minusTest := ((chunk ^ 0x2D2D2D2D2D2D2D2D) - 0x0101010101010101) & 0x8080808080808080 +func (z *Int) Quo(x, y *Int) *Int { + if x.Sign() > 0 { + if y.Sign() > 0 { + return z.uquo(x, y) + } + z.uquo(x, new(Int).Neg(y)) + return z.Neg(z) + } + if y.Sign() < 0 { + return z.uquo(new(Int).Neg(x), new(Int).Neg(y)) + } + z.uquo(new(Int).Neg(x), y) + return z.Neg(z) +} - // If either test is non-zero, a sign character exists - if (plusTest | minusTest) != 0 { - return nil, errInvalidSignInMiddleOfNumber +func (z *Int) uquo(x, y *Int) *Int { + if y.IsZero() { + panic(ErrZeroDivision) + } + if x.IsZero() { + z.Clear() + } + if x.Eq(y) { + return z.SetOne() + } + if x.IsInt64() && y.IsInt64() { + return z.SetInt64(x.Int64() / y.Int64()) + } + quot := Int{} + udivrem(quot[:], x[:], y) + return z.Set(") +} + +func (z *Int) Rem(x, y *Int) *Int { + if x.Sign() > 0 { + if y.Sign() > 0 { + return z.urem(x, y) } + return z.urem(x, new(Int).Neg(y)) + } + if y.Sign() < 0 { + z.urem(new(Int).Neg(x), new(Int).Neg(y)) + return z.Neg(z) + } + z.urem(new(Int).Neg(x), y) + return z.Neg(z) +} - i += 8 +func (z *Int) urem(x, y *Int) *Int { + if y.IsZero() { + panic(ErrZeroDivision) + } + if x.IsZero() { + z.Clear() + } + if x.Eq(y) { + return z.Clear() } + if x.IsInt64() && y.IsInt64() { + xInt64 := x.Int64() + yInt64 := y.Int64() + return z.SetInt64(xInt64 % yInt64) + } + quot := Int{} + rem := udivrem(quot[:], x[:], y) + return z.Set(&rem) +} - // Process remaining bytes - for ; i < sLen; i++ { - if s[i] == '+' || s[i] == '-' { - return nil, errInvalidSignInMiddleOfNumber +func (z *Int) Pow(x *Int, n uint64) *Int { + c := x.Clone() + z.SetOne() + for n > 0 { + if n&1 == 1 { + z.Mul(z, c) } + n >>= 1 + c.Mul(c, c) } + return z +} + +func (z *Int) Lt(x *Int) bool { + return z.Cmp(x) < 0 +} - abs, err := uint256.FromDecimal(s) - if err != nil { - return nil, err +func (z *Int) Lte(x *Int) bool { + return z.Cmp(x) <= 0 +} + +func (z *Int) Gt(x *Int) bool { + return z.Cmp(x) > 0 +} + +func (z *Int) Gte(x *Int) bool { + return z.Cmp(x) >= 0 +} + +func (z *Int) Cmp(x *Int) int { + zneg := int8(z[3] >> 63) + xneg := int8(x[3] >> 63) + if zneg != xneg { + return int(xneg - zneg) + } + d0, carry := bits.Sub64(z[0], x[0], 0) + d1, carry := bits.Sub64(z[1], x[1], carry) + d2, carry := bits.Sub64(z[2], x[2], carry) + d3, carry := bits.Sub64(z[3], x[3], carry) + if carry == 1 { + return -1 + } + if d0|d1|d2|d3 == 0 { + return 0 } + return 1 +} + +func (z *Int) Clone() *Int { + return &Int{z[0], z[1], z[2], z[3]} +} - return &Int{abs, neg}, nil +func (z *Int) Or(x, y *Int) *Int { + z[0] = x[0] | y[0] + z[1] = x[1] | y[1] + z[2] = x[2] | y[2] + z[3] = x[3] | y[3] + return z } -// FromUint256 creates a new Int from a uint256.Uint. -// It returns nil if the input is nil. -func FromUint256(x *uint256.Uint) *Int { - if x == nil { - return nil +func (z *Int) And(x, y *Int) *Int { + z[0] = x[0] & y[0] + z[1] = x[1] & y[1] + z[2] = x[2] & y[2] + z[3] = x[3] & y[3] + return z +} + +func (z *Int) Xor(x, y *Int) *Int { + z[0] = x[0] ^ y[0] + z[1] = x[1] ^ y[1] + z[2] = x[2] ^ y[2] + z[3] = x[3] ^ y[3] + return z +} + +func (z *Int) Not(x *Int) *Int { + z[0] = ^x[0] + z[1] = ^x[1] + z[2] = ^x[2] + z[3] = ^x[3] + return z +} + +func (z *Int) Lsh(x *Int, n uint) *Int { + if n == 0 { + return z.Set(x) } - z := Zero() + if n >= 256 { + return z.Clear() + } + switch { + case n >= 192: + n -= 192 + z[3], z[2], z[1], z[0] = x[0]<= 128: + n -= 128 + z[3] = (x[1] << n) | (x[0] >> (64 - n)) + z[2] = x[0] << n + z[1], z[0] = 0, 0 + case n >= 64: + n -= 64 + z[3] = (x[2] << n) | (x[1] >> (64 - n)) + z[2] = (x[1] << n) | (x[0] >> (64 - n)) + z[1] = x[0] << n + z[0] = 0 + default: + z[3] = (x[3] << n) | (x[2] >> (64 - n)) + z[2] = (x[2] << n) | (x[1] >> (64 - n)) + z[1] = (x[1] << n) | (x[0] >> (64 - n)) + z[0] = x[0] << n + } + return z +} - z.SetUint256(x) +func (z *Int) Rsh(x *Int, n uint) *Int { + if n == 0 { + return z.Set(x) + } + if x.IsNegative() { + return z.negRsh(x, n) + } + return z.rsh(x, n) +} + +func (z *Int) rsh(x *Int, n uint) *Int { + if n >= 255 { + return z.Clear() + } + switch { + case n >= 192: + n -= 192 + z[3], z[2], z[1], z[0] = 0, 0, 0, x[3]>>n + case n >= 128: + n -= 128 + z[3], z[2] = 0, 0 + z[1] = x[3] >> n + z[0] = (x[3] << (64 - n)) | (x[2] >> n) + case n >= 64: + n -= 64 + z[3] = 0 + z[2] = x[3] >> n + z[1] = (x[3] << (64 - n)) | (x[2] >> n) + z[0] = (x[2] << (64 - n)) | (x[1] >> n) + default: + z[3] = x[3] >> n + z[2] = (x[3] << (64 - n)) | (x[2] >> n) + z[1] = (x[2] << (64 - n)) | (x[1] >> n) + z[0] = (x[1] << (64 - n)) | (x[0] >> n) + } return z } -// NilToZero returns z if it's non-nil, otherwise returns a new Int set to 0. -// This differs from the original mempooler int256 implementation. -func (z *Int) NilToZero() *Int { - if z == nil { - return NewInt(0) +func (z *Int) negRsh(x *Int, n uint) *Int { + if n >= 255 { + return z.SetAllBitsOne() + } + var v uint64 = 0xffffffffffffffff + switch { + case n >= 192: + n -= 192 + z[3], z[2], z[1], z[0] = v, v, v, (v<<(64-n))|(x[3]>>n) + case n >= 128: + n -= 128 + z[3], z[2] = v, v + z[1] = (v << (64 - n)) | (x[3] >> n) + z[0] = (x[3] << (64 - n)) | (x[2] >> n) + case n >= 64: + n -= 64 + z[3] = v + z[2] = (v << (64 - n)) | (x[3] >> n) + z[1] = (x[3] << (64 - n)) | (x[2] >> n) + z[0] = (x[2] << (64 - n)) | (x[1] >> n) + default: + z[3] = (v << (64 - n)) | (x[3] >> n) + z[2] = (x[3] << (64 - n)) | (x[2] >> n) + z[1] = (x[2] << (64 - n)) | (x[1] >> n) + z[0] = (x[1] << (64 - n)) | (x[0] >> n) } return z } -// initiateAbs ensures z and z.abs are initialized. -// If z is nil, it returns a new Int set to 0. -// If z.abs is nil, it initializes it to a new uint256.Uint. -// This differs from mempooler int256 by also checking if z itself is nil. -func (z *Int) initiateAbs() *Int { - if z == nil { - return NewInt(0) +func (z *Int) Sqrt(x *Int) *Int { + if x.IsNegative() { + panic(ErrNegativeNum) + } + if x.IsInt64() { + return z.SetInt64(int64(math.Sqrt(float64(x.Int64())))) + } + var ( + z1 = new(Int).SetOne() + z2 = new(Int) + ) + z1 = z1.Lsh(z1, uint(x.BitLen()+1)>>1) + for { + z2 = z2.Quo(x, z1) + z2 = z2.Add(z2, z1) + { + a := z2[3] << 63 + z2[3] = z2[3] >> 1 + b := z2[2] << 63 + z2[2] = (z2[2] >> 1) | a + a = z2[1] << 63 + z2[1] = (z2[1] >> 1) | b + z2[0] = (z2[0] >> 1) | a + } + if z2.Cmp(z1) >= 0 { + return z.Set(z1) + } + z1, z2 = z2, z1 } - if z.abs == nil { - z.abs = new(uint256.Uint) +} + +func (z *Int) BitLen() int { + switch { + case z[3] != 0: + return 192 + bits.Len64(z[3]) + case z[2] != 0: + return 128 + bits.Len64(z[2]) + case z[1] != 0: + return 64 + bits.Len64(z[1]) + default: + return bits.Len64(z[0]) } +} + +func (z *Int) SetBytes32(in []byte) *Int { + _ = in[31] // bounds check hint to compiler; see golang.org/issue/14808 + z[3] = binary.BigEndian.Uint64(in[0:8]) + z[2] = binary.BigEndian.Uint64(in[8:16]) + z[1] = binary.BigEndian.Uint64(in[16:24]) + z[0] = binary.BigEndian.Uint64(in[24:32]) return z } + +// WriteToArray32 writes all 32 bytes of z to the destination array, including zero-bytes +func (z *Int) WriteToArray32(dest *[32]byte) { + for i := 0; i < 32; i++ { + dest[31-i] = byte(z[i/8] >> uint64(8*(i%8))) + } +} diff --git a/contract/p/gnoswap/int256/int256_test.gno b/contract/p/gnoswap/int256/int256_test.gno index f8e3060be..57699e075 100644 --- a/contract/p/gnoswap/int256/int256_test.gno +++ b/contract/p/gnoswap/int256/int256_test.gno @@ -3,6 +3,9 @@ package int256 import ( "testing" + + "gno.land/p/nt/uassert" + "gno.land/p/nt/ufmt" ) func TestSign(t *testing.T) { @@ -72,114 +75,70 @@ func TestSetString(t *testing.T) { } if err != nil { + println("ERROR", err.Error(), tt.input) t.Errorf("SetString(%q) returned unexpected error: %v", tt.input, err) continue } - if got := z.abs.ToString(); got != tt.wantVal { + if got := z.Abs().ToString(); got != tt.wantVal { t.Errorf("SetString(%q).abs = %s, want %s", tt.input, got, tt.wantVal) } - if got := z.neg; got != tt.wantSign { + if got := z.IsNegative(); got != tt.wantSign { t.Errorf("SetString(%q).neg = %v, want %v", tt.input, got, tt.wantSign) } } } -func TestInitiateAbs(t *testing.T) { +func TestAbs(t *testing.T) { tests := []struct { - name string - input *Int - wantNil bool - wantZero bool + x, want string + wantErr bool + wantPanic bool }{ - { - name: "nil input returns new zero Int", - input: nil, - wantNil: false, - wantZero: true, - }, - { - name: "nil abs field gets initialized", - input: &Int{abs: nil, neg: false}, - wantNil: false, - wantZero: true, - }, - { - name: "existing abs field remains unchanged", - input: NewInt(123), - wantNil: false, - wantZero: false, - }, + {"0", "0", false, false}, + {"1", "1", false, false}, + {"-1", "1", false, false}, + {"-2", "2", false, false}, + // Int256 min value overflow: out of range, FromDecimal should error + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "", true, false}, + // Int256 most negative value: FromDecimal succeeds, Abs() panics + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819968", false, true}, } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result := tc.input.initiateAbs() - - if result == nil { - t.Error("initiateAbs returned nil") - return - } + x, err := FromDecimal(tc.x) - if (result.abs == nil) != tc.wantNil { - t.Errorf("abs field nil status = %v, want %v", result.abs == nil, tc.wantNil) - } - - isZero := result.abs != nil && result.abs.IsZero() - if isZero != tc.wantZero { - t.Errorf("IsZero() = %v, want %v", isZero, tc.wantZero) + // Check if FromDecimal returned expected error + if tc.wantErr { + if err == nil { + t.Errorf("FromDecimal(%s) expected error, but got nil", tc.x) } - }) - } -} - -func TestInitiateAbsInOperation(t *testing.T) { - tests := []struct { - name string - setup func() *Int - op func(*Int) *Int - want string - }{ - { - name: "Add with nil receiver", - setup: func() *Int { return nil }, - op: func(z *Int) *Int { return z.Add(NewInt(10), NewInt(20)) }, - want: "30", - }, - { - name: "Add with nil abs field", - setup: func() *Int { return &Int{abs: nil} }, - op: func(z *Int) *Int { return z.Add(NewInt(10), NewInt(20)) }, - want: "30", - }, - { - name: "Sub with nil receiver", - setup: func() *Int { return nil }, - op: func(z *Int) *Int { return z.Sub(NewInt(30), NewInt(20)) }, - want: "10", - }, - { - name: "Sub with nil abs field", - setup: func() *Int { return &Int{abs: nil} }, - op: func(z *Int) *Int { return z.Sub(NewInt(30), NewInt(20)) }, - want: "10", - }, - } + continue + } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - z := tc.setup() - result := tc.op(z) + // If we expect no error, but got one + if err != nil { + t.Errorf("FromDecimal(%s) returned unexpected error: %v", tc.x, err) + continue + } - if result == nil { - t.Error("operation returned nil") - return - } + // At this point, x should not be nil + if x == nil { + t.Errorf("FromDecimal(%s) returned nil without error", tc.x) + continue + } - if got := result.ToString(); got != tc.want { - t.Errorf("got %v, want %v", got, tc.want) + if tc.wantPanic { + uassert.PanicsContains(t, "int256: overflow", func() { + x.Abs() + }) + ufmt.Printf("Abs(%s) correctly panicked with overflow\n", tc.x) + } else { + got := x.Abs() + if got.ToString() != tc.want { + t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) } - }) + } } } diff --git a/contract/p/gnoswap/int256/runtime_metrics_test.gno b/contract/p/gnoswap/int256/runtime_metrics_test.gno deleted file mode 100644 index 4fb9cbd3e..000000000 --- a/contract/p/gnoswap/int256/runtime_metrics_test.gno +++ /dev/null @@ -1,165 +0,0 @@ -package int256 - -import ( - "runtime" - "strconv" - "strings" - "testing" - "time" -) - -// readAllocatorBytes parses runtime.MemStats() into the current allocated bytes. -func readAllocatorBytes(t *testing.T) int64 { - t.Helper() - stats := runtime.MemStats() - if stats == "nil allocator" { - return 0 - } - if !strings.HasPrefix(stats, "Allocator{") || !strings.HasSuffix(stats, "}") { - t.Fatalf("unexpected runtime.MemStats output: %q", stats) - } - body := strings.TrimSuffix(strings.TrimPrefix(stats, "Allocator{"), "}") - parts := strings.Split(body, ", ") - if len(parts) != 2 { - t.Fatalf("unexpected runtime.MemStats content: %q", stats) - } - var ( - bytes int64 - found bool - ) - for _, part := range parts { - fields := strings.Split(part, ":") - if len(fields) != 2 { - t.Fatalf("unexpected runtime.MemStats pair %q", part) - } - if fields[0] == "bytes" { - val, err := strconv.ParseInt(fields[1], 10, 64) - if err != nil { - t.Fatalf("failed to parse bytes from %q: %v", part, err) - } - bytes = val - found = true - break - } - } - if !found { - t.Fatalf("bytes key not found in runtime.MemStats output: %q", stats) - } - return bytes -} - -type MetricResult struct { - Name string - Iterations int - DurationNs int64 - AllocDelta int64 -} - -// runMetric executes fn the given number of iterations while logging elapsed time and allocation deltas. -func runMetric(t *testing.T, name string, iterations int, fn func()) MetricResult { - t.Helper() - - // Skip GC if allocator is nil to avoid panic - stats := runtime.MemStats() - if stats != "nil allocator" { - runtime.GC() - } - beforeBytes := readAllocatorBytes(t) - start := time.Now() - for i := 0; i < iterations; i++ { - fn() - } - elapsed := time.Since(start) - afterBytes := readAllocatorBytes(t) - - return MetricResult{ - Name: name, - Iterations: iterations, - DurationNs: elapsed.Nanoseconds(), - AllocDelta: afterBytes - beforeBytes, - } -} - -func TestPublicFunctionRuntimeMetrics(t *testing.T) { - const iterations = 200 - var results []MetricResult - - // Test data - commonDecimal := "1234567890123456789012345678901234567890" - - tests := []struct { - name string - run func(t *testing.T) MetricResult - }{ - { - name: "FromDecimal", - run: func(t *testing.T) MetricResult { - return runMetric(t, "FromDecimal", iterations, func() { - if _, err := FromDecimal(commonDecimal); err != nil { - t.Fatalf("FromDecimal error: %v", err) - } - }) - }, - }, - { - name: "SetString", - run: func(t *testing.T) MetricResult { - return runMetric(t, "SetString", iterations, func() { - var target Int - if _, err := target.SetString(commonDecimal); err != nil { - t.Fatalf("SetString error: %v", err) - } - }) - }, - }, - { - name: "MustFromDecimal", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MustFromDecimal", iterations, func() { - MustFromDecimal(commonDecimal) - }) - }, - }, - { - name: "MaxInt256", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MaxInt256", iterations, func() { - MaxInt256() - }) - }, - }, - { - name: "MinInt256", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MinInt256", iterations, func() { - MinInt256() - }) - }, - }, - { - name: "ToString", - run: func(t *testing.T) MetricResult { - largeInt := MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") // max int256 - return runMetric(t, "ToString", iterations, func() { - _ = largeInt.ToString() - }) - }, - }, - } - - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - result := test.run(t) - results = append(results, result) - }) - } - - // Print results as markdown table - t.Log("\n## Runtime Metrics Results\n") - t.Log("| Function | Iterations | Duration (ns) | Alloc Delta (bytes) |") - t.Log("|----------|------------|---------------|---------------------|") - for _, result := range results { - t.Logf("| %s | %d | %d | %d |", result.Name, result.Iterations, result.DurationNs, result.AllocDelta) - } -} From 2e14b7b43772d7cb7d1221cc813312934f890a86 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 18:53:58 +0900 Subject: [PATCH 02/28] refactor: create proxy function for backward compatibility --- contract/p/gnoswap/int256/int256.gno | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 6f7a34d52..e2cd71112 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -17,6 +17,46 @@ var ( ErrNegativeNum = errors.New("negative number") ) +// Zero returns a new Int set to 0. +func Zero() *Int { + return &Int{} +} + +// One returns a new Int set to 1. +func One() *Int { + return &Int{1, 0, 0, 0} +} + +// MinInt256 returns a new Int set to the minimum value of int256. +func MinInt256() *Int { + return &Int{0, 0, 0, 0x8000000000000000} +} + +// MaxInt256 returns a new Int set to the maximum value of int256. +func MaxInt256() *Int { + return &Int{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff} +} + +// New returns a new Int set to 0. +func New() *Int { + return &Int{} +} + +// Neq returns true if z != x. +func (z *Int) Neq(x *Int) bool { + return !z.Eq(x) +} + +// Div is an alias for Quo for backward compatibility. +func (z *Int) Div(x, y *Int) *Int { + return z.Quo(x, y) +} + +// IsNeg is an alias for IsNegative for backward compatibility. +func (z *Int) IsNeg() bool { + return z.IsNegative() +} + func NewInt(val int64) *Int { z := &Int{} z.SetInt64(val) From 795ace7774a8290de069995ef3ac7e108a715795 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Fri, 28 Nov 2025 19:01:48 +0900 Subject: [PATCH 03/28] fix: failed arithmetic cased --- contract/p/gnoswap/int256/arithmetic_test.gno | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/contract/p/gnoswap/int256/arithmetic_test.gno b/contract/p/gnoswap/int256/arithmetic_test.gno index ebd1e0e91..239bb8cd6 100644 --- a/contract/p/gnoswap/int256/arithmetic_test.gno +++ b/contract/p/gnoswap/int256/arithmetic_test.gno @@ -3,6 +3,8 @@ package int256 import "testing" func TestAdd(t *testing.T) { + maxIntStr := MaxInt256().ToString() + tests := []struct { x, y, want string }{ @@ -18,8 +20,8 @@ func TestAdd(t *testing.T) { {"-1", "-2", "-3"}, {"-1", "3", "2"}, {"3", "-1", "2"}, - // OVERFLOW - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "0"}, + // BOUNDARY + {maxIntStr, "0", maxIntStr}, } for _, tc := range tests { @@ -167,6 +169,11 @@ func TestAddOverflow(t *testing.T) { } func TestSub(t *testing.T) { + maxInt := MaxInt256() + maxIntStr := maxInt.ToString() + maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() + minIntStr := MinInt256().ToString() + tests := []struct { x, y, want string }{ @@ -175,9 +182,10 @@ func TestSub(t *testing.T) { {"-1", "1", "-2"}, {"1", "-1", "2"}, {"-1", "-1", "0"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {x: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", y: "1", want: "0"}, + {minIntStr, minIntStr, "0"}, + {minIntStr, "0", minIntStr}, + {maxIntStr, maxIntStr, "0"}, + {maxIntMinusOneStr, "-1", maxIntStr}, } for _, tc := range tests { @@ -492,6 +500,10 @@ func TestInt_MulOverflow(t *testing.T) { } func TestDiv(t *testing.T) { + maxInt := MaxInt256() + maxIntStr := maxInt.ToString() + maxIntHalfStr := new(Int).Rsh(maxInt, 1).ToString() + tests := []struct { x, y, expected string }{ @@ -505,7 +517,7 @@ func TestDiv(t *testing.T) { {"-10", "3", "-3"}, {"7", "3", "2"}, {"-7", "3", "-2"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, // Max uint256 / 2 + {maxIntStr, "2", maxIntHalfStr}, // Max int256 / 2 } for _, tt := range tests { @@ -516,7 +528,7 @@ func TestDiv(t *testing.T) { if result.ToString() != tt.expected { t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) } - if result.abs.IsZero() && result.neg { + if result.IsZero() && result.IsNegative() { t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) } }) From 3c6684a32dbcb12baa7c0bcdb1d0b88cb0231fd4 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 19:12:59 +0900 Subject: [PATCH 04/28] feat(p/int256): add FromUint256 and AbsUint256 conversion functions - Add FromUint256 to convert uint256 to int256 with overflow check - Add AbsUint256 to return absolute value as uint256 - Update callers to use AbsUint256 instead of Abs where uint256 is needed --- .../p/gnoswap/gnsmath/sqrt_price_math.gno | 8 ++--- .../sqrt_price_math_fuzz_params_test.gno | 6 ++-- contract/p/gnoswap/gnsmath/swap_math.gno | 2 +- contract/p/gnoswap/gnsmath/swap_math_test.gno | 10 +++---- contract/p/gnoswap/int256/conversion.gno | 30 +++++++++++++++++++ .../r/gnoswap/common/liquidity_amounts.gno | 2 +- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/contract/p/gnoswap/gnsmath/sqrt_price_math.gno b/contract/p/gnoswap/gnsmath/sqrt_price_math.gno index 553084682..c700a6cd7 100644 --- a/contract/p/gnoswap/gnsmath/sqrt_price_math.gno +++ b/contract/p/gnoswap/gnsmath/sqrt_price_math.gno @@ -261,7 +261,7 @@ func GetAmount0Delta( } if liquidity.IsNeg() { - u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), false) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount0DeltaOverflow) @@ -271,7 +271,7 @@ func GetAmount0Delta( return i256.Zero().Neg(i256.FromUint256(u)) } - u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), true) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount0DeltaOverflow) @@ -293,7 +293,7 @@ func GetAmount1Delta( } if liquidity.IsNeg() { - u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), false) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount1DeltaOverflow) @@ -303,7 +303,7 @@ func GetAmount1Delta( return i256.Zero().Neg(i256.FromUint256(u)) } - u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), true) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount1DeltaOverflow) diff --git a/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno b/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno index 4b031b21b..eaed578e2 100644 --- a/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno +++ b/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno @@ -31,8 +31,8 @@ func (p *getAmount0DeltaParams) IsValid() bool { } // Check: Lsh overflow - liquidity << 96 must not overflow - // This happens when liquidity.Abs() > 2^160 (because 2^160 << 96 = 2^256 which overflows) - liquidityAbs := liquidity.Abs() + // This happens when liquidity.AbsUint256() > 2^160 (because 2^160 << 96 = 2^256 which overflows) + liquidityAbs := liquidity.AbsUint256() max160 := u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 160), u256.One()) if liquidityAbs.Gt(max160) { return false @@ -116,7 +116,7 @@ func (p *getAmount1DeltaParams) IsValid() bool { sqrtB := u256.MustFromDecimal(p.sqrtRatioBX96) liquidity := i256.MustFromDecimal(p.liquidity) - liquidityAbs := liquidity.Abs() + liquidityAbs := liquidity.AbsUint256() // getAmount1DeltaHelper will swap if sqrtA > sqrtB if sqrtA.Gt(sqrtB) { diff --git a/contract/p/gnoswap/gnsmath/swap_math.gno b/contract/p/gnoswap/gnsmath/swap_math.gno index e4d3508a1..ad9799d44 100644 --- a/contract/p/gnoswap/gnsmath/swap_math.gno +++ b/contract/p/gnoswap/gnsmath/swap_math.gno @@ -46,7 +46,7 @@ func SwapMathComputeSwapStep( // NEGATIVE == EXACT_OUT => Estimated AmountIn exactIn := !amountRemaining.IsNeg() - amountRemainingAbs := amountRemaining.Abs() + amountRemainingAbs := amountRemaining.AbsUint256() feeRateInPips := u256.NewUint(feePips) withoutFeeRateInPips := u256.NewUint(denominator - feePips) diff --git a/contract/p/gnoswap/gnsmath/swap_math_test.gno b/contract/p/gnoswap/gnsmath/swap_math_test.gno index 7023d24e8..75ecb87e3 100644 --- a/contract/p/gnoswap/gnsmath/swap_math_test.gno +++ b/contract/p/gnoswap/gnsmath/swap_math_test.gno @@ -371,7 +371,7 @@ func TestSwapMath_FeeConsistency(t *testing.T) { if is_partial_fill && !test.amount.IsNeg() { // exactIn mode // For partial fills in exactIn: fee = amountRemaining - amountIn - expected_fee := new(u256.Uint).Sub(test.amount.Abs(), amount_in_num) + expected_fee := new(u256.Uint).Sub(test.amount.AbsUint256(), amount_in_num) uassert.True(t, fee_amount_num.Eq(expected_fee), ufmt.Sprintf("Partial fill fee should be %s, got %s", expected_fee.ToString(), fee_amount_num.ToString())) } else { @@ -659,9 +659,9 @@ func TestSwapMath_HighPrecision(t *testing.T) { // For exact input, total consumption should not exceed input if !test.amount.IsNeg() { total := new(u256.Uint).Add(amountInNum, feeAmountNum) - uassert.True(t, total.Lte(test.amount.Abs()), + uassert.True(t, total.Lte(test.amount.AbsUint256()), ufmt.Sprintf("Total consumption %s should not exceed input %s", - total.ToString(), test.amount.Abs().ToString())) + total.ToString(), test.amount.AbsUint256().ToString())) } }) } @@ -724,7 +724,7 @@ func TestSwapMath_ExtremeFees(t *testing.T) { } // Fee should never exceed original amount - uassert.True(t, feeAmountNum.Lte(amount.Abs()), + uassert.True(t, feeAmountNum.Lte(amount.AbsUint256()), "Fee should not exceed input amount") }) } @@ -803,7 +803,7 @@ func TestSwapMath_ConsistencyChecks(t *testing.T) { // Conservation check: for exact input if !test.amount.IsNeg() { total := new(u256.Uint).Add(amountInNum, feeAmountNum) - uassert.True(t, total.Lte(test.amount.Abs()), + uassert.True(t, total.Lte(test.amount.AbsUint256()), "amountIn + feeAmount should not exceed input") } }) diff --git a/contract/p/gnoswap/int256/conversion.gno b/contract/p/gnoswap/int256/conversion.gno index 2e1c2c6d8..082241731 100644 --- a/contract/p/gnoswap/int256/conversion.gno +++ b/contract/p/gnoswap/int256/conversion.gno @@ -4,6 +4,8 @@ import ( "errors" "math/bits" "strconv" + + u256 "gno.land/p/gnoswap/uint256" ) const ( @@ -211,3 +213,31 @@ func (z *Int) fromDecimal(bs string) error { } return nil } + +// FromUint256 converts a uint256 to int256. +// Panics if the uint256 value is greater than MaxInt256 (2^255 - 1). +func FromUint256(x *u256.Uint) *Int { + // Check overflow: if MSB of x[3] is set, value > MaxInt256 + if x[3] > 0x7fffffffffffffff { + panic("int256: overflow - uint256 value exceeds MaxInt256") + } + z := &Int{} + z[0] = x[0] + z[1] = x[1] + z[2] = x[2] + z[3] = x[3] + return z +} + +// AbsUint256 returns the absolute value of z as a uint256. +// Panics if z is MinInt256 (which cannot be represented as a positive int256). +func (z *Int) AbsUint256() *u256.Uint { + if z.IsNegative() { + if z.IsMinI256() { + panic("int256: overflow") + } + neg := new(Int).Neg(z) + return &u256.Uint{neg[0], neg[1], neg[2], neg[3]} + } + return &u256.Uint{z[0], z[1], z[2], z[3]} +} diff --git a/contract/r/gnoswap/common/liquidity_amounts.gno b/contract/r/gnoswap/common/liquidity_amounts.gno index 2b6db66e8..e3bb4432a 100644 --- a/contract/r/gnoswap/common/liquidity_amounts.gno +++ b/contract/r/gnoswap/common/liquidity_amounts.gno @@ -340,7 +340,7 @@ func LiquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint { panic("liquidity_math: x or y is nil") } - yAbs := y.Abs() + yAbs := y.AbsUint256() // Subtract or add based on the sign of y if y.Lt(i256.Zero()) { From 8bd90067067d2ec143da6ee9efd8205adcd7316f Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Fri, 28 Nov 2025 19:19:11 +0900 Subject: [PATCH 05/28] fix: Add `NilToZero` --- contract/r/gnoswap/pool/v1/swap.gno | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contract/r/gnoswap/pool/v1/swap.gno b/contract/r/gnoswap/pool/v1/swap.gno index 6a2fb66f1..0938889dc 100644 --- a/contract/r/gnoswap/pool/v1/swap.gno +++ b/contract/r/gnoswap/pool/v1/swap.gno @@ -233,17 +233,17 @@ func (i *poolV1) Swap( // receive token0 from swap callback // send token1 to recipient (output) if result.Amount1.IsNeg() { - i.safeSwapCallback(pool, token0Path, result.Amount0.Abs(), zeroForOne, swapCallback) + i.safeSwapCallback(pool, token0Path, result.Amount0.AbsUint256(), zeroForOne, swapCallback) - i.safeTransfer(pool, recipient, token1Path, result.Amount1.Abs(), false) + i.safeTransfer(pool, recipient, token1Path, result.Amount1.AbsUint256(), false) } } else { // receive token1 from swap callback // send token0 to recipient (output) if result.Amount0.IsNeg() { - i.safeSwapCallback(pool, token1Path, result.Amount1.Abs(), zeroForOne, swapCallback) + i.safeSwapCallback(pool, token1Path, result.Amount1.AbsUint256(), zeroForOne, swapCallback) - i.safeTransfer(pool, recipient, token0Path, result.Amount0.Abs(), true) + i.safeTransfer(pool, recipient, token0Path, result.Amount0.AbsUint256(), true) } } @@ -331,11 +331,11 @@ func (i *poolV1) DrySwap( } if zeroForOne { - if pool.BalanceToken1().Lt(result.Amount1.Abs()) { + if pool.BalanceToken1().Lt(result.Amount1.AbsUint256()) { return "0", "0", false } } else { - if pool.BalanceToken0().Lt(result.Amount0.Abs()) { + if pool.BalanceToken0().Lt(result.Amount0.AbsUint256()) { return "0", "0", false } } From ca496f55d3ea32c8e8c6b95be82ffc825070e6f2 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Fri, 28 Nov 2025 19:20:44 +0900 Subject: [PATCH 06/28] fix --- contract/p/gnoswap/int256/int256.gno | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index e2cd71112..b24ccade3 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -640,3 +640,10 @@ func (z *Int) WriteToArray32(dest *[32]byte) { dest[31-i] = byte(z[i/8] >> uint64(8*(i%8))) } } + +func (z *Int) NilToZero() *Int { + if z == nil { + return Zero() + } + return z +} From bc4e95b5ae57a36303464177f3cf655a2e19df7d Mon Sep 17 00:00:00 2001 From: jinoosss <112360739+jinoosss@users.noreply.github.com> Date: Fri, 28 Nov 2025 19:22:04 +0900 Subject: [PATCH 07/28] test: add missing tests --- contract/p/gnoswap/int256/absolute_test.gno | 184 ++++++ contract/p/gnoswap/int256/bitwise_test.gno | 204 +++++++ contract/p/gnoswap/int256/cmp_test.gno | 316 ++++++++++ contract/p/gnoswap/int256/conversion_test.gno | 540 ++++++++++++++++++ .../p/gnoswap/int256/runtime_metrics_test.gno | 165 ++++++ 5 files changed, 1409 insertions(+) create mode 100644 contract/p/gnoswap/int256/absolute_test.gno create mode 100644 contract/p/gnoswap/int256/bitwise_test.gno create mode 100644 contract/p/gnoswap/int256/cmp_test.gno create mode 100644 contract/p/gnoswap/int256/conversion_test.gno create mode 100644 contract/p/gnoswap/int256/runtime_metrics_test.gno diff --git a/contract/p/gnoswap/int256/absolute_test.gno b/contract/p/gnoswap/int256/absolute_test.gno new file mode 100644 index 000000000..2162d626d --- /dev/null +++ b/contract/p/gnoswap/int256/absolute_test.gno @@ -0,0 +1,184 @@ +package int256 + +import ( + "testing" + + "gno.land/p/gnoswap/uint256" +) + +func TestAbs(t *testing.T) { + tests := []struct { + x, want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "1"}, + {"-2", "2"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.Abs() + + if got.ToString() != tc.want { + t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) + } + } +} + +func TestAbsGt(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "0", "false"}, + {"1", "0", "true"}, + {"-1", "0", "true"}, + {"-1", "1", "false"}, + {"-2", "1", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.AbsGt(y) + + if got != (tc.want == "true") { + t.Errorf("AbsGt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestAbsLt(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "0", "false"}, + {"1", "0", "false"}, + {"-1", "0", "false"}, + {"-1", "1", "false"}, + {"-2", "1", "false"}, + {"-5", "10", "true"}, + {"31330", "31337", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "false"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "false"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.AbsLt(y) + + if got != (tc.want == "true") { + t.Errorf("AbsLt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestInt_AbsOverflow(t *testing.T) { + tests := []struct { + name string + x *Int + wantResult string + wantOverflow bool + }{ + { + name: "zero", + x: Zero(), + wantResult: "0", + wantOverflow: false, + }, + { + name: "positive number", + x: NewInt(100), + wantResult: "100", + wantOverflow: false, + }, + { + name: "negative number", + x: NewInt(-100), + wantResult: "100", + wantOverflow: false, + }, + { + name: "max_int256", + x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967"), + wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + wantOverflow: false, + }, + { + name: "min_int256", + x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968"), + wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + wantOverflow: true, + }, + { + name: "min_int256 + 1", + x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819967"), + wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + wantOverflow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := new(Int) + gotResult, gotOverflow := z.AbsOverflow(tt.x) + + if gotOverflow != tt.wantOverflow { + t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) + return + } + + if gotResult == nil { + t.Error("unexpected nil result") + return + } + + if gotResult.ToString() != tt.wantResult { + t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) + } + + // abs value must be GTE 0 (if there is no overflow) + if !tt.wantOverflow && gotResult.neg && !gotResult.IsZero() { + t.Error("absolute value cannot be negative") + } + + // original value must not be modified + originalValue := tt.x.ToString() + if tt.x.ToString() != originalValue { + t.Errorf("original value was modified: got %v, want %v", + tt.x.ToString(), originalValue) + } + }) + } +} diff --git a/contract/p/gnoswap/int256/bitwise_test.gno b/contract/p/gnoswap/int256/bitwise_test.gno new file mode 100644 index 000000000..282de5b03 --- /dev/null +++ b/contract/p/gnoswap/int256/bitwise_test.gno @@ -0,0 +1,204 @@ +package int256 + +import ( + "testing" + + "gno.land/p/nt/uassert" + + "gno.land/p/gnoswap/uint256" +) + +func TestOr(t *testing.T) { + tests := []struct { + name string + x, y, want Int + }{ + { + name: "all zeroes", + x: Int{abs: uint256.Zero(), neg: false}, + y: Int{abs: uint256.Zero(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "all ones", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + }, + { + name: "mixed", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.NewUint(0), neg: false}, + want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := New() + got.Or(&tc.x, &tc.y) + + if got.Neq(&tc.want) { + t.Errorf("Or(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + } + }) + } +} + +func TestAnd(t *testing.T) { + tests := []struct { + name string + x, y, want Int + }{ + { + name: "all zeroes", + x: Int{abs: uint256.Zero(), neg: false}, + y: Int{abs: uint256.Zero(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "all ones", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + }, + { + name: "mixed", + x: Int{abs: uint256.Zero(), neg: false}, + y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "mixed 2", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.Zero(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "mixed 3", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.Zero(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "one operand zero", + x: Int{abs: uint256.Zero(), neg: false}, + y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + want: Int{abs: uint256.Zero(), neg: false}, + }, + { + name: "one operand all ones", + x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := New() + got.And(&tc.x, &tc.y) + + if got.Neq(&tc.want) { + t.Errorf("And(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + } + }) + } +} + +func TestRsh(t *testing.T) { + tests := []struct { + x string + n uint + want string + }{ + {"1024", 0, "1024"}, + {"1024", 1, "512"}, + {"1024", 2, "256"}, + {"1024", 10, "1"}, + {"1024", 11, "0"}, + {"18446744073709551615", 0, "18446744073709551615"}, + {"18446744073709551615", 1, "9223372036854775807"}, + {"18446744073709551615", 62, "3"}, + {"18446744073709551615", 63, "1"}, + {"18446744073709551615", 64, "0"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 0, "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 1, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 128, "340282366920938463463374607431768211455"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 255, "1"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 256, "0"}, + {"-1024", 0, "-1024"}, + {"-1024", 1, "-512"}, + {"-1024", 2, "-256"}, + {"-1024", 10, "-1"}, + {"-1024", 10, "-1"}, + {"-9223372036854775808", 0, "-9223372036854775808"}, + {"-9223372036854775808", 1, "-4611686018427387904"}, + {"-9223372036854775808", 62, "-2"}, + {"-9223372036854775808", 63, "-1"}, + {"-9223372036854775808", 64, "-1"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 0, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 1, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 253, "-4"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 254, "-2"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "-1"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 256, "-1"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Rsh(x, tc.n) + + if got.ToString() != tc.want { + t.Errorf("Rsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) + } + } +} + +func TestLsh(t *testing.T) { + tests := []struct { + x string + n uint + want string + expectPanic bool + }{ + {"1", 0, "1", false}, + {"1", 1, "2", false}, + {"1", 2, "4", false}, + {"2", 0, "2", false}, + {"2", 1, "4", false}, + {"2", 2, "8", false}, + {"-2", 0, "-2", false}, + {"-4", 0, "-4", false}, + {"-8", 0, "-8", false}, + {"-1", 255, "-57896044618658097711785492504343953926634992332820282019728792003956564819968", false}, + {"-1", 256, "0", true}, + {"-2", 255, "0", true}, + {"-4", 254, "0", true}, + {"-8", 253, "0", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := New() + if tc.expectPanic { + uassert.PanicsWithMessage(t, "Lsh: overflow", func() { + got.Lsh(x, tc.n) + }) + } else { + got.Lsh(x, tc.n) + uassert.Equal(t, tc.want, got.ToString()) + } + } +} diff --git a/contract/p/gnoswap/int256/cmp_test.gno b/contract/p/gnoswap/int256/cmp_test.gno new file mode 100644 index 000000000..b6bb6a8ee --- /dev/null +++ b/contract/p/gnoswap/int256/cmp_test.gno @@ -0,0 +1,316 @@ +package int256 + +import ( + "testing" +) + +func TestEq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", true}, + {"0", "1", false}, + {"1", "0", false}, + {"-1", "0", false}, + {"0", "-1", false}, + {"1", "1", true}, + {"-1", "-1", true}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Eq(y) + if got != tc.want { + t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestNeq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", true}, + {"-1", "0", true}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Neq(y) + if got != tc.want { + t.Errorf("Neq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestCmp(t *testing.T) { + tests := []struct { + x, y string + want int + }{ + {"0", "0", 0}, + {"0", "1", -1}, + {"1", "0", 1}, + {"-1", "0", -1}, + {"0", "-1", 1}, + {"1", "1", 0}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Cmp(y) + if got != tc.want { + t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", true}, + {"-0", true}, + {"1", false}, + {"-1", false}, + {"10", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsZero() + if got != tc.want { + t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestIsNeg(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", false}, + {"-0", true}, + {"1", false}, + {"-1", true}, + {"10", false}, + {"-10", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsNeg() + if got != tc.want { + t.Errorf("IsNeg(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestLt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", false}, + {"-1", "0", true}, + {"0", "-1", false}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Lt(y) + if got != tc.want { + t.Errorf("Lt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestGt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", false}, + {"1", "0", true}, + {"-1", "0", false}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Gt(y) + if got != tc.want { + t.Errorf("Gt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestClone(t *testing.T) { + tests := []struct { + x string + }{ + {"0"}, + {"-0"}, + {"1"}, + {"-1"}, + {"10"}, + {"-10"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y := x.Clone() + + if x.Cmp(y) != 0 { + t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) + } + } +} + +func TestNilChecks(t *testing.T) { + validInt := NewInt(123) + + tests := []struct { + name string + fn func() + wantPanic string + }{ + { + name: "Eq with nil", + fn: func() { validInt.Eq(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Neq with nil", + fn: func() { validInt.Neq(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Cmp with nil", + fn: func() { validInt.Cmp(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Lt with nil", + fn: func() { validInt.Lt(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Gt with nil", + fn: func() { validInt.Gt(nil) }, + wantPanic: "int256: comparing with nil", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.Errorf("%s: expected panic but got none", tt.name) + return + } + if r.(string) != tt.wantPanic { + t.Errorf("%s: got panic %v, want %v", tt.name, r, tt.wantPanic) + } + }() + + tt.fn() + }) + } +} diff --git a/contract/p/gnoswap/int256/conversion_test.gno b/contract/p/gnoswap/int256/conversion_test.gno new file mode 100644 index 000000000..aae2b8226 --- /dev/null +++ b/contract/p/gnoswap/int256/conversion_test.gno @@ -0,0 +1,540 @@ +package int256 + +import ( + "testing" + + "gno.land/p/nt/ufmt" + + "gno.land/p/gnoswap/uint256" +) + +func TestSetInt64(t *testing.T) { + tests := []struct { + x int64 + want string + }{ + {0, "0"}, + {1, "1"}, + {-1, "-1"}, + {9223372036854775807, "9223372036854775807"}, + {-9223372036854775808, "-9223372036854775808"}, + } + + for _, tc := range tests { + var z Int + z.SetInt64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSetInt64MinValueOverflow(t *testing.T) { + const minInt64 = -9223372036854775808 // -2^63 + const maxInt64 = 9223372036854775807 // 2^63 - 1 + + tests := []struct { + name string + x int64 + want string + }{ + { + name: "MinInt64 should not cause overflow", + x: minInt64, + want: "-9223372036854775808", + }, + { + name: "MaxInt64 works correctly", + x: maxInt64, + want: "9223372036854775807", + }, + { + name: "MinInt64 + 1", + x: minInt64 + 1, + want: "-9223372036854775807", + }, + { + name: "Negative one", + x: -1, + want: "-1", + }, + { + name: "Zero", + x: 0, + want: "0", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var z Int + z.SetInt64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + } + + // Verify the internal representation is correct + if tc.x < 0 { + if !z.neg { + t.Errorf("SetInt64(%d): expected neg=true, got neg=false", tc.x) + } + // Check magnitude for MinInt64 + if tc.x == minInt64 { + expectedMag := uint64(1 << 63) // 2^63 + gotMag := z.abs.Uint64() + if gotMag != expectedMag { + t.Errorf("SetInt64(%d): magnitude = %d, want %d", tc.x, gotMag, expectedMag) + } + } + } else if tc.x > 0 { + if z.neg { + t.Errorf("SetInt64(%d): expected neg=false, got neg=true", tc.x) + } + } else { // tc.x == 0 + if z.neg { + t.Errorf("SetInt64(0): expected neg=false (no -0), got neg=true") + } + } + }) + } +} + +func TestSetUint64(t *testing.T) { + tests := []struct { + x uint64 + want string + }{ + {0, "0"}, + {1, "1"}, + } + + for _, tc := range tests { + var z Int + z.SetUint64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestUint64(t *testing.T) { + tests := []struct { + x string + want uint64 + }{ + {"0", 0}, + {"1", 1}, + {"9223372036854775807", 9223372036854775807}, + {"9223372036854775808", 9223372036854775808}, + {"18446744073709551615", 18446744073709551615}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + {"-1", 1}, + {"-18446744073709551615", 18446744073709551615}, + {"-18446744073709551616", 0}, + {"-18446744073709551617", 1}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Uint64() + if got != tc.want { + t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestInt64(t *testing.T) { + tests := []struct { + x string + want int64 + }{ + {"0", 0}, + {"1", 1}, + {"-1", -1}, + {"9223372036854775807", 9223372036854775807}, + {"-9223372036854775808", -9223372036854775808}, + {"9223372036854775808", -9223372036854775808}, + {"-9223372036854775809", 9223372036854775807}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + {"18446744073709551615", -1}, + {"-18446744073709551615", 1}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Int64() + if got != tc.want { + t.Errorf("Int64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestInt64EdgeCases(t *testing.T) { + const minInt64 = -9223372036854775808 // -2^63 + const maxInt64 = 9223372036854775807 // 2^63 - 1 + + tests := []struct { + name string + setupInt func() *Int + want int64 + description string + }{ + { + name: "MinInt64 from SetInt64", + setupInt: func() *Int { + z := new(Int) + return z.SetInt64(minInt64) + }, + want: minInt64, + description: "SetInt64(MinInt64) should round-trip correctly", + }, + { + name: "MaxInt64 from SetInt64", + setupInt: func() *Int { + z := new(Int) + return z.SetInt64(maxInt64) + }, + want: maxInt64, + description: "SetInt64(MaxInt64) should round-trip correctly", + }, + { + name: "Magnitude 2^63 with negative sign", + setupInt: func() *Int { + // Create Int with magnitude = 2^63 and neg = true + z := new(Int) + z.abs = uint256.NewUint(1 << 63) + z.neg = true + return z + }, + want: minInt64, + description: "Magnitude 2^63 with neg=true should return MinInt64", + }, + { + name: "Magnitude 2^63 with positive sign", + setupInt: func() *Int { + // Create Int with magnitude = 2^63 and neg = false + z := new(Int) + z.abs = uint256.NewUint(1 << 63) + z.neg = false + return z + }, + want: minInt64, // Wraps around due to two's complement + description: "Magnitude 2^63 with neg=false wraps to MinInt64", + }, + { + name: "Large positive value wrapping", + setupInt: func() *Int { + // 2^64 - 1 (max uint64) + z := new(Int) + z.abs = uint256.NewUint(18446744073709551615) + z.neg = false + return z + }, + want: -1, + description: "Max uint64 wraps to -1 in int64", + }, + { + name: "Negative large value", + setupInt: func() *Int { + // -(2^64 - 1) + z := new(Int) + z.abs = uint256.NewUint(18446744073709551615) + z.neg = true + return z + }, + want: 1, + description: "-(Max uint64) becomes 1 due to two's complement", + }, + { + name: "Zero", + setupInt: func() *Int { + return Zero() + }, + want: 0, + description: "Zero should return 0", + }, + { + name: "Negative zero prevention", + setupInt: func() *Int { + z := new(Int) + z.abs = uint256.NewUint(0) + z.neg = true // This should be normalized to false + return z + }, + want: 0, + description: "Negative zero should return 0", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + z := tc.setupInt() + got := z.Int64() + + if got != tc.want { + t.Errorf("%s: got %d, want %d", tc.description, got, tc.want) + } + }) + } +} + +// TestInt64RoundTrip verifies that SetInt64 and Int64 work correctly together +func TestInt64RoundTrip(t *testing.T) { + // Test all interesting int64 values + values := []int64{ + 0, 1, -1, + 127, -128, // int8 boundaries + 32767, -32768, // int16 boundaries + 2147483647, -2147483648, // int32 boundaries + 9223372036854775807, -9223372036854775808, // int64 boundaries (MaxInt64, MinInt64) + 1234567890, -1234567890, + } + + for _, v := range values { + t.Run(ufmt.Sprintf("RoundTrip_%d", v), func(t *testing.T) { + z := new(Int) + z.SetInt64(v) + + got := z.Int64() + if got != v { + t.Errorf("Round trip failed: SetInt64(%d).Int64() = %d", v, got) + } + }) + } +} + +func TestNeg(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "-1"}, + {"-1", "1"}, + {"9223372036854775807", "-9223372036854775807"}, + {"-18446744073709551615", "18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Neg(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestInt_NegOverflow(t *testing.T) { + maxInt256 := MaxInt256() + minInt256 := MinInt256() + + negMaxInt256 := New().Neg(maxInt256) + + tests := []struct { + name string + x *Int + wantResult string + wantOverflow bool + }{ + { + name: "negate zero", + x: Zero(), + wantResult: "0", + wantOverflow: false, + }, + { + name: "negate positive", + x: NewInt(100), + wantResult: "-100", + wantOverflow: false, + }, + { + name: "negate negative", + x: NewInt(-100), + wantResult: "100", + wantOverflow: false, + }, + { + name: "negate max_int256", + x: maxInt256, + wantResult: negMaxInt256.ToString(), + wantOverflow: false, + }, + { + name: "negate min_int256", + x: minInt256, + wantResult: minInt256.ToString(), // must preserve the original value + wantOverflow: true, + }, + { + name: "negate (min_int256 + 1)", + x: new(Int).Add(minInt256, One()), + wantResult: new(Int).Sub(maxInt256, Zero()).ToString(), + wantOverflow: false, + }, + { + name: "negate (max_int256 - 1)", + x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819966"), // max_int256 - 1 + wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819966", + wantOverflow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := new(Int) + gotResult, gotOverflow := z.NegOverflow(tt.x) + + if gotOverflow != tt.wantOverflow { + t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) + return + } + + if gotResult == nil { + t.Error("unexpected nil result") + return + } + + if gotResult.ToString() != tt.wantResult { + // use almost equal comparison to handle the precision issue + diff := new(Int).Sub(gotResult, MustFromDecimal(tt.wantResult)) + if diff.Abs().Cmp(uint256.NewUint(1)) > 0 { + t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) + } + } + + // double negation test (only if there is no overflow) + if !tt.wantOverflow { + doubleNegResult, doubleNegOverflow := new(Int).NegOverflow(gotResult) + if doubleNegOverflow { + t.Error("unexpected overflow in double negation") + } + if doubleNegResult.ToString() != tt.x.ToString() { + t.Errorf("double negation result = %v, want %v", + doubleNegResult.ToString(), tt.x.ToString()) + } + } + }) + } +} + +func TestSet(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "-1"}, + {"9223372036854775807", "9223372036854775807"}, + {"-18446744073709551615", "-18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Set(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("set(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSetUint256(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "1"}, + {"9223372036854775807", "9223372036854775807"}, + {"18446744073709551615", "18446744073709551615"}, + } + + for _, tc := range tests { + got := New() + + z := uint256.MustFromDecimal(tc.x) + got.SetUint256(z) + + if got.ToString() != tc.want { + t.Errorf("SetUint256(%s) = %s, want %s", tc.x, got.ToString(), tc.want) + } + } +} + +func TestToString(t *testing.T) { + tests := []struct { + name string + setup func() *Int + expected string + }{ + { + name: "Zero from subtraction", + setup: func() *Int { + minusThree := MustFromDecimal("-3") + three := MustFromDecimal("3") + return Zero().Add(minusThree, three) + }, + expected: "0", + }, + { + name: "Zero from right shift", + setup: func() *Int { + return Zero().Rsh(One(), 1234) + }, + expected: "0", + }, + { + name: "Positive number", + setup: func() *Int { + return MustFromDecimal("42") + }, + expected: "42", + }, + { + name: "Negative number", + setup: func() *Int { + return MustFromDecimal("-42") + }, + expected: "-42", + }, + { + name: "Large positive number", + setup: func() *Int { + return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + { + name: "Large negative number", + setup: func() *Int { + return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := tt.setup() + result := z.ToString() + if result != tt.expected { + t.Errorf("ToString() = %s, want %s", result, tt.expected) + } + }) + } +} diff --git a/contract/p/gnoswap/int256/runtime_metrics_test.gno b/contract/p/gnoswap/int256/runtime_metrics_test.gno new file mode 100644 index 000000000..4fb9cbd3e --- /dev/null +++ b/contract/p/gnoswap/int256/runtime_metrics_test.gno @@ -0,0 +1,165 @@ +package int256 + +import ( + "runtime" + "strconv" + "strings" + "testing" + "time" +) + +// readAllocatorBytes parses runtime.MemStats() into the current allocated bytes. +func readAllocatorBytes(t *testing.T) int64 { + t.Helper() + stats := runtime.MemStats() + if stats == "nil allocator" { + return 0 + } + if !strings.HasPrefix(stats, "Allocator{") || !strings.HasSuffix(stats, "}") { + t.Fatalf("unexpected runtime.MemStats output: %q", stats) + } + body := strings.TrimSuffix(strings.TrimPrefix(stats, "Allocator{"), "}") + parts := strings.Split(body, ", ") + if len(parts) != 2 { + t.Fatalf("unexpected runtime.MemStats content: %q", stats) + } + var ( + bytes int64 + found bool + ) + for _, part := range parts { + fields := strings.Split(part, ":") + if len(fields) != 2 { + t.Fatalf("unexpected runtime.MemStats pair %q", part) + } + if fields[0] == "bytes" { + val, err := strconv.ParseInt(fields[1], 10, 64) + if err != nil { + t.Fatalf("failed to parse bytes from %q: %v", part, err) + } + bytes = val + found = true + break + } + } + if !found { + t.Fatalf("bytes key not found in runtime.MemStats output: %q", stats) + } + return bytes +} + +type MetricResult struct { + Name string + Iterations int + DurationNs int64 + AllocDelta int64 +} + +// runMetric executes fn the given number of iterations while logging elapsed time and allocation deltas. +func runMetric(t *testing.T, name string, iterations int, fn func()) MetricResult { + t.Helper() + + // Skip GC if allocator is nil to avoid panic + stats := runtime.MemStats() + if stats != "nil allocator" { + runtime.GC() + } + beforeBytes := readAllocatorBytes(t) + start := time.Now() + for i := 0; i < iterations; i++ { + fn() + } + elapsed := time.Since(start) + afterBytes := readAllocatorBytes(t) + + return MetricResult{ + Name: name, + Iterations: iterations, + DurationNs: elapsed.Nanoseconds(), + AllocDelta: afterBytes - beforeBytes, + } +} + +func TestPublicFunctionRuntimeMetrics(t *testing.T) { + const iterations = 200 + var results []MetricResult + + // Test data + commonDecimal := "1234567890123456789012345678901234567890" + + tests := []struct { + name string + run func(t *testing.T) MetricResult + }{ + { + name: "FromDecimal", + run: func(t *testing.T) MetricResult { + return runMetric(t, "FromDecimal", iterations, func() { + if _, err := FromDecimal(commonDecimal); err != nil { + t.Fatalf("FromDecimal error: %v", err) + } + }) + }, + }, + { + name: "SetString", + run: func(t *testing.T) MetricResult { + return runMetric(t, "SetString", iterations, func() { + var target Int + if _, err := target.SetString(commonDecimal); err != nil { + t.Fatalf("SetString error: %v", err) + } + }) + }, + }, + { + name: "MustFromDecimal", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MustFromDecimal", iterations, func() { + MustFromDecimal(commonDecimal) + }) + }, + }, + { + name: "MaxInt256", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MaxInt256", iterations, func() { + MaxInt256() + }) + }, + }, + { + name: "MinInt256", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MinInt256", iterations, func() { + MinInt256() + }) + }, + }, + { + name: "ToString", + run: func(t *testing.T) MetricResult { + largeInt := MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") // max int256 + return runMetric(t, "ToString", iterations, func() { + _ = largeInt.ToString() + }) + }, + }, + } + + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + result := test.run(t) + results = append(results, result) + }) + } + + // Print results as markdown table + t.Log("\n## Runtime Metrics Results\n") + t.Log("| Function | Iterations | Duration (ns) | Alloc Delta (bytes) |") + t.Log("|----------|------------|---------------|---------------------|") + for _, result := range results { + t.Logf("| %s | %d | %d | %d |", result.Name, result.Iterations, result.DurationNs, result.AllocDelta) + } +} From 63fa769c787d8c09b317d1bd6ad81632fe24db49 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 19:23:53 +0900 Subject: [PATCH 08/28] refactor(p/int256): change Abs() to return *u256.Uint directly - Remove AbsUint256() function from conversion.gno - Modify Abs() in int256.gno to return *u256.Uint instead of *Int - Update all callers from AbsUint256() to Abs() --- contract/p/gnoswap/gnsmath/sqrt_price_math.gno | 8 ++++---- .../gnsmath/sqrt_price_math_fuzz_params_test.gno | 6 +++--- contract/p/gnoswap/gnsmath/swap_math.gno | 2 +- contract/p/gnoswap/gnsmath/swap_math_test.gno | 10 +++++----- contract/p/gnoswap/int256/conversion.gno | 12 ------------ contract/p/gnoswap/int256/int256.gno | 10 ++++++---- contract/r/gnoswap/common/liquidity_amounts.gno | 2 +- contract/r/gnoswap/pool/v1/swap.gno | 12 ++++++------ 8 files changed, 26 insertions(+), 36 deletions(-) diff --git a/contract/p/gnoswap/gnsmath/sqrt_price_math.gno b/contract/p/gnoswap/gnsmath/sqrt_price_math.gno index c700a6cd7..553084682 100644 --- a/contract/p/gnoswap/gnsmath/sqrt_price_math.gno +++ b/contract/p/gnoswap/gnsmath/sqrt_price_math.gno @@ -261,7 +261,7 @@ func GetAmount0Delta( } if liquidity.IsNeg() { - u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), false) + u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount0DeltaOverflow) @@ -271,7 +271,7 @@ func GetAmount0Delta( return i256.Zero().Neg(i256.FromUint256(u)) } - u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), true) + u := getAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount0DeltaOverflow) @@ -293,7 +293,7 @@ func GetAmount1Delta( } if liquidity.IsNeg() { - u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), false) + u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount1DeltaOverflow) @@ -303,7 +303,7 @@ func GetAmount1Delta( return i256.Zero().Neg(i256.FromUint256(u)) } - u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.AbsUint256(), true) + u := getAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) if u.Gt(maxInt256) { // if u > (2**255 - 1), cannot cast to int256 panic(errAmount1DeltaOverflow) diff --git a/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno b/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno index eaed578e2..4b031b21b 100644 --- a/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno +++ b/contract/p/gnoswap/gnsmath/sqrt_price_math_fuzz_params_test.gno @@ -31,8 +31,8 @@ func (p *getAmount0DeltaParams) IsValid() bool { } // Check: Lsh overflow - liquidity << 96 must not overflow - // This happens when liquidity.AbsUint256() > 2^160 (because 2^160 << 96 = 2^256 which overflows) - liquidityAbs := liquidity.AbsUint256() + // This happens when liquidity.Abs() > 2^160 (because 2^160 << 96 = 2^256 which overflows) + liquidityAbs := liquidity.Abs() max160 := u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 160), u256.One()) if liquidityAbs.Gt(max160) { return false @@ -116,7 +116,7 @@ func (p *getAmount1DeltaParams) IsValid() bool { sqrtB := u256.MustFromDecimal(p.sqrtRatioBX96) liquidity := i256.MustFromDecimal(p.liquidity) - liquidityAbs := liquidity.AbsUint256() + liquidityAbs := liquidity.Abs() // getAmount1DeltaHelper will swap if sqrtA > sqrtB if sqrtA.Gt(sqrtB) { diff --git a/contract/p/gnoswap/gnsmath/swap_math.gno b/contract/p/gnoswap/gnsmath/swap_math.gno index ad9799d44..e4d3508a1 100644 --- a/contract/p/gnoswap/gnsmath/swap_math.gno +++ b/contract/p/gnoswap/gnsmath/swap_math.gno @@ -46,7 +46,7 @@ func SwapMathComputeSwapStep( // NEGATIVE == EXACT_OUT => Estimated AmountIn exactIn := !amountRemaining.IsNeg() - amountRemainingAbs := amountRemaining.AbsUint256() + amountRemainingAbs := amountRemaining.Abs() feeRateInPips := u256.NewUint(feePips) withoutFeeRateInPips := u256.NewUint(denominator - feePips) diff --git a/contract/p/gnoswap/gnsmath/swap_math_test.gno b/contract/p/gnoswap/gnsmath/swap_math_test.gno index 75ecb87e3..7023d24e8 100644 --- a/contract/p/gnoswap/gnsmath/swap_math_test.gno +++ b/contract/p/gnoswap/gnsmath/swap_math_test.gno @@ -371,7 +371,7 @@ func TestSwapMath_FeeConsistency(t *testing.T) { if is_partial_fill && !test.amount.IsNeg() { // exactIn mode // For partial fills in exactIn: fee = amountRemaining - amountIn - expected_fee := new(u256.Uint).Sub(test.amount.AbsUint256(), amount_in_num) + expected_fee := new(u256.Uint).Sub(test.amount.Abs(), amount_in_num) uassert.True(t, fee_amount_num.Eq(expected_fee), ufmt.Sprintf("Partial fill fee should be %s, got %s", expected_fee.ToString(), fee_amount_num.ToString())) } else { @@ -659,9 +659,9 @@ func TestSwapMath_HighPrecision(t *testing.T) { // For exact input, total consumption should not exceed input if !test.amount.IsNeg() { total := new(u256.Uint).Add(amountInNum, feeAmountNum) - uassert.True(t, total.Lte(test.amount.AbsUint256()), + uassert.True(t, total.Lte(test.amount.Abs()), ufmt.Sprintf("Total consumption %s should not exceed input %s", - total.ToString(), test.amount.AbsUint256().ToString())) + total.ToString(), test.amount.Abs().ToString())) } }) } @@ -724,7 +724,7 @@ func TestSwapMath_ExtremeFees(t *testing.T) { } // Fee should never exceed original amount - uassert.True(t, feeAmountNum.Lte(amount.AbsUint256()), + uassert.True(t, feeAmountNum.Lte(amount.Abs()), "Fee should not exceed input amount") }) } @@ -803,7 +803,7 @@ func TestSwapMath_ConsistencyChecks(t *testing.T) { // Conservation check: for exact input if !test.amount.IsNeg() { total := new(u256.Uint).Add(amountInNum, feeAmountNum) - uassert.True(t, total.Lte(test.amount.AbsUint256()), + uassert.True(t, total.Lte(test.amount.Abs()), "amountIn + feeAmount should not exceed input") } }) diff --git a/contract/p/gnoswap/int256/conversion.gno b/contract/p/gnoswap/int256/conversion.gno index 082241731..34161602e 100644 --- a/contract/p/gnoswap/int256/conversion.gno +++ b/contract/p/gnoswap/int256/conversion.gno @@ -229,15 +229,3 @@ func FromUint256(x *u256.Uint) *Int { return z } -// AbsUint256 returns the absolute value of z as a uint256. -// Panics if z is MinInt256 (which cannot be represented as a positive int256). -func (z *Int) AbsUint256() *u256.Uint { - if z.IsNegative() { - if z.IsMinI256() { - panic("int256: overflow") - } - neg := new(Int).Neg(z) - return &u256.Uint{neg[0], neg[1], neg[2], neg[3]} - } - return &u256.Uint{z[0], z[1], z[2], z[3]} -} diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index b24ccade3..96180db0d 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -5,6 +5,8 @@ import ( "errors" "math" "math/bits" + + u256 "gno.land/p/gnoswap/uint256" ) type Int [4]uint64 @@ -109,15 +111,15 @@ func (z *Int) Uint64() uint64 { return z[0] } -func (z *Int) Abs() *Int { +func (z *Int) Abs() *u256.Uint { if z.IsNegative() { if z[3] == 0x8000000000000000 && z[2] == 0 && z[1] == 0 && z[0] == 0 { panic("int256: overflow") } - return z.Clone().Neg(z) + neg := new(Int).Neg(z) + return &u256.Uint{neg[0], neg[1], neg[2], neg[3]} } - - return z + return &u256.Uint{z[0], z[1], z[2], z[3]} } func (z *Int) Sign() int { diff --git a/contract/r/gnoswap/common/liquidity_amounts.gno b/contract/r/gnoswap/common/liquidity_amounts.gno index e3bb4432a..2b6db66e8 100644 --- a/contract/r/gnoswap/common/liquidity_amounts.gno +++ b/contract/r/gnoswap/common/liquidity_amounts.gno @@ -340,7 +340,7 @@ func LiquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint { panic("liquidity_math: x or y is nil") } - yAbs := y.AbsUint256() + yAbs := y.Abs() // Subtract or add based on the sign of y if y.Lt(i256.Zero()) { diff --git a/contract/r/gnoswap/pool/v1/swap.gno b/contract/r/gnoswap/pool/v1/swap.gno index 0938889dc..6a2fb66f1 100644 --- a/contract/r/gnoswap/pool/v1/swap.gno +++ b/contract/r/gnoswap/pool/v1/swap.gno @@ -233,17 +233,17 @@ func (i *poolV1) Swap( // receive token0 from swap callback // send token1 to recipient (output) if result.Amount1.IsNeg() { - i.safeSwapCallback(pool, token0Path, result.Amount0.AbsUint256(), zeroForOne, swapCallback) + i.safeSwapCallback(pool, token0Path, result.Amount0.Abs(), zeroForOne, swapCallback) - i.safeTransfer(pool, recipient, token1Path, result.Amount1.AbsUint256(), false) + i.safeTransfer(pool, recipient, token1Path, result.Amount1.Abs(), false) } } else { // receive token1 from swap callback // send token0 to recipient (output) if result.Amount0.IsNeg() { - i.safeSwapCallback(pool, token1Path, result.Amount1.AbsUint256(), zeroForOne, swapCallback) + i.safeSwapCallback(pool, token1Path, result.Amount1.Abs(), zeroForOne, swapCallback) - i.safeTransfer(pool, recipient, token0Path, result.Amount0.AbsUint256(), true) + i.safeTransfer(pool, recipient, token0Path, result.Amount0.Abs(), true) } } @@ -331,11 +331,11 @@ func (i *poolV1) DrySwap( } if zeroForOne { - if pool.BalanceToken1().Lt(result.Amount1.AbsUint256()) { + if pool.BalanceToken1().Lt(result.Amount1.Abs()) { return "0", "0", false } } else { - if pool.BalanceToken0().Lt(result.Amount0.AbsUint256()) { + if pool.BalanceToken0().Lt(result.Amount0.Abs()) { return "0", "0", false } } From 62bcc8f00beeede0e81e97d8dfc592364abd4268 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Fri, 28 Nov 2025 19:34:14 +0900 Subject: [PATCH 09/28] test(fuzz): Add int256 fuzzing cases --- .../r/gnoswap/test/fuzz/int256_fuzz_test.gno | 303 +++++++++++++++++ .../test/fuzz/int256_params_fuzz_test.gno | 314 ++++++++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 contract/r/gnoswap/test/fuzz/int256_fuzz_test.gno create mode 100644 contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno diff --git a/contract/r/gnoswap/test/fuzz/int256_fuzz_test.gno b/contract/r/gnoswap/test/fuzz/int256_fuzz_test.gno new file mode 100644 index 000000000..3c3f43904 --- /dev/null +++ b/contract/r/gnoswap/test/fuzz/int256_fuzz_test.gno @@ -0,0 +1,303 @@ +package fuzz + +import ( + "testing" + + "gno.land/p/gnoswap/fuzz" + "gno.land/p/gnoswap/fuzzutils" + i256 "gno.land/p/gnoswap/int256" +) + +// TestFuzzInt256Add_ValidParams_Stateless tests Add with valid parameters +func TestFuzzInt256Add_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntAddParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Add(x, y) + }) +} + +// TestFuzzInt256Add_RandomizedParams_Stateless tests Add with randomized parameters +func TestFuzzInt256Add_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntAddParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Add(x, y) + }) +} + +// TestFuzzInt256AddOverflow_ValidParams_Stateless tests AddOverflow with valid (non-overflowing) parameters +func TestFuzzInt256AddOverflow_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntAddOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.AddOverflow(x, y) + + if overflow { + panic("IsValid returned true but overflow occurred") + } + }) +} + +// TestFuzzInt256AddOverflow_RandomizedParams_Stateless tests AddOverflow with randomized parameters +func TestFuzzInt256AddOverflow_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntAddOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.AddOverflow(x, y) + + switch { + case params.IsValid() && overflow: + panic("IsValid returned true but overflow occurred") + case !params.IsValid() && !overflow: + panic("expected overflow" + params.ToString()) + case !params.IsValid() && overflow: + // expected overflow: signal error so fuzz harness aligns with IsValid=false + panic("overflow as expected " + params.ToString()) + } + }) +} + +// TestFuzzInt256Sub_ValidParams_Stateless tests Sub with valid parameters +func TestFuzzInt256Sub_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntSubParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Sub(x, y) + }) +} + +// TestFuzzInt256Sub_RandomizedParams_Stateless tests Sub with randomized parameters +func TestFuzzInt256Sub_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntSubParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Sub(x, y) + }) +} + +// TestFuzzInt256SubOverflow_ValidParams_Stateless tests SubOverflow with valid (non-overflowing) parameters +func TestFuzzInt256SubOverflow_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntSubOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.SubOverflow(x, y) + + if overflow { + panic("IsValid returned true but overflow occurred") + } + }) +} + +// TestFuzzInt256SubOverflow_RandomizedParams_Stateless tests SubOverflow with randomized parameters +func TestFuzzInt256SubOverflow_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntSubOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.SubOverflow(x, y) + + switch { + case params.IsValid() && overflow: + panic("IsValid returned true but overflow occurred") + case !params.IsValid() && !overflow: + panic("expected underflow" + params.ToString()) + case !params.IsValid() && overflow: + // expected underflow: signal error so fuzz harness aligns with IsValid=false + panic("underflow as expected " + params.ToString()) + } + }) +} + +// TestFuzzInt256Mul_ValidParams_Stateless tests Mul with valid parameters +func TestFuzzInt256Mul_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntMulParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Mul(x, y) + }) +} + +// TestFuzzInt256Mul_RandomizedParams_Stateless tests Mul with randomized parameters +func TestFuzzInt256Mul_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntMulParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Mul(x, y) + }) +} + +// TestFuzzInt256MulOverflow_ValidParams_Stateless tests MulOverflow with valid (non-overflowing) parameters +func TestFuzzInt256MulOverflow_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntMulOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.MulOverflow(x, y) + + if overflow { + panic("IsValid returned true but overflow occurred") + } + }) +} + +// TestFuzzInt256MulOverflow_RandomizedParams_Stateless tests MulOverflow with randomized parameters +func TestFuzzInt256MulOverflow_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntMulOverflowParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + _, overflow := z.MulOverflow(x, y) + + switch { + case params.IsValid() && overflow: + panic("IsValid returned true but overflow occurred") + case !params.IsValid() && !overflow: + panic("expected overflow" + params.ToString()) + case !params.IsValid() && overflow: + // expected overflow: signal error so fuzz harness aligns with IsValid=false + panic("overflow as expected " + params.ToString()) + } + }) +} + +// TestFuzzInt256Div_ValidParams_Stateless tests Div with valid parameters +func TestFuzzInt256Div_ValidParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewValidIntDivParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Div(x, y) + }) +} + +// TestFuzzInt256Div_RandomizedParams_Stateless tests Div with randomized parameters +func TestFuzzInt256Div_RandomizedParams_Stateless(t *testing.T) { + fuzzutils.RunFuzzTest(t, FUZZ_ITERATIONS, func(ft *fuzz.T, fuzzResult *fuzzutils.Result, index int) { + // given + params := NewRandomizedIntDivParams(ft) + + // when + fuzzResult.AddParams(index, params) + + // then + x := i256.MustFromDecimal(params.x) + y := i256.MustFromDecimal(params.y) + + var z i256.Int + z.Div(x, y) + }) +} diff --git a/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno b/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno new file mode 100644 index 000000000..1342ba7b7 --- /dev/null +++ b/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno @@ -0,0 +1,314 @@ +package fuzz + +import ( + "gno.land/p/gnoswap/fuzz" + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" + "gno.land/p/nt/ufmt" +) + +const ( + smallIntRangeMin = "-1000000000000000000" + smallIntRangeMax = "1000000000000000000" + smallMulRangeMin = "-1000000000" + smallMulRangeMax = "1000000000" +) + +var ( + maxInt256Abs = intAbs(i256.MaxInt256()) + minInt256Abs = intAbs(i256.MinInt256()) +) + +// Add Parameters +type intAddParams struct { + x string + y string +} + +func (p *intAddParams) IsValid() bool { + return true +} + +func (p *intAddParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntAddParams(t *fuzz.T) *intAddParams { + x := fuzz.Int256Range(smallIntRangeMin, smallIntRangeMax).Draw(t, "x").(*i256.Int) + y := fuzz.Int256Range(smallIntRangeMin, smallIntRangeMax).Draw(t, "y").(*i256.Int) + + return &intAddParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntAddParams(t *fuzz.T) *intAddParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intAddParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// AddOverflow Parameters +type intAddOverflowParams struct { + x string + y string +} + +func (p *intAddOverflowParams) IsValid() bool { + x := i256.MustFromDecimal(p.x) + y := i256.MustFromDecimal(p.y) + + _, overflow := new(i256.Int).AddOverflow(x, y) + return !overflow +} + +func (p *intAddOverflowParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntAddOverflowParams(t *fuzz.T) *intAddOverflowParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + + var limit *u256.Uint + if x.IsNeg() { + limit = minInt256Abs + } else { + limit = maxInt256Abs + } + + remaining := new(u256.Uint).Sub(limit, intAbs(x)) + yAbs := fuzz.Uint256Range("0", remaining.ToString()).Draw(t, "yAbs").(*u256.Uint) + y := newIntWithSign(yAbs, x.IsNeg()) + + return &intAddOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntAddOverflowParams(t *fuzz.T) *intAddOverflowParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intAddOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// Sub Parameters +type intSubParams struct { + x string + y string +} + +func (p *intSubParams) IsValid() bool { + return true +} + +func (p *intSubParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntSubParams(t *fuzz.T) *intSubParams { + x := fuzz.Int256Range(smallIntRangeMin, smallIntRangeMax).Draw(t, "x").(*i256.Int) + y := fuzz.Int256Range(smallIntRangeMin, smallIntRangeMax).Draw(t, "y").(*i256.Int) + + return &intSubParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntSubParams(t *fuzz.T) *intSubParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intSubParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// SubOverflow Parameters +type intSubOverflowParams struct { + x string + y string +} + +func (p *intSubOverflowParams) IsValid() bool { + x := i256.MustFromDecimal(p.x) + y := i256.MustFromDecimal(p.y) + + _, overflow := new(i256.Int).SubOverflow(x, y) + return !overflow +} + +func (p *intSubOverflowParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntSubOverflowParams(t *fuzz.T) *intSubOverflowParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + + var limit *u256.Uint + if x.IsNeg() { + limit = minInt256Abs + } else { + limit = maxInt256Abs + } + + remaining := new(u256.Uint).Sub(limit, intAbs(x)) + negYAbs := fuzz.Uint256Range("0", remaining.ToString()).Draw(t, "negYAbs").(*u256.Uint) + negY := newIntWithSign(negYAbs, x.IsNeg()) + y := i256.New().Neg(negY) + + return &intSubOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntSubOverflowParams(t *fuzz.T) *intSubOverflowParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intSubOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// Mul Parameters +type intMulParams struct { + x string + y string +} + +func (p *intMulParams) IsValid() bool { + return true +} + +func (p *intMulParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntMulParams(t *fuzz.T) *intMulParams { + x := fuzz.Int256Range(smallMulRangeMin, smallMulRangeMax).Draw(t, "x").(*i256.Int) + y := fuzz.Int256Range(smallMulRangeMin, smallMulRangeMax).Draw(t, "y").(*i256.Int) + + return &intMulParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntMulParams(t *fuzz.T) *intMulParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intMulParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// MulOverflow Parameters +type intMulOverflowParams struct { + x string + y string +} + +func (p *intMulOverflowParams) IsValid() bool { + x := i256.MustFromDecimal(p.x) + y := i256.MustFromDecimal(p.y) + + _, overflow := new(i256.Int).MulOverflow(x, y) + return !overflow +} + +func (p *intMulOverflowParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntMulOverflowParams(t *fuzz.T) *intMulOverflowParams { + x := fuzz.Int256Range(smallMulRangeMin, smallMulRangeMax).Draw(t, "x").(*i256.Int) + y := fuzz.Int256Range(smallMulRangeMin, smallMulRangeMax).Draw(t, "y").(*i256.Int) + + return &intMulOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntMulOverflowParams(t *fuzz.T) *intMulOverflowParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intMulOverflowParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +// Div Parameters +type intDivParams struct { + x string + y string +} + +func (p *intDivParams) IsValid() bool { + return !i256.MustFromDecimal(p.y).IsZero() +} + +func (p *intDivParams) ToString() string { + return ufmt.Sprintf("x: %s, y: %s", p.x, p.y) +} + +func NewValidIntDivParams(t *fuzz.T) *intDivParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + if y.IsZero() { + y = i256.One() + } + + return &intDivParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func NewRandomizedIntDivParams(t *fuzz.T) *intDivParams { + x := fuzz.Int256().Draw(t, "x").(*i256.Int) + y := fuzz.Int256().Draw(t, "y").(*i256.Int) + + return &intDivParams{ + x: x.ToString(), + y: y.ToString(), + } +} + +func newIntWithSign(abs *u256.Uint, neg bool) *i256.Int { + val := i256.FromUint256(abs) + if neg && !abs.IsZero() { + val.Neg(val) + } + return val +} + +func intAbs(x *i256.Int) *u256.Uint { + if x.IsMinI256() { + // |MinInt256| is 1<<255, which does not fit in Int.Abs(). + return u256.Zero().Lsh(u256.One(), 255) + } + if x.IsNeg() { + neg := i256.New().Neg(x) + return &u256.Uint{neg[0], neg[1], neg[2], neg[3]} + } + return &u256.Uint{x[0], x[1], x[2], x[3]} +} From db5599013b6604ca40ca4a2a20d4374fcb5746de Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 19:40:22 +0900 Subject: [PATCH 10/28] fix: int256 test failure --- contract/p/gnoswap/int256/absolute_test.gno | 17 ++++--- contract/p/gnoswap/int256/cmp_test.gno | 55 +++++++++------------ 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/contract/p/gnoswap/int256/absolute_test.gno b/contract/p/gnoswap/int256/absolute_test.gno index 2162d626d..ea06fb8c2 100644 --- a/contract/p/gnoswap/int256/absolute_test.gno +++ b/contract/p/gnoswap/int256/absolute_test.gno @@ -14,7 +14,8 @@ func TestAbs(t *testing.T) { {"1", "1"}, {"-1", "1"}, {"-2", "2"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, } for _, tc := range tests { @@ -41,9 +42,9 @@ func TestAbsGt(t *testing.T) { {"-1", "0", "true"}, {"-1", "1", "false"}, {"-2", "1", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "0", "true"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "1", "true"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967", "false"}, } for _, tc := range tests { @@ -78,9 +79,9 @@ func TestAbsLt(t *testing.T) { {"-2", "1", "false"}, {"-5", "10", "true"}, {"31330", "31337", "true"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "false"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "false"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "0", "false"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "1", "false"}, + {"1000", "57896044618658097711785492504343953926634992332820282019728792003956564819967", "true"}, } for _, tc := range tests { @@ -169,7 +170,7 @@ func TestInt_AbsOverflow(t *testing.T) { } // abs value must be GTE 0 (if there is no overflow) - if !tt.wantOverflow && gotResult.neg && !gotResult.IsZero() { + if !tt.wantOverflow && gotResult.IsNegative() && !gotResult.IsZero() { t.Error("absolute value cannot be negative") } diff --git a/contract/p/gnoswap/int256/cmp_test.gno b/contract/p/gnoswap/int256/cmp_test.gno index b6bb6a8ee..75eb7ecc5 100644 --- a/contract/p/gnoswap/int256/cmp_test.gno +++ b/contract/p/gnoswap/int256/cmp_test.gno @@ -16,8 +16,8 @@ func TestEq(t *testing.T) { {"0", "-1", false}, {"1", "1", true}, {"-1", "-1", true}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, } for _, tc := range tests { @@ -52,8 +52,8 @@ func TestNeq(t *testing.T) { {"0", "-1", true}, {"1", "1", false}, {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, } for _, tc := range tests { @@ -87,7 +87,7 @@ func TestCmp(t *testing.T) { {"-1", "0", -1}, {"0", "-1", 1}, {"1", "1", 0}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", 1}, } for _, tc := range tests { @@ -142,7 +142,7 @@ func TestIsNeg(t *testing.T) { want bool }{ {"0", false}, - {"-0", true}, + {"-0", false}, // In two's complement, -0 is same as 0 {"1", false}, {"-1", true}, {"10", false}, @@ -175,7 +175,7 @@ func TestLt(t *testing.T) { {"0", "-1", false}, {"1", "1", false}, {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, } for _, tc := range tests { @@ -210,7 +210,7 @@ func TestGt(t *testing.T) { {"0", "-1", true}, {"1", "1", false}, {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, } for _, tc := range tests { @@ -243,8 +243,8 @@ func TestClone(t *testing.T) { {"-1"}, {"10"}, {"-10"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, } for _, tc := range tests { @@ -266,34 +266,28 @@ func TestNilChecks(t *testing.T) { validInt := NewInt(123) tests := []struct { - name string - fn func() - wantPanic string + name string + fn func() }{ { - name: "Eq with nil", - fn: func() { validInt.Eq(nil) }, - wantPanic: "int256: comparing with nil", + name: "Eq with nil", + fn: func() { validInt.Eq(nil) }, }, { - name: "Neq with nil", - fn: func() { validInt.Neq(nil) }, - wantPanic: "int256: comparing with nil", + name: "Neq with nil", + fn: func() { validInt.Neq(nil) }, }, { - name: "Cmp with nil", - fn: func() { validInt.Cmp(nil) }, - wantPanic: "int256: comparing with nil", + name: "Cmp with nil", + fn: func() { validInt.Cmp(nil) }, }, { - name: "Lt with nil", - fn: func() { validInt.Lt(nil) }, - wantPanic: "int256: comparing with nil", + name: "Lt with nil", + fn: func() { validInt.Lt(nil) }, }, { - name: "Gt with nil", - fn: func() { validInt.Gt(nil) }, - wantPanic: "int256: comparing with nil", + name: "Gt with nil", + fn: func() { validInt.Gt(nil) }, }, } @@ -303,11 +297,8 @@ func TestNilChecks(t *testing.T) { r := recover() if r == nil { t.Errorf("%s: expected panic but got none", tt.name) - return - } - if r.(string) != tt.wantPanic { - t.Errorf("%s: got panic %v, want %v", tt.name, r, tt.wantPanic) } + // Any panic (nil pointer or custom message) is acceptable }() tt.fn() From 35315ee03fefcd38c91654497847bb82b3573694 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 19:45:40 +0900 Subject: [PATCH 11/28] fix: remove redundant function test cases --- contract/p/gnoswap/int256/absolute_test.gno | 152 ------------------ contract/p/gnoswap/int256/conversion_test.gno | 152 ++---------------- 2 files changed, 13 insertions(+), 291 deletions(-) diff --git a/contract/p/gnoswap/int256/absolute_test.gno b/contract/p/gnoswap/int256/absolute_test.gno index ea06fb8c2..9e8522f7d 100644 --- a/contract/p/gnoswap/int256/absolute_test.gno +++ b/contract/p/gnoswap/int256/absolute_test.gno @@ -2,8 +2,6 @@ package int256 import ( "testing" - - "gno.land/p/gnoswap/uint256" ) func TestAbs(t *testing.T) { @@ -33,153 +31,3 @@ func TestAbs(t *testing.T) { } } -func TestAbsGt(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "0", "false"}, - {"1", "0", "true"}, - {"-1", "0", "true"}, - {"-1", "1", "false"}, - {"-2", "1", "true"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "0", "true"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "1", "true"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967", "false"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.AbsGt(y) - - if got != (tc.want == "true") { - t.Errorf("AbsGt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestAbsLt(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "0", "false"}, - {"1", "0", "false"}, - {"-1", "0", "false"}, - {"-1", "1", "false"}, - {"-2", "1", "false"}, - {"-5", "10", "true"}, - {"31330", "31337", "true"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "0", "false"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "1", "false"}, - {"1000", "57896044618658097711785492504343953926634992332820282019728792003956564819967", "true"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := uint256.FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.AbsLt(y) - - if got != (tc.want == "true") { - t.Errorf("AbsLt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestInt_AbsOverflow(t *testing.T) { - tests := []struct { - name string - x *Int - wantResult string - wantOverflow bool - }{ - { - name: "zero", - x: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "positive number", - x: NewInt(100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "negative number", - x: NewInt(-100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "max_int256", - x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967"), - wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - wantOverflow: false, - }, - { - name: "min_int256", - x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968"), - wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", - wantOverflow: true, - }, - { - name: "min_int256 + 1", - x: MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819967"), - wantResult: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.AbsOverflow(tt.x) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if gotResult == nil { - t.Error("unexpected nil result") - return - } - - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - - // abs value must be GTE 0 (if there is no overflow) - if !tt.wantOverflow && gotResult.IsNegative() && !gotResult.IsZero() { - t.Error("absolute value cannot be negative") - } - - // original value must not be modified - originalValue := tt.x.ToString() - if tt.x.ToString() != originalValue { - t.Errorf("original value was modified: got %v, want %v", - tt.x.ToString(), originalValue) - } - }) - } -} diff --git a/contract/p/gnoswap/int256/conversion_test.gno b/contract/p/gnoswap/int256/conversion_test.gno index aae2b8226..912acb3c7 100644 --- a/contract/p/gnoswap/int256/conversion_test.gno +++ b/contract/p/gnoswap/int256/conversion_test.gno @@ -3,9 +3,8 @@ package int256 import ( "testing" - "gno.land/p/nt/ufmt" - "gno.land/p/gnoswap/uint256" + "gno.land/p/nt/ufmt" ) func TestSetInt64(t *testing.T) { @@ -77,26 +76,18 @@ func TestSetInt64MinValueOverflow(t *testing.T) { t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) } - // Verify the internal representation is correct + // Verify sign is correct if tc.x < 0 { - if !z.neg { - t.Errorf("SetInt64(%d): expected neg=true, got neg=false", tc.x) - } - // Check magnitude for MinInt64 - if tc.x == minInt64 { - expectedMag := uint64(1 << 63) // 2^63 - gotMag := z.abs.Uint64() - if gotMag != expectedMag { - t.Errorf("SetInt64(%d): magnitude = %d, want %d", tc.x, gotMag, expectedMag) - } + if !z.IsNegative() { + t.Errorf("SetInt64(%d): expected IsNegative()=true, got false", tc.x) } } else if tc.x > 0 { - if z.neg { - t.Errorf("SetInt64(%d): expected neg=false, got neg=true", tc.x) + if z.IsNegative() { + t.Errorf("SetInt64(%d): expected IsNegative()=false, got true", tc.x) } } else { // tc.x == 0 - if z.neg { - t.Errorf("SetInt64(0): expected neg=false (no -0), got neg=true") + if z.IsNegative() { + t.Errorf("SetInt64(0): expected IsNegative()=false (no -0), got true") } } }) @@ -138,7 +129,7 @@ func TestUint64(t *testing.T) { {"-1", 1}, {"-18446744073709551615", 18446744073709551615}, {"-18446744073709551616", 0}, - {"-18446744073709551617", 1}, + {"-18446744073709551617", 18446744073709551615}, } for _, tc := range tests { @@ -336,100 +327,6 @@ func TestNeg(t *testing.T) { } } -func TestInt_NegOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - - negMaxInt256 := New().Neg(maxInt256) - - tests := []struct { - name string - x *Int - wantResult string - wantOverflow bool - }{ - { - name: "negate zero", - x: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "negate positive", - x: NewInt(100), - wantResult: "-100", - wantOverflow: false, - }, - { - name: "negate negative", - x: NewInt(-100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "negate max_int256", - x: maxInt256, - wantResult: negMaxInt256.ToString(), - wantOverflow: false, - }, - { - name: "negate min_int256", - x: minInt256, - wantResult: minInt256.ToString(), // must preserve the original value - wantOverflow: true, - }, - { - name: "negate (min_int256 + 1)", - x: new(Int).Add(minInt256, One()), - wantResult: new(Int).Sub(maxInt256, Zero()).ToString(), - wantOverflow: false, - }, - { - name: "negate (max_int256 - 1)", - x: MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819966"), // max_int256 - 1 - wantResult: "-57896044618658097711785492504343953926634992332820282019728792003956564819966", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.NegOverflow(tt.x) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if gotResult == nil { - t.Error("unexpected nil result") - return - } - - if gotResult.ToString() != tt.wantResult { - // use almost equal comparison to handle the precision issue - diff := new(Int).Sub(gotResult, MustFromDecimal(tt.wantResult)) - if diff.Abs().Cmp(uint256.NewUint(1)) > 0 { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - // double negation test (only if there is no overflow) - if !tt.wantOverflow { - doubleNegResult, doubleNegOverflow := new(Int).NegOverflow(gotResult) - if doubleNegOverflow { - t.Error("unexpected overflow in double negation") - } - if doubleNegResult.ToString() != tt.x.ToString() { - t.Errorf("double negation result = %v, want %v", - doubleNegResult.ToString(), tt.x.ToString()) - } - } - }) - } -} - func TestSet(t *testing.T) { tests := []struct { x string @@ -453,29 +350,6 @@ func TestSet(t *testing.T) { } } -func TestSetUint256(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "1"}, - {"9223372036854775807", "9223372036854775807"}, - {"18446744073709551615", "18446744073709551615"}, - } - - for _, tc := range tests { - got := New() - - z := uint256.MustFromDecimal(tc.x) - got.SetUint256(z) - - if got.ToString() != tc.want { - t.Errorf("SetUint256(%s) = %s, want %s", tc.x, got.ToString(), tc.want) - } - } -} - func TestToString(t *testing.T) { tests := []struct { name string @@ -515,16 +389,16 @@ func TestToString(t *testing.T) { { name: "Large positive number", setup: func() *Int { - return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + return MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") }, - expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819967", }, { name: "Large negative number", setup: func() *Int { - return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") + return MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968") }, - expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", }, } From 10dd8b09be8f2c928a57f6a0ebae5355e286f14c Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Fri, 28 Nov 2025 19:55:38 +0900 Subject: [PATCH 12/28] fix: Div --- contract/p/gnoswap/int256/int256.gno | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 96180db0d..2b3901624 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -49,9 +49,22 @@ func (z *Int) Neq(x *Int) bool { return !z.Eq(x) } -// Div is an alias for Quo for backward compatibility. +// Div sets z to the quotient of x/y, rounding toward negative infinity. +// Use Quo to truncate toward zero. func (z *Int) Div(x, y *Int) *Int { - return z.Quo(x, y) + if y.IsZero() { + panic(ErrZeroDivision) + } + + quotient := new(Int).Quo(x, y) + remainder := new(Int).Rem(x, y) + + if remainder.IsZero() || x.Sign() == y.Sign() { + return z.Set(quotient) + } + + // When signs differ and there is a remainder, move one step toward -inf. + return z.Sub(quotient, One()) } // IsNeg is an alias for IsNegative for backward compatibility. From 1f64f49dc8f46067d7baf3fefff978b711ff65b9 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 20:11:33 +0900 Subject: [PATCH 13/28] refactor: Rename `IsNegative` to `IsNeg`, remove redundant `New` function, and simplify int256 tests. --- contract/p/gnoswap/int256/arithmetic_test.gno | 10 +- contract/p/gnoswap/int256/bitwise_test.gno | 151 ++++++------------ contract/p/gnoswap/int256/conversion_test.gno | 80 +++------- contract/p/gnoswap/int256/int256.gno | 33 ++-- contract/p/gnoswap/int256/int256_test.gno | 4 +- 5 files changed, 84 insertions(+), 194 deletions(-) diff --git a/contract/p/gnoswap/int256/arithmetic_test.gno b/contract/p/gnoswap/int256/arithmetic_test.gno index 239bb8cd6..e8a0ffd10 100644 --- a/contract/p/gnoswap/int256/arithmetic_test.gno +++ b/contract/p/gnoswap/int256/arithmetic_test.gno @@ -43,7 +43,7 @@ func TestAdd(t *testing.T) { continue } - got := New() + got := Zero() got.Add(x, y) if got.Neq(want) { @@ -207,7 +207,7 @@ func TestSub(t *testing.T) { continue } - got := New() + got := Zero() got.Sub(x, y) if got.Neq(want) { @@ -246,7 +246,7 @@ func TestMul(t *testing.T) { continue } - got := New() + got := Zero() got.Mul(x, y) if got.Neq(want) { @@ -341,7 +341,7 @@ func TestInt_SubOverflow(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // z := new(Int) - z := New() + z := Zero() gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) if gotOverflow != tt.wantOverflow { @@ -528,7 +528,7 @@ func TestDiv(t *testing.T) { if result.ToString() != tt.expected { t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) } - if result.IsZero() && result.IsNegative() { + if result.IsZero() && result.IsNeg() { t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) } }) diff --git a/contract/p/gnoswap/int256/bitwise_test.gno b/contract/p/gnoswap/int256/bitwise_test.gno index 282de5b03..a44711f29 100644 --- a/contract/p/gnoswap/int256/bitwise_test.gno +++ b/contract/p/gnoswap/int256/bitwise_test.gno @@ -4,42 +4,29 @@ import ( "testing" "gno.land/p/nt/uassert" - - "gno.land/p/gnoswap/uint256" ) func TestOr(t *testing.T) { tests := []struct { name string - x, y, want Int + x, y, want string }{ - { - name: "all zeroes", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - { - name: "mixed", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, + {"all zeroes", "0", "0", "0"}, + {"all ones", "-1", "-1", "-1"}, + {"mixed", "-1", "0", "-1"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := New() - got.Or(&tc.x, &tc.y) + x := MustFromDecimal(tc.x) + y := MustFromDecimal(tc.y) + want := MustFromDecimal(tc.want) + + got := Zero() + got.Or(x, y) - if got.Neq(&tc.want) { - t.Errorf("Or(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + if got.Neq(want) { + t.Errorf("Or(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) } }) } @@ -48,59 +35,25 @@ func TestOr(t *testing.T) { func TestAnd(t *testing.T) { tests := []struct { name string - x, y, want Int + x, y, want string }{ - { - name: "all zeroes", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, - { - name: "mixed", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "mixed 2", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "mixed 3", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.Zero(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "one operand zero", - x: Int{abs: uint256.Zero(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.Zero(), neg: false}, - }, - { - name: "one operand all ones", - x: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - y: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - want: Int{abs: uint256.NewUint(0).SetAllOne(), neg: false}, - }, + {"all zeroes", "0", "0", "0"}, + {"all ones", "-1", "-1", "-1"}, + {"mixed", "0", "-1", "0"}, + {"mixed 2", "-1", "0", "0"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := New() - got.And(&tc.x, &tc.y) + x := MustFromDecimal(tc.x) + y := MustFromDecimal(tc.y) + want := MustFromDecimal(tc.want) - if got.Neq(&tc.want) { - t.Errorf("And(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + got := Zero() + got.And(x, y) + + if got.Neq(want) { + t.Errorf("And(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) } }) } @@ -122,11 +75,12 @@ func TestRsh(t *testing.T) { {"18446744073709551615", 62, "3"}, {"18446744073709551615", 63, "1"}, {"18446744073709551615", 64, "0"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 0, "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 1, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 128, "340282366920938463463374607431768211455"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 255, "1"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 256, "0"}, + // Use max int256 value instead of max uint256 + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 0, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 1, "28948022309329048855892746252171976963317496166410141009864396001978282409983"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 128, "170141183460469231731687303715884105727"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 254, "1"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 255, "0"}, {"-1024", 0, "-1024"}, {"-1024", 1, "-512"}, {"-1024", 2, "-256"}, @@ -152,7 +106,7 @@ func TestRsh(t *testing.T) { continue } - got := New() + got := Zero() got.Rsh(x, tc.n) if got.ToString() != tc.want { @@ -166,22 +120,21 @@ func TestLsh(t *testing.T) { x string n uint want string - expectPanic bool }{ - {"1", 0, "1", false}, - {"1", 1, "2", false}, - {"1", 2, "4", false}, - {"2", 0, "2", false}, - {"2", 1, "4", false}, - {"2", 2, "8", false}, - {"-2", 0, "-2", false}, - {"-4", 0, "-4", false}, - {"-8", 0, "-8", false}, - {"-1", 255, "-57896044618658097711785492504343953926634992332820282019728792003956564819968", false}, - {"-1", 256, "0", true}, - {"-2", 255, "0", true}, - {"-4", 254, "0", true}, - {"-8", 253, "0", true}, + {"1", 0, "1"}, + {"1", 1, "2"}, + {"1", 2, "4"}, + {"2", 0, "2"}, + {"2", 1, "4"}, + {"2", 2, "8"}, + {"-2", 0, "-2"}, + {"-4", 0, "-4"}, + {"-8", 0, "-8"}, + {"-1", 255, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"-1", 256, "0"}, // Overflow results in 0 + {"-2", 255, "0"}, + {"-4", 254, "0"}, + {"-8", 253, "0"}, } for _, tc := range tests { @@ -191,14 +144,8 @@ func TestLsh(t *testing.T) { continue } - got := New() - if tc.expectPanic { - uassert.PanicsWithMessage(t, "Lsh: overflow", func() { - got.Lsh(x, tc.n) - }) - } else { - got.Lsh(x, tc.n) - uassert.Equal(t, tc.want, got.ToString()) - } + got := Zero() + got.Lsh(x, tc.n) + uassert.Equal(t, tc.want, got.ToString()) } } diff --git a/contract/p/gnoswap/int256/conversion_test.gno b/contract/p/gnoswap/int256/conversion_test.gno index 912acb3c7..eab9d79af 100644 --- a/contract/p/gnoswap/int256/conversion_test.gno +++ b/contract/p/gnoswap/int256/conversion_test.gno @@ -3,7 +3,6 @@ package int256 import ( "testing" - "gno.land/p/gnoswap/uint256" "gno.land/p/nt/ufmt" ) @@ -78,15 +77,15 @@ func TestSetInt64MinValueOverflow(t *testing.T) { // Verify sign is correct if tc.x < 0 { - if !z.IsNegative() { + if !z.IsNeg() { t.Errorf("SetInt64(%d): expected IsNegative()=true, got false", tc.x) } } else if tc.x > 0 { - if z.IsNegative() { + if z.IsNeg() { t.Errorf("SetInt64(%d): expected IsNegative()=false, got true", tc.x) } } else { // tc.x == 0 - if z.IsNegative() { + if z.IsNeg() { t.Errorf("SetInt64(0): expected IsNegative()=false (no -0), got true") } } @@ -126,8 +125,9 @@ func TestUint64(t *testing.T) { {"18446744073709551615", 18446744073709551615}, {"18446744073709551616", 0}, {"18446744073709551617", 1}, - {"-1", 1}, - {"-18446744073709551615", 18446744073709551615}, + // In two's complement, -1 = 0xFFFF...FFFF, so z[0] = max uint64 + {"-1", 18446744073709551615}, + {"-18446744073709551615", 1}, {"-18446744073709551616", 0}, {"-18446744073709551617", 18446744073709551615}, } @@ -198,54 +198,6 @@ func TestInt64EdgeCases(t *testing.T) { want: maxInt64, description: "SetInt64(MaxInt64) should round-trip correctly", }, - { - name: "Magnitude 2^63 with negative sign", - setupInt: func() *Int { - // Create Int with magnitude = 2^63 and neg = true - z := new(Int) - z.abs = uint256.NewUint(1 << 63) - z.neg = true - return z - }, - want: minInt64, - description: "Magnitude 2^63 with neg=true should return MinInt64", - }, - { - name: "Magnitude 2^63 with positive sign", - setupInt: func() *Int { - // Create Int with magnitude = 2^63 and neg = false - z := new(Int) - z.abs = uint256.NewUint(1 << 63) - z.neg = false - return z - }, - want: minInt64, // Wraps around due to two's complement - description: "Magnitude 2^63 with neg=false wraps to MinInt64", - }, - { - name: "Large positive value wrapping", - setupInt: func() *Int { - // 2^64 - 1 (max uint64) - z := new(Int) - z.abs = uint256.NewUint(18446744073709551615) - z.neg = false - return z - }, - want: -1, - description: "Max uint64 wraps to -1 in int64", - }, - { - name: "Negative large value", - setupInt: func() *Int { - // -(2^64 - 1) - z := new(Int) - z.abs = uint256.NewUint(18446744073709551615) - z.neg = true - return z - }, - want: 1, - description: "-(Max uint64) becomes 1 due to two's complement", - }, { name: "Zero", setupInt: func() *Int { @@ -255,15 +207,21 @@ func TestInt64EdgeCases(t *testing.T) { description: "Zero should return 0", }, { - name: "Negative zero prevention", + name: "Large positive int256", setupInt: func() *Int { - z := new(Int) - z.abs = uint256.NewUint(0) - z.neg = true // This should be normalized to false - return z + // Value larger than int64 max + return MustFromDecimal("18446744073709551615") }, - want: 0, - description: "Negative zero should return 0", + want: -1, // Truncated to int64 + description: "Large positive wraps in int64", + }, + { + name: "Large negative int256", + setupInt: func() *Int { + return MustFromDecimal("-18446744073709551615") + }, + want: 1, // Truncated to int64 + description: "Large negative wraps in int64", }, } diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 2b3901624..0ebfa9bc4 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -19,36 +19,22 @@ var ( ErrNegativeNum = errors.New("negative number") ) -// Zero returns a new Int set to 0. func Zero() *Int { return &Int{} } -// One returns a new Int set to 1. func One() *Int { return &Int{1, 0, 0, 0} } -// MinInt256 returns a new Int set to the minimum value of int256. func MinInt256() *Int { return &Int{0, 0, 0, 0x8000000000000000} } -// MaxInt256 returns a new Int set to the maximum value of int256. func MaxInt256() *Int { return &Int{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff} } -// New returns a new Int set to 0. -func New() *Int { - return &Int{} -} - -// Neq returns true if z != x. -func (z *Int) Neq(x *Int) bool { - return !z.Eq(x) -} - // Div sets z to the quotient of x/y, rounding toward negative infinity. // Use Quo to truncate toward zero. func (z *Int) Div(x, y *Int) *Int { @@ -67,11 +53,6 @@ func (z *Int) Div(x, y *Int) *Int { return z.Sub(quotient, One()) } -// IsNeg is an alias for IsNegative for backward compatibility. -func (z *Int) IsNeg() bool { - return z.IsNegative() -} - func NewInt(val int64) *Int { z := &Int{} z.SetInt64(val) @@ -125,7 +106,7 @@ func (z *Int) Uint64() uint64 { } func (z *Int) Abs() *u256.Uint { - if z.IsNegative() { + if z.IsNeg() { if z[3] == 0x8000000000000000 && z[2] == 0 && z[1] == 0 && z[0] == 0 { panic("int256: overflow") } @@ -153,7 +134,7 @@ func (z *Int) IsOne() bool { return (z[0] == 1) && (z[1]|z[2]|z[3]) == 0 } -func (z *Int) IsNegative() bool { +func (z *Int) IsNeg() bool { return z[3]&0x8000000000000000 != 0 } @@ -179,6 +160,10 @@ func (z *Int) Eq(x *Int) bool { return (z[0] == x[0]) && (z[1] == x[1]) && (z[2] == x[2]) && (z[3] == x[3]) } +func (z *Int) Neq(x *Int) bool { + return !z.Eq(x) +} + func (z *Int) Add(x, y *Int) *Int { var carry uint64 z[0], carry = bits.Add64(x[0], y[0], 0) @@ -274,7 +259,7 @@ func (z *Int) MulOverflow(x, y *Int) (*Int, bool) { z[0], z[1], z[2], z[3] = p[0], p[1], p[2], p[3] var overflow bool - if ((p[4] | p[5] | p[6] | p[7]) != 0) || z.IsNegative() { + if ((p[4] | p[5] | p[6] | p[7]) != 0) || z.IsNeg() { overflow = true } @@ -533,7 +518,7 @@ func (z *Int) Rsh(x *Int, n uint) *Int { if n == 0 { return z.Set(x) } - if x.IsNegative() { + if x.IsNeg() { return z.negRsh(x, n) } return z.rsh(x, n) @@ -597,7 +582,7 @@ func (z *Int) negRsh(x *Int, n uint) *Int { } func (z *Int) Sqrt(x *Int) *Int { - if x.IsNegative() { + if x.IsNeg() { panic(ErrNegativeNum) } if x.IsInt64() { diff --git a/contract/p/gnoswap/int256/int256_test.gno b/contract/p/gnoswap/int256/int256_test.gno index 57699e075..a3aebebc6 100644 --- a/contract/p/gnoswap/int256/int256_test.gno +++ b/contract/p/gnoswap/int256/int256_test.gno @@ -84,13 +84,13 @@ func TestSetString(t *testing.T) { t.Errorf("SetString(%q).abs = %s, want %s", tt.input, got, tt.wantVal) } - if got := z.IsNegative(); got != tt.wantSign { + if got := z.IsNeg(); got != tt.wantSign { t.Errorf("SetString(%q).neg = %v, want %v", tt.input, got, tt.wantSign) } } } -func TestAbs(t *testing.T) { +func TestAbsWithPanicCases(t *testing.T) { tests := []struct { x, want string wantErr bool From 2d739fa06ad6d283f377c5960631bbc332e64f51 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 20:22:17 +0900 Subject: [PATCH 14/28] fix: replace i256.New() with i256.Zero() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- contract/p/gnoswap/gnsmath/sqrt_price_math_test.gno | 6 +++--- contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contract/p/gnoswap/gnsmath/sqrt_price_math_test.gno b/contract/p/gnoswap/gnsmath/sqrt_price_math_test.gno index fbcf5518b..771dd0b46 100644 --- a/contract/p/gnoswap/gnsmath/sqrt_price_math_test.gno +++ b/contract/p/gnoswap/gnsmath/sqrt_price_math_test.gno @@ -1092,7 +1092,7 @@ func TestSqrtPriceMath_GetAmount0Delta(t *testing.T) { name: "negative_liquidity", sqrtRatioAX96: u256.MustFromDecimal("1000000"), sqrtRatioBX96: u256.MustFromDecimal("2000000"), - liquidity: i256.New().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))), + liquidity: i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))), expected: "-198070406285660843983859875840", }, // Zero liquidity @@ -1160,14 +1160,14 @@ func TestSqrtPriceMath_GetAmount1Delta(t *testing.T) { name: "negative_liquidity", sqrtRatioAX96: u256.MustFromDecimal("1000000"), sqrtRatioBX96: u256.MustFromDecimal("2000000"), - liquidity: i256.New().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))), + liquidity: i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))), expected: "0", }, { name: "negative_liquidity_large", sqrtRatioAX96: encodePriceSqrt("1", "1"), sqrtRatioBX96: encodePriceSqrt("4", "1"), - liquidity: i256.New().Neg(i256.FromUint256(u256.MustFromDecimal("1000000000000000000"))), + liquidity: i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("1000000000000000000"))), expected: "-1000000000000000000", }, { diff --git a/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno b/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno index 1342ba7b7..3709740c1 100644 --- a/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno +++ b/contract/r/gnoswap/test/fuzz/int256_params_fuzz_test.gno @@ -166,7 +166,7 @@ func NewValidIntSubOverflowParams(t *fuzz.T) *intSubOverflowParams { remaining := new(u256.Uint).Sub(limit, intAbs(x)) negYAbs := fuzz.Uint256Range("0", remaining.ToString()).Draw(t, "negYAbs").(*u256.Uint) negY := newIntWithSign(negYAbs, x.IsNeg()) - y := i256.New().Neg(negY) + y := i256.Zero().Neg(negY) return &intSubOverflowParams{ x: x.ToString(), @@ -307,7 +307,7 @@ func intAbs(x *i256.Int) *u256.Uint { return u256.Zero().Lsh(u256.One(), 255) } if x.IsNeg() { - neg := i256.New().Neg(x) + neg := i256.Zero().Neg(x) return &u256.Uint{neg[0], neg[1], neg[2], neg[3]} } return &u256.Uint{x[0], x[1], x[2], x[3]} From bcfa92e82bc7c667c48e56f3f16bb650e12acb24 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Fri, 28 Nov 2025 20:49:35 +0900 Subject: [PATCH 15/28] fix: align int256 division with truncation, optimize bit shifts, and update router overflow tests --- contract/p/gnoswap/int256/int256.gno | 79 +++++++++++++------ .../r/gnoswap/router/v1/router_dry_test.gno | 6 +- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 0ebfa9bc4..7458b962c 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -35,22 +35,9 @@ func MaxInt256() *Int { return &Int{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff} } -// Div sets z to the quotient of x/y, rounding toward negative infinity. -// Use Quo to truncate toward zero. +// Div sets z to the quotient of x/y, truncating toward zero (same as Quo). func (z *Int) Div(x, y *Int) *Int { - if y.IsZero() { - panic(ErrZeroDivision) - } - - quotient := new(Int).Quo(x, y) - remainder := new(Int).Rem(x, y) - - if remainder.IsZero() || x.Sign() == y.Sign() { - return z.Set(quotient) - } - - // When signs differ and there is a remainder, move one step toward -inf. - return z.Sub(quotient, One()) + return z.Quo(x, y) } func NewInt(val int64) *Int { @@ -490,16 +477,28 @@ func (z *Int) Lsh(x *Int, n uint) *Int { if n >= 256 { return z.Clear() } + // Handle exact multiples of 64 separately to avoid 64-bit shift issues + if n&0x3f == 0 { + switch n { + case 64: + z[3], z[2], z[1], z[0] = x[2], x[1], x[0], 0 + case 128: + z[3], z[2], z[1], z[0] = x[1], x[0], 0, 0 + case 192: + z[3], z[2], z[1], z[0] = x[0], 0, 0, 0 + } + return z + } switch { - case n >= 192: + case n > 192: n -= 192 z[3], z[2], z[1], z[0] = x[0]<= 128: + case n > 128: n -= 128 z[3] = (x[1] << n) | (x[0] >> (64 - n)) z[2] = x[0] << n z[1], z[0] = 0, 0 - case n >= 64: + case n > 64: n -= 64 z[3] = (x[2] << n) | (x[1] >> (64 - n)) z[2] = (x[1] << n) | (x[0] >> (64 - n)) @@ -525,19 +524,33 @@ func (z *Int) Rsh(x *Int, n uint) *Int { } func (z *Int) rsh(x *Int, n uint) *Int { - if n >= 255 { + if n >= 256 { return z.Clear() } + // Handle exact multiples of 64 separately to avoid 64-bit shift issues + if n&0x3f == 0 { + switch n { + case 0: + return z.Set(x) + case 64: + z[3], z[2], z[1], z[0] = 0, x[3], x[2], x[1] + case 128: + z[3], z[2], z[1], z[0] = 0, 0, x[3], x[2] + case 192: + z[3], z[2], z[1], z[0] = 0, 0, 0, x[3] + } + return z + } switch { - case n >= 192: + case n > 192: n -= 192 z[3], z[2], z[1], z[0] = 0, 0, 0, x[3]>>n - case n >= 128: + case n > 128: n -= 128 z[3], z[2] = 0, 0 z[1] = x[3] >> n z[0] = (x[3] << (64 - n)) | (x[2] >> n) - case n >= 64: + case n > 64: n -= 64 z[3] = 0 z[2] = x[3] >> n @@ -553,20 +566,34 @@ func (z *Int) rsh(x *Int, n uint) *Int { } func (z *Int) negRsh(x *Int, n uint) *Int { - if n >= 255 { + if n >= 256 { return z.SetAllBitsOne() } var v uint64 = 0xffffffffffffffff + // Handle exact multiples of 64 separately to avoid 64-bit shift issues + if n&0x3f == 0 { + switch n { + case 0: + return z.Set(x) + case 64: + z[3], z[2], z[1], z[0] = v, x[3], x[2], x[1] + case 128: + z[3], z[2], z[1], z[0] = v, v, x[3], x[2] + case 192: + z[3], z[2], z[1], z[0] = v, v, v, x[3] + } + return z + } switch { - case n >= 192: + case n > 192: n -= 192 z[3], z[2], z[1], z[0] = v, v, v, (v<<(64-n))|(x[3]>>n) - case n >= 128: + case n > 128: n -= 128 z[3], z[2] = v, v z[1] = (v << (64 - n)) | (x[3] >> n) z[0] = (x[3] << (64 - n)) | (x[2] >> n) - case n >= 64: + case n > 64: n -= 64 z[3] = v z[2] = (v << (64 - n)) | (x[3] >> n) diff --git a/contract/r/gnoswap/router/v1/router_dry_test.gno b/contract/r/gnoswap/router/v1/router_dry_test.gno index ac2a2f3f8..bb75541ff 100644 --- a/contract/r/gnoswap/router/v1/router_dry_test.gno +++ b/contract/r/gnoswap/router/v1/router_dry_test.gno @@ -211,6 +211,7 @@ func TestDrySwapRoute_Basic(t *testing.T) { } func TestDrySwapRouteOverflow(t *testing.T) { + // maxUint256 exceeds int256 max (2^255 - 1), so parsing it as int256 will overflow maxUint256 := "115792089237316195423570985008687907853269984665640564039457584007913129639935" tests := []DrySwapTestCase{ @@ -228,7 +229,8 @@ func TestDrySwapRouteOverflow(t *testing.T) { expectedAmountIn: "0", expectedAmountOut: "0", expectedSuccess: false, - expectPanic: false, + expectPanic: true, + expectedPanicMsg: "int256: overflow", }, { name: "ExactOut - Overflow in calculateSwapAmount", @@ -244,7 +246,7 @@ func TestDrySwapRouteOverflow(t *testing.T) { expectedAmountIn: "0", expectedAmountOut: "0", expectPanic: true, - expectedPanicMsg: "overflow: denominator(9985) must be greater than hi(9999)", + expectedPanicMsg: "int256: overflow", }, } From c0c5d4b5f10af5bc55789aab3d06478c20db3d39 Mon Sep 17 00:00:00 2001 From: jinoosss <112360739+jinoosss@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:59:27 +0900 Subject: [PATCH 16/28] fix: method names --- contract/p/gnoswap/int256/int256.gno | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index 7458b962c..a04285f47 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -35,11 +35,6 @@ func MaxInt256() *Int { return &Int{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff} } -// Div sets z to the quotient of x/y, truncating toward zero (same as Quo). -func (z *Int) Div(x, y *Int) *Int { - return z.Quo(x, y) -} - func NewInt(val int64) *Int { z := &Int{} z.SetInt64(val) @@ -318,7 +313,7 @@ func (z *Int) SetAllBitsOne() *Int { return z } -func (z *Int) Quo(x, y *Int) *Int { +func (z *Int) Div(x, y *Int) *Int { if x.Sign() > 0 { if y.Sign() > 0 { return z.uquo(x, y) @@ -621,7 +616,7 @@ func (z *Int) Sqrt(x *Int) *Int { ) z1 = z1.Lsh(z1, uint(x.BitLen()+1)>>1) for { - z2 = z2.Quo(x, z1) + z2 = z2.Div(x, z1) z2 = z2.Add(z2, z1) { a := z2[3] << 63 From 90fb6e9dea963062fcb09adb486ea361939393aa Mon Sep 17 00:00:00 2001 From: jinoosss <112360739+jinoosss@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:17:59 +0900 Subject: [PATCH 17/28] test: fix int256 txtar tests --- .../base/int256_gas_measurement.txtar | 120 ------------------ 1 file changed, 120 deletions(-) diff --git a/tests/integration/testdata/base/int256_gas_measurement.txtar b/tests/integration/testdata/base/int256_gas_measurement.txtar index 3b160d870..ca2710294 100644 --- a/tests/integration/testdata/base/int256_gas_measurement.txtar +++ b/tests/integration/testdata/base/int256_gas_measurement.txtar @@ -40,10 +40,6 @@ gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAdd -insecure gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAddOverflow -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 9869996' -# AddUint256 -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAddUint256 -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4348107' - # Sub gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestSub -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 4382174' @@ -52,10 +48,6 @@ gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestSub -insecure gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestSubOverflow -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 4401211' -# SubUint256 -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestSubUint256 -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4362770' - # Mul gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestMul -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 4428494' @@ -64,30 +56,10 @@ gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestMul -insecure gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestMulOverflow -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 10107046' -# MulUint256 -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestMulUint256 -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4418751' - # Div gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestDiv -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 4386817' -# DivUint256 -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestDivUint256 -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4373178' - -# Quo -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestQuo -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4392997' - -# Rem -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestRem -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4396670' - -# Mod -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestMod -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4537413' - # Neg gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestNeg -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 3644946' @@ -139,14 +111,6 @@ gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestIsNeg -insecu gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAbs -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 3625840' -# AbsGt -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAbsGt -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4327613' - -# AbsLt -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAbsLt -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4325745' - ### Conversion Methods ### # ToString gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestToString -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 @@ -160,14 +124,6 @@ gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestClone -insecu gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestSign -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 # stdout 'GAS USED: 3628520' -### Delta Operations ### -# AddDelta -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAddDelta -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 4329302' - -# AddDeltaOverflow -gnokey maketx call -pkgpath gno.land/r/gnoswap/int256gas -func TestAddDeltaOverflow -insecure-password-stdin=true -broadcast=true -chainid=tendermint_test -gas-fee 1000000ugnot -gas-wanted 1000000000 -memo "" test1 -# stdout 'GAS USED: 7872829' -- gnomod.toml -- module = "gno.land/r/gnoswap/int256gas" @@ -222,13 +178,6 @@ func TestAddOverflow(cur realm) { z.AddOverflow(x, y) } -func TestAddUint256(cur realm) { - x := int256.NewInt(1000000) - y := uint256.NewUint(500000) - z := int256.Zero() - z.AddUint256(x, y) -} - func TestSub(cur realm) { x := int256.NewInt(1000000) y := int256.NewInt(300000) @@ -243,13 +192,6 @@ func TestSubOverflow(cur realm) { z.SubOverflow(x, y) } -func TestSubUint256(cur realm) { - x := int256.NewInt(1000000) - y := uint256.NewUint(300000) - z := int256.Zero() - z.SubUint256(x, y) -} - func TestMul(cur realm) { x := int256.NewInt(1000) y := int256.NewInt(2000) @@ -264,13 +206,6 @@ func TestMulOverflow(cur realm) { z.MulOverflow(x, y) } -func TestMulUint256(cur realm) { - x := int256.NewInt(1000) - y := uint256.NewUint(2000) - z := int256.Zero() - z.MulUint256(x, y) -} - func TestDiv(cur realm) { x := int256.NewInt(1000000) y := int256.NewInt(3) @@ -278,34 +213,6 @@ func TestDiv(cur realm) { z.Div(x, y) } -func TestDivUint256(cur realm) { - x := int256.NewInt(1000000) - y := uint256.NewUint(3) - z := int256.Zero() - z.DivUint256(x, y) -} - -func TestQuo(cur realm) { - x := int256.NewInt(-1000000) - y := int256.NewInt(3) - z := int256.Zero() - z.Quo(x, y) -} - -func TestRem(cur realm) { - x := int256.NewInt(-1000000) - y := int256.NewInt(7) - z := int256.Zero() - z.Rem(x, y) -} - -func TestMod(cur realm) { - x := int256.NewInt(-1000000) - y := int256.NewInt(7) - z := int256.Zero() - z.Mod(x, y) -} - func TestNeg(cur realm) { x := int256.NewInt(12345) z := int256.Zero() @@ -380,18 +287,6 @@ func TestAbs(cur realm) { x.Abs() } -func TestAbsGt(cur realm) { - x := int256.NewInt(-1000000) - y := uint256.NewUint(500000) - x.AbsGt(y) -} - -func TestAbsLt(cur realm) { - x := int256.NewInt(-1000000) - y := uint256.NewUint(2000000) - x.AbsLt(y) -} - // Conversion Methods func TestToString(cur realm) { x := int256.NewInt(-123456789) @@ -407,18 +302,3 @@ func TestSign(cur realm) { x := int256.NewInt(-12345) x.Sign() } - -// Delta Operations -func TestAddDelta(cur realm) { - z := uint256.NewUint(1000000) - x := uint256.NewUint(500000) - y := int256.NewInt(300000) - int256.AddDelta(z, x, y) -} - -func TestAddDeltaOverflow(cur realm) { - z := uint256.NewUint(1000000) - x := uint256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") - y := int256.NewInt(1) - int256.AddDeltaOverflow(z, x, y) -} From d0f1b541530a705e9d2ce6d86b02ce877e4aff3e Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Sat, 29 Nov 2025 11:02:39 +0900 Subject: [PATCH 18/28] test(int256): All branch tests and mathematical property checks for the `Lsh` and `Rsh` functions --- contract/p/gnoswap/int256/bitwise_test.gno | 260 +++++++++++++++------ 1 file changed, 183 insertions(+), 77 deletions(-) diff --git a/contract/p/gnoswap/int256/bitwise_test.gno b/contract/p/gnoswap/int256/bitwise_test.gno index a44711f29..c0d3ff5fa 100644 --- a/contract/p/gnoswap/int256/bitwise_test.gno +++ b/contract/p/gnoswap/int256/bitwise_test.gno @@ -59,93 +59,199 @@ func TestAnd(t *testing.T) { } } -func TestRsh(t *testing.T) { +func TestLsh(t *testing.T) { tests := []struct { - x string - n uint - want string + name string + x string + n uint + expected string }{ - {"1024", 0, "1024"}, - {"1024", 1, "512"}, - {"1024", 2, "256"}, - {"1024", 10, "1"}, - {"1024", 11, "0"}, - {"18446744073709551615", 0, "18446744073709551615"}, - {"18446744073709551615", 1, "9223372036854775807"}, - {"18446744073709551615", 62, "3"}, - {"18446744073709551615", 63, "1"}, - {"18446744073709551615", 64, "0"}, - // Use max int256 value instead of max uint256 - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 0, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 1, "28948022309329048855892746252171976963317496166410141009864396001978282409983"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 128, "170141183460469231731687303715884105727"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 254, "1"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", 255, "0"}, - {"-1024", 0, "-1024"}, - {"-1024", 1, "-512"}, - {"-1024", 2, "-256"}, - {"-1024", 10, "-1"}, - {"-1024", 10, "-1"}, - {"-9223372036854775808", 0, "-9223372036854775808"}, - {"-9223372036854775808", 1, "-4611686018427387904"}, - {"-9223372036854775808", 62, "-2"}, - {"-9223372036854775808", 63, "-1"}, - {"-9223372036854775808", 64, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 0, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 1, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 253, "-4"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 254, "-2"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 256, "-1"}, + { + name: "n is equal 0", + x: "100", + n: 0, + expected: "100", + }, + { + name: "n is greater than or equal 256", + x: "-4125871947195612497219427349", + n: 256, + expected: "0", + }, + { + name: "x is non-neg & n is smaller than 256 and greater than or equal 192", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 200, + expected: "-1606938044258990275541962092341162602522202993782792835301376", + }, + { + name: "x is non-neg & n is smaller than 192 and greater than or equal 128", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 150, + expected: "-1427247692705959881058285969449495136382746624", + }, + { + name: "x is non-neg & n is smaller than 128 and greater than or equal 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 100, + expected: "-1267650600228229401496703205376", + }, + { + name: "x is non-neg & n is smaller than 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 32, + expected: "-4294967296", + }, + { + name: "x is neg & n is smaller than 256 and greater than or equal 192", + x: "-11111", + n: 200, + expected: "-17854688609761640951546740808002657676624197463920611193033588736", + }, + { + name: "x is neg & n is smaller than 192 and greater than or equal 128", + x: "-11111", + n: 150, + expected: "-15858149113655920238438615406553340460348697739264", + }, + { + name: "x is neg & n is smaller than 128 and greater than or equal 64", + x: "-11111", + n: 100, + expected: "-14084865819135856880029869314932736", + }, + { + name: "x is neg & n is smaller than 64", + x: "-11111", + n: 32, + expected: "-47721381625856", + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Rsh(x, tc.n) - - if got.ToString() != tc.want { - t.Errorf("Rsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + z := new(Int).Lsh(x, tt.n) + uassert.Equal(t, tt.expected, z.ToString()) + }) } } -func TestLsh(t *testing.T) { +func TestRsh(t *testing.T) { tests := []struct { - x string - n uint - want string + name string + x string + n uint + expected string }{ - {"1", 0, "1"}, - {"1", 1, "2"}, - {"1", 2, "4"}, - {"2", 0, "2"}, - {"2", 1, "4"}, - {"2", 2, "8"}, - {"-2", 0, "-2"}, - {"-4", 0, "-4"}, - {"-8", 0, "-8"}, - {"-1", 255, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, - {"-1", 256, "0"}, // Overflow results in 0 - {"-2", 255, "0"}, - {"-4", 254, "0"}, - {"-8", 253, "0"}, + { + name: "n is equal 0", + x: "100", + n: 0, + expected: "100", + }, + { + name: "x is non-neg & n is greater than or equal 255", + x: "431247391574329147932", + n: 255, + expected: "0", + }, + { + name: "x is non-neg & n is smaller than 256 and greater than or equal 192", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 200, + expected: "36028797018963967", + }, + { + name: "x is non-neg & n is smaller than 192 and greater than or equal 128", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 150, + expected: "40564819207303340847894502572031", + }, + { + name: "x is non-neg & n is smaller than 128 and greater than or equal 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 100, + expected: "45671926166590716193865151022383844364247891967", + }, + { + name: "x is non-neg & n is smaller than 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 32, + expected: "13479973333575319897333507543509815336818572211270286240551805124607", + }, + { + name: "x is neg & n is smaller than 256 and greater than or equal 192", + x: "-11111", + n: 200, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 192 and greater than or equal 128", + x: "-11111", + n: 150, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 128 and greater than or equal 64", + x: "-11111", + n: 100, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 64", + x: "-11111", + n: 2, + expected: "-2778", + }, + { + name: "x is neg & n is greater than or equal 255", + x: "-11111", + n: 255, + expected: "-1", + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Lsh(x, tc.n) - uassert.Equal(t, tc.want, got.ToString()) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + z := new(Int).Rsh(x, tt.n) + uassert.Equal(t, tt.expected, z.ToString()) + }) + } +} + +func TestLsh_NoPanic(t *testing.T) { + x := MustFromDecimal("-12345678901234567890") + + for n := uint(0); n < 300; n++ { + uassert.NotPanics(t, func() { + _ = new(Int).Lsh(x, n) + }) + } +} + +func TestRsh_NoPanic(t *testing.T) { + x := MustFromDecimal("-12345678901234567890") + + for n := uint(0); n < 300; n++ { + uassert.NotPanics(t, func() { + _ = new(Int).Rsh(x, n) + }) + } +} + +func TestRsh_SignExtendProperty(t *testing.T) { + // For all negative integers, Rsh should satisfy: + // - Rsh(x, n) <= 0 + // - Rsh(x, n+1) == Rsh(Rsh(x,n), 1) + x := MustFromDecimal("-11111") + + for n := uint(0); n < 255; n++ { + z := new(Int).Rsh(x, n) + zz := new(Int).Rsh(z, 1) + z2 := new(Int).Rsh(x, n+1) + + uassert.Equal(t, z2.ToString(), zz.ToString()) } } From 4a21f60858d0ddc25dee0443582b8df94e40459e Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Mon, 1 Dec 2025 14:06:08 +0900 Subject: [PATCH 19/28] refactor(int256): consolidate scattered test files Merge all int256 package test files into two main files: - int256_test.gno: tests for int256.gno functions - conversion_test.gno: tests for conversion.gno functions Deleted files: - arithmetic_test.gno - absolute_test.gno - bitwise_test.gno - cmp_test.gno - runtime_metrics_test.gno --- contract/p/gnoswap/int256/absolute_test.gno | 33 - contract/p/gnoswap/int256/arithmetic_test.gno | 547 ------ contract/p/gnoswap/int256/bitwise_test.gno | 257 --- contract/p/gnoswap/int256/cmp_test.gno | 307 ---- contract/p/gnoswap/int256/conversion_test.gno | 482 ++--- contract/p/gnoswap/int256/int256_test.gno | 1578 ++++++++++++++++- .../p/gnoswap/int256/runtime_metrics_test.gno | 165 -- 7 files changed, 1709 insertions(+), 1660 deletions(-) delete mode 100644 contract/p/gnoswap/int256/absolute_test.gno delete mode 100644 contract/p/gnoswap/int256/arithmetic_test.gno delete mode 100644 contract/p/gnoswap/int256/bitwise_test.gno delete mode 100644 contract/p/gnoswap/int256/cmp_test.gno delete mode 100644 contract/p/gnoswap/int256/runtime_metrics_test.gno diff --git a/contract/p/gnoswap/int256/absolute_test.gno b/contract/p/gnoswap/int256/absolute_test.gno deleted file mode 100644 index 9e8522f7d..000000000 --- a/contract/p/gnoswap/int256/absolute_test.gno +++ /dev/null @@ -1,33 +0,0 @@ -package int256 - -import ( - "testing" -) - -func TestAbs(t *testing.T) { - tests := []struct { - x, want string - }{ - {"0", "0"}, - {"1", "1"}, - {"-1", "1"}, - {"-2", "2"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.Abs() - - if got.ToString() != tc.want { - t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) - } - } -} - diff --git a/contract/p/gnoswap/int256/arithmetic_test.gno b/contract/p/gnoswap/int256/arithmetic_test.gno deleted file mode 100644 index e8a0ffd10..000000000 --- a/contract/p/gnoswap/int256/arithmetic_test.gno +++ /dev/null @@ -1,547 +0,0 @@ -package int256 - -import "testing" - -func TestAdd(t *testing.T) { - maxIntStr := MaxInt256().ToString() - - tests := []struct { - x, y, want string - }{ - {"0", "1", "1"}, - {"1", "0", "1"}, - {"1", "1", "2"}, - {"1", "2", "3"}, - // NEGATIVE - {"-1", "1", "0"}, - {"1", "-1", "0"}, - {"3", "-3", "0"}, - {"-1", "-1", "-2"}, - {"-1", "-2", "-3"}, - {"-1", "3", "2"}, - {"3", "-1", "2"}, - // BOUNDARY - {maxIntStr, "0", maxIntStr}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Add(x, y) - - if got.Neq(want) { - t.Errorf("Add(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestAddOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - - tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool - }{ - // Basic cases (no overflow) - { - name: "positive + positive (no overflow)", - x: NewInt(100), - y: NewInt(200), - wantResult: "300", - wantOverflow: false, - }, - { - name: "negative + negative (no overflow)", - x: NewInt(-100), - y: NewInt(-200), - wantResult: "-300", - wantOverflow: false, - }, - // Boundary cases - near maximum value - { - name: "max_int256 + 0", - x: maxInt256, - y: Zero(), - wantResult: maxInt256.ToString(), - wantOverflow: false, - }, - { - name: "max_int256 - 1 + 1", - x: new(Int).Sub(maxInt256, One()), - y: One(), - wantResult: maxInt256.ToString(), - wantOverflow: false, - }, - { - name: "max_int256 + 1", - x: maxInt256, - y: One(), - wantResult: "", // overflow - wantOverflow: true, - }, - - // Boundary cases - near minimum value - { - name: "min_int256 + 0", - x: minInt256, - y: Zero(), - wantResult: minInt256.ToString(), - wantOverflow: false, - }, - { - name: "min_int256 + 1 - 1", - x: new(Int).Add(minInt256, One()), - y: NewInt(-1), - wantResult: minInt256.ToString(), - wantOverflow: false, - }, - { - name: "min_int256 + (-1)", - x: minInt256, - y: NewInt(-1), - wantResult: "", // overflow - wantOverflow: true, - }, - - // Special cases - { - name: "max_int256 + min_int256", - x: maxInt256, - y: minInt256, - wantResult: "-1", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.AddOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - // Commutativity test only for non-overflow cases - if !tt.wantOverflow { - reverseResult, reverseOverflow := z.AddOverflow(tt.y, tt.x) - if reverseOverflow != gotOverflow { - t.Error("addition is not commutative for overflow") - } - if reverseResult.ToString() != gotResult.ToString() { - t.Error("addition is not commutative for result") - } - } - }) - } -} - -func TestSub(t *testing.T) { - maxInt := MaxInt256() - maxIntStr := maxInt.ToString() - maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() - minIntStr := MinInt256().ToString() - - tests := []struct { - x, y, want string - }{ - {"1", "0", "1"}, - {"1", "1", "0"}, - {"-1", "1", "-2"}, - {"1", "-1", "2"}, - {"-1", "-1", "0"}, - {minIntStr, minIntStr, "0"}, - {minIntStr, "0", minIntStr}, - {maxIntStr, maxIntStr, "0"}, - {maxIntMinusOneStr, "-1", maxIntStr}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Sub(x, y) - - if got.Neq(want) { - t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestMul(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"5", "3", "15"}, - {"-5", "3", "-15"}, - {"5", "-3", "-15"}, - {"0", "3", "0"}, - {"3", "0", "0"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Mul(x, y) - - if got.Neq(want) { - t.Errorf("Mul(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestInt_SubOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - - tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool - }{ - { - name: "positive - positive (no overflow)", - x: NewInt(200), - y: NewInt(100), - wantResult: "100", - wantOverflow: false, - }, - { - name: "negative - negative (no overflow)", - x: NewInt(-200), - y: NewInt(-300), - wantResult: "100", - wantOverflow: false, - }, - { - name: "positive - negative (no overflow)", - x: NewInt(200), - y: NewInt(-100), - wantResult: "300", - wantOverflow: false, - }, - { - name: "max_int256 - 0", - x: maxInt256, - y: Zero(), - wantResult: maxInt256.ToString(), - wantOverflow: false, - }, - { - name: "min_int256 - 0", - x: minInt256, - y: Zero(), - wantResult: minInt256.ToString(), - wantOverflow: false, - }, - { - name: "max_int256 - (-1)", // max_int256 + 1 -> overflow - x: maxInt256, - y: NewInt(-1), - wantResult: "", - wantOverflow: true, - }, - { - name: "min_int256 - 1", // min_int256 - 1 -> overflow - x: minInt256, - y: One(), - wantResult: "", - wantOverflow: true, - }, - { - name: "0 - 0", - x: Zero(), - y: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "min_int256 - min_int256", - x: minInt256, - y: minInt256, - wantResult: "0", - wantOverflow: false, - }, - { - name: "max_int256 - max_int256", - x: maxInt256, - y: maxInt256, - wantResult: "0", - wantOverflow: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // z := new(Int) - z := Zero() - gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - }) - } -} - -func TestInt_MulOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - - tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool - }{ - { - name: "positive * positive (no overflow)", - x: NewInt(100), - y: NewInt(100), - wantResult: "10000", - wantOverflow: false, - }, - { - name: "negative * negative (no overflow)", - x: NewInt(-100), - y: NewInt(-100), - wantResult: "10000", - wantOverflow: false, - }, - { - name: "positive * negative (no overflow)", - x: NewInt(100), - y: NewInt(-100), - wantResult: "-10000", - wantOverflow: false, - }, - { - name: "0 * positive", - x: Zero(), - y: NewInt(100), - wantResult: "0", - wantOverflow: false, - }, - { - name: "positive * 0", - x: NewInt(100), - y: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "0 * 0", - x: Zero(), - y: Zero(), - wantResult: "0", - wantOverflow: false, - }, - { - name: "max_int256 * 1", - x: maxInt256, - y: One(), - wantResult: maxInt256.ToString(), - wantOverflow: false, - }, - { - name: "min_int256 * 1", - x: minInt256, - y: One(), - wantResult: minInt256.ToString(), - wantOverflow: false, - }, - { - name: "min_int256 * -1", - x: minInt256, - y: NewInt(-1), - wantResult: "", // overflow because abs(min_int256) > max_int256 - wantOverflow: true, - }, - { - name: "max_int256 * 2", - x: maxInt256, - y: NewInt(2), - wantResult: "", - wantOverflow: true, - }, - { - name: "min_int256 * 2", - x: minInt256, - y: NewInt(2), - wantResult: "", - wantOverflow: true, - }, - { - name: "half_max * 2", - x: MustFromDecimal("28948022309329048855892746252171976963317496332820282019728792003956564819983"), // (2^255-1)/2 - y: NewInt(2), - wantResult: "", - wantOverflow: true, - }, - { - name: "(half_max + 1) * 2", - x: new(Int).Add(new(Int).Div(maxInt256, NewInt(2)), One()), - y: NewInt(2), - wantResult: "", - wantOverflow: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.MulOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - if !tt.wantOverflow { - reverseResult, reverseOverflow := z.MulOverflow(tt.y, tt.x) - if reverseOverflow != gotOverflow { - t.Error("multiplication is not commutative for overflow") - } - if reverseResult.ToString() != gotResult.ToString() { - t.Error("multiplication is not commutative for result") - } - } - }) - } -} - -func TestDiv(t *testing.T) { - maxInt := MaxInt256() - maxIntStr := maxInt.ToString() - maxIntHalfStr := new(Int).Rsh(maxInt, 1).ToString() - - tests := []struct { - x, y, expected string - }{ - {"1", "1", "1"}, - {"0", "1", "0"}, - {"-1", "1", "-1"}, - {"1", "-1", "-1"}, - {"-1", "-1", "1"}, - {"-6", "3", "-2"}, - {"10", "-2", "-5"}, - {"-10", "3", "-3"}, - {"7", "3", "2"}, - {"-7", "3", "-2"}, - {maxIntStr, "2", maxIntHalfStr}, // Max int256 / 2 - } - - for _, tt := range tests { - t.Run(tt.x+"/"+tt.y, func(t *testing.T) { - x := MustFromDecimal(tt.x) - y := MustFromDecimal(tt.y) - result := Zero().Div(x, y) - if result.ToString() != tt.expected { - t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) - } - if result.IsZero() && result.IsNeg() { - t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) - } - }) - } - - t.Run("Division by zero", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("Div(1, 0) did not panic") - } - }() - x := MustFromDecimal("1") - y := MustFromDecimal("0") - Zero().Div(x, y) - }) -} diff --git a/contract/p/gnoswap/int256/bitwise_test.gno b/contract/p/gnoswap/int256/bitwise_test.gno deleted file mode 100644 index c0d3ff5fa..000000000 --- a/contract/p/gnoswap/int256/bitwise_test.gno +++ /dev/null @@ -1,257 +0,0 @@ -package int256 - -import ( - "testing" - - "gno.land/p/nt/uassert" -) - -func TestOr(t *testing.T) { - tests := []struct { - name string - x, y, want string - }{ - {"all zeroes", "0", "0", "0"}, - {"all ones", "-1", "-1", "-1"}, - {"mixed", "-1", "0", "-1"}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - x := MustFromDecimal(tc.x) - y := MustFromDecimal(tc.y) - want := MustFromDecimal(tc.want) - - got := Zero() - got.Or(x, y) - - if got.Neq(want) { - t.Errorf("Or(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) - } - }) - } -} - -func TestAnd(t *testing.T) { - tests := []struct { - name string - x, y, want string - }{ - {"all zeroes", "0", "0", "0"}, - {"all ones", "-1", "-1", "-1"}, - {"mixed", "0", "-1", "0"}, - {"mixed 2", "-1", "0", "0"}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - x := MustFromDecimal(tc.x) - y := MustFromDecimal(tc.y) - want := MustFromDecimal(tc.want) - - got := Zero() - got.And(x, y) - - if got.Neq(want) { - t.Errorf("And(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) - } - }) - } -} - -func TestLsh(t *testing.T) { - tests := []struct { - name string - x string - n uint - expected string - }{ - { - name: "n is equal 0", - x: "100", - n: 0, - expected: "100", - }, - { - name: "n is greater than or equal 256", - x: "-4125871947195612497219427349", - n: 256, - expected: "0", - }, - { - name: "x is non-neg & n is smaller than 256 and greater than or equal 192", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 200, - expected: "-1606938044258990275541962092341162602522202993782792835301376", - }, - { - name: "x is non-neg & n is smaller than 192 and greater than or equal 128", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 150, - expected: "-1427247692705959881058285969449495136382746624", - }, - { - name: "x is non-neg & n is smaller than 128 and greater than or equal 64", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 100, - expected: "-1267650600228229401496703205376", - }, - { - name: "x is non-neg & n is smaller than 64", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 32, - expected: "-4294967296", - }, - { - name: "x is neg & n is smaller than 256 and greater than or equal 192", - x: "-11111", - n: 200, - expected: "-17854688609761640951546740808002657676624197463920611193033588736", - }, - { - name: "x is neg & n is smaller than 192 and greater than or equal 128", - x: "-11111", - n: 150, - expected: "-15858149113655920238438615406553340460348697739264", - }, - { - name: "x is neg & n is smaller than 128 and greater than or equal 64", - x: "-11111", - n: 100, - expected: "-14084865819135856880029869314932736", - }, - { - name: "x is neg & n is smaller than 64", - x: "-11111", - n: 32, - expected: "-47721381625856", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - x := MustFromDecimal(tt.x) - z := new(Int).Lsh(x, tt.n) - uassert.Equal(t, tt.expected, z.ToString()) - }) - } -} - -func TestRsh(t *testing.T) { - tests := []struct { - name string - x string - n uint - expected string - }{ - { - name: "n is equal 0", - x: "100", - n: 0, - expected: "100", - }, - { - name: "x is non-neg & n is greater than or equal 255", - x: "431247391574329147932", - n: 255, - expected: "0", - }, - { - name: "x is non-neg & n is smaller than 256 and greater than or equal 192", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 200, - expected: "36028797018963967", - }, - { - name: "x is non-neg & n is smaller than 192 and greater than or equal 128", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 150, - expected: "40564819207303340847894502572031", - }, - { - name: "x is non-neg & n is smaller than 128 and greater than or equal 64", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 100, - expected: "45671926166590716193865151022383844364247891967", - }, - { - name: "x is non-neg & n is smaller than 64", - x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", - n: 32, - expected: "13479973333575319897333507543509815336818572211270286240551805124607", - }, - { - name: "x is neg & n is smaller than 256 and greater than or equal 192", - x: "-11111", - n: 200, - expected: "-1", - }, - { - name: "x is neg & n is smaller than 192 and greater than or equal 128", - x: "-11111", - n: 150, - expected: "-1", - }, - { - name: "x is neg & n is smaller than 128 and greater than or equal 64", - x: "-11111", - n: 100, - expected: "-1", - }, - { - name: "x is neg & n is smaller than 64", - x: "-11111", - n: 2, - expected: "-2778", - }, - { - name: "x is neg & n is greater than or equal 255", - x: "-11111", - n: 255, - expected: "-1", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - x := MustFromDecimal(tt.x) - z := new(Int).Rsh(x, tt.n) - uassert.Equal(t, tt.expected, z.ToString()) - }) - } -} - -func TestLsh_NoPanic(t *testing.T) { - x := MustFromDecimal("-12345678901234567890") - - for n := uint(0); n < 300; n++ { - uassert.NotPanics(t, func() { - _ = new(Int).Lsh(x, n) - }) - } -} - -func TestRsh_NoPanic(t *testing.T) { - x := MustFromDecimal("-12345678901234567890") - - for n := uint(0); n < 300; n++ { - uassert.NotPanics(t, func() { - _ = new(Int).Rsh(x, n) - }) - } -} - -func TestRsh_SignExtendProperty(t *testing.T) { - // For all negative integers, Rsh should satisfy: - // - Rsh(x, n) <= 0 - // - Rsh(x, n+1) == Rsh(Rsh(x,n), 1) - x := MustFromDecimal("-11111") - - for n := uint(0); n < 255; n++ { - z := new(Int).Rsh(x, n) - zz := new(Int).Rsh(z, 1) - z2 := new(Int).Rsh(x, n+1) - - uassert.Equal(t, z2.ToString(), zz.ToString()) - } -} diff --git a/contract/p/gnoswap/int256/cmp_test.gno b/contract/p/gnoswap/int256/cmp_test.gno deleted file mode 100644 index 75eb7ecc5..000000000 --- a/contract/p/gnoswap/int256/cmp_test.gno +++ /dev/null @@ -1,307 +0,0 @@ -package int256 - -import ( - "testing" -) - -func TestEq(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", true}, - {"0", "1", false}, - {"1", "0", false}, - {"-1", "0", false}, - {"0", "-1", false}, - {"1", "1", true}, - {"-1", "-1", true}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Eq(y) - if got != tc.want { - t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestNeq(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", true}, - {"1", "0", true}, - {"-1", "0", true}, - {"0", "-1", true}, - {"1", "1", false}, - {"-1", "-1", false}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Neq(y) - if got != tc.want { - t.Errorf("Neq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestCmp(t *testing.T) { - tests := []struct { - x, y string - want int - }{ - {"0", "0", 0}, - {"0", "1", -1}, - {"1", "0", 1}, - {"-1", "0", -1}, - {"0", "-1", 1}, - {"1", "1", 0}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", 1}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Cmp(y) - if got != tc.want { - t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestIsZero(t *testing.T) { - tests := []struct { - x string - want bool - }{ - {"0", true}, - {"-0", true}, - {"1", false}, - {"-1", false}, - {"10", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.IsZero() - if got != tc.want { - t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) - } - } -} - -func TestIsNeg(t *testing.T) { - tests := []struct { - x string - want bool - }{ - {"0", false}, - {"-0", false}, // In two's complement, -0 is same as 0 - {"1", false}, - {"-1", true}, - {"10", false}, - {"-10", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - got := x.IsNeg() - if got != tc.want { - t.Errorf("IsNeg(%s) = %v, want %v", tc.x, got, tc.want) - } - } -} - -func TestLt(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", true}, - {"1", "0", false}, - {"-1", "0", true}, - {"0", "-1", false}, - {"1", "1", false}, - {"-1", "-1", false}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Lt(y) - if got != tc.want { - t.Errorf("Lt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestGt(t *testing.T) { - tests := []struct { - x, y string - want bool - }{ - {"0", "0", false}, - {"0", "1", false}, - {"1", "0", true}, - {"-1", "0", false}, - {"0", "-1", true}, - {"1", "1", false}, - {"-1", "-1", false}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Gt(y) - if got != tc.want { - t.Errorf("Gt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } - } -} - -func TestClone(t *testing.T) { - tests := []struct { - x string - }{ - {"0"}, - {"-0"}, - {"1"}, - {"-1"}, - {"10"}, - {"-10"}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y := x.Clone() - - if x.Cmp(y) != 0 { - t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) - } - } -} - -func TestNilChecks(t *testing.T) { - validInt := NewInt(123) - - tests := []struct { - name string - fn func() - }{ - { - name: "Eq with nil", - fn: func() { validInt.Eq(nil) }, - }, - { - name: "Neq with nil", - fn: func() { validInt.Neq(nil) }, - }, - { - name: "Cmp with nil", - fn: func() { validInt.Cmp(nil) }, - }, - { - name: "Lt with nil", - fn: func() { validInt.Lt(nil) }, - }, - { - name: "Gt with nil", - fn: func() { validInt.Gt(nil) }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - r := recover() - if r == nil { - t.Errorf("%s: expected panic but got none", tt.name) - } - // Any panic (nil pointer or custom message) is acceptable - }() - - tt.fn() - }) - } -} diff --git a/contract/p/gnoswap/int256/conversion_test.gno b/contract/p/gnoswap/int256/conversion_test.gno index eab9d79af..b99bc6be7 100644 --- a/contract/p/gnoswap/int256/conversion_test.gno +++ b/contract/p/gnoswap/int256/conversion_test.gno @@ -1,372 +1,244 @@ package int256 import ( + "runtime" + "strconv" + "strings" "testing" - - "gno.land/p/nt/ufmt" + "time" ) -func TestSetInt64(t *testing.T) { +func TestFromDecimal(t *testing.T) { tests := []struct { - x int64 - want string + input string + wantErr bool + want string }{ - {0, "0"}, - {1, "1"}, - {-1, "-1"}, - {9223372036854775807, "9223372036854775807"}, - {-9223372036854775808, "-9223372036854775808"}, + {"0", false, "0"}, + {"1", false, "1"}, + {"-1", false, "-1"}, + {"123456789", false, "123456789"}, + {"-123456789", false, "-123456789"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", false, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", false, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + // overflow cases + {"57896044618658097711785492504343953926634992332820282019728792003956564819968", true, ""}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819969", true, ""}, + // invalid inputs + {"", true, ""}, + {"abc", true, ""}, + {"12.34", true, ""}, } for _, tc := range tests { - var z Int - z.SetInt64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + z, err := FromDecimal(tc.input) + if tc.wantErr { + if err == nil { + t.Errorf("FromDecimal(%q) expected error, got nil", tc.input) + } + continue + } + if err != nil { + t.Errorf("FromDecimal(%q) unexpected error: %v", tc.input, err) + continue + } + if z.ToString() != tc.want { + t.Errorf("FromDecimal(%q) = %s, want %s", tc.input, z.ToString(), tc.want) } } } -func TestSetInt64MinValueOverflow(t *testing.T) { - const minInt64 = -9223372036854775808 // -2^63 - const maxInt64 = 9223372036854775807 // 2^63 - 1 - - tests := []struct { - name string - x int64 - want string - }{ - { - name: "MinInt64 should not cause overflow", - x: minInt64, - want: "-9223372036854775808", - }, - { - name: "MaxInt64 works correctly", - x: maxInt64, - want: "9223372036854775807", - }, - { - name: "MinInt64 + 1", - x: minInt64 + 1, - want: "-9223372036854775807", - }, - { - name: "Negative one", - x: -1, - want: "-1", - }, - { - name: "Zero", - x: 0, - want: "0", - }, +func TestMustFromDecimal(t *testing.T) { + // Valid cases should not panic + validCases := []string{ + "0", "1", "-1", "123456789", + "57896044618658097711785492504343953926634992332820282019728792003956564819967", } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - var z Int - z.SetInt64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) - } - - // Verify sign is correct - if tc.x < 0 { - if !z.IsNeg() { - t.Errorf("SetInt64(%d): expected IsNegative()=true, got false", tc.x) - } - } else if tc.x > 0 { - if z.IsNeg() { - t.Errorf("SetInt64(%d): expected IsNegative()=false, got true", tc.x) - } - } else { // tc.x == 0 - if z.IsNeg() { - t.Errorf("SetInt64(0): expected IsNegative()=false (no -0), got true") + for _, tc := range validCases { + func() { + defer func() { + if r := recover(); r != nil { + t.Errorf("MustFromDecimal(%q) panicked unexpectedly: %v", tc, r) } + }() + z := MustFromDecimal(tc) + if z == nil { + t.Errorf("MustFromDecimal(%q) returned nil", tc) } - }) + }() } -} -func TestSetUint64(t *testing.T) { - tests := []struct { - x uint64 - want string - }{ - {0, "0"}, - {1, "1"}, + // Invalid cases should panic + invalidCases := []string{ + "", "abc", "12.34", + "57896044618658097711785492504343953926634992332820282019728792003956564819968", } - for _, tc := range tests { - var z Int - z.SetUint64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) - } + for _, tc := range invalidCases { + func() { + defer func() { + if r := recover(); r == nil { + t.Errorf("MustFromDecimal(%q) should have panicked", tc) + } + }() + MustFromDecimal(tc) + }() } } -func TestUint64(t *testing.T) { - tests := []struct { - x string - want uint64 - }{ - {"0", 0}, - {"1", 1}, - {"9223372036854775807", 9223372036854775807}, - {"9223372036854775808", 9223372036854775808}, - {"18446744073709551615", 18446744073709551615}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, - // In two's complement, -1 = 0xFFFF...FFFF, so z[0] = max uint64 - {"-1", 18446744073709551615}, - {"-18446744073709551615", 1}, - {"-18446744073709551616", 0}, - {"-18446744073709551617", 18446744073709551615}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - - got := z.Uint64() - if got != tc.want { - t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) +// readAllocatorBytes parses runtime.MemStats() into the current allocated bytes. +func readAllocatorBytes(t *testing.T) int64 { + t.Helper() + stats := runtime.MemStats() + if stats == "nil allocator" { + return 0 + } + if !strings.HasPrefix(stats, "Allocator{") || !strings.HasSuffix(stats, "}") { + t.Fatalf("unexpected runtime.MemStats output: %q", stats) + } + body := strings.TrimSuffix(strings.TrimPrefix(stats, "Allocator{"), "}") + parts := strings.Split(body, ", ") + if len(parts) != 2 { + t.Fatalf("unexpected runtime.MemStats content: %q", stats) + } + var ( + bytes int64 + found bool + ) + for _, part := range parts { + fields := strings.Split(part, ":") + if len(fields) != 2 { + t.Fatalf("unexpected runtime.MemStats pair %q", part) } - } -} - -func TestInt64(t *testing.T) { - tests := []struct { - x string - want int64 - }{ - {"0", 0}, - {"1", 1}, - {"-1", -1}, - {"9223372036854775807", 9223372036854775807}, - {"-9223372036854775808", -9223372036854775808}, - {"9223372036854775808", -9223372036854775808}, - {"-9223372036854775809", 9223372036854775807}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, - {"18446744073709551615", -1}, - {"-18446744073709551615", 1}, - } - - for _, tc := range tests { - z := MustFromDecimal(tc.x) - - got := z.Int64() - if got != tc.want { - t.Errorf("Int64(%s) = %d, want %d", tc.x, got, tc.want) + if fields[0] == "bytes" { + val, err := strconv.ParseInt(fields[1], 10, 64) + if err != nil { + t.Fatalf("failed to parse bytes from %q: %v", part, err) + } + bytes = val + found = true + break } } -} - -func TestInt64EdgeCases(t *testing.T) { - const minInt64 = -9223372036854775808 // -2^63 - const maxInt64 = 9223372036854775807 // 2^63 - 1 - - tests := []struct { - name string - setupInt func() *Int - want int64 - description string - }{ - { - name: "MinInt64 from SetInt64", - setupInt: func() *Int { - z := new(Int) - return z.SetInt64(minInt64) - }, - want: minInt64, - description: "SetInt64(MinInt64) should round-trip correctly", - }, - { - name: "MaxInt64 from SetInt64", - setupInt: func() *Int { - z := new(Int) - return z.SetInt64(maxInt64) - }, - want: maxInt64, - description: "SetInt64(MaxInt64) should round-trip correctly", - }, - { - name: "Zero", - setupInt: func() *Int { - return Zero() - }, - want: 0, - description: "Zero should return 0", - }, - { - name: "Large positive int256", - setupInt: func() *Int { - // Value larger than int64 max - return MustFromDecimal("18446744073709551615") - }, - want: -1, // Truncated to int64 - description: "Large positive wraps in int64", - }, - { - name: "Large negative int256", - setupInt: func() *Int { - return MustFromDecimal("-18446744073709551615") - }, - want: 1, // Truncated to int64 - description: "Large negative wraps in int64", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - z := tc.setupInt() - got := z.Int64() - - if got != tc.want { - t.Errorf("%s: got %d, want %d", tc.description, got, tc.want) - } - }) + if !found { + t.Fatalf("bytes key not found in runtime.MemStats output: %q", stats) } + return bytes } -// TestInt64RoundTrip verifies that SetInt64 and Int64 work correctly together -func TestInt64RoundTrip(t *testing.T) { - // Test all interesting int64 values - values := []int64{ - 0, 1, -1, - 127, -128, // int8 boundaries - 32767, -32768, // int16 boundaries - 2147483647, -2147483648, // int32 boundaries - 9223372036854775807, -9223372036854775808, // int64 boundaries (MaxInt64, MinInt64) - 1234567890, -1234567890, - } +type MetricResult struct { + Name string + Iterations int + DurationNs int64 + AllocDelta int64 +} - for _, v := range values { - t.Run(ufmt.Sprintf("RoundTrip_%d", v), func(t *testing.T) { - z := new(Int) - z.SetInt64(v) +// runMetric executes fn the given number of iterations while logging elapsed time and allocation deltas. +func runMetric(t *testing.T, name string, iterations int, fn func()) MetricResult { + t.Helper() - got := z.Int64() - if got != v { - t.Errorf("Round trip failed: SetInt64(%d).Int64() = %d", v, got) - } - }) + // Skip GC if allocator is nil to avoid panic + stats := runtime.MemStats() + if stats != "nil allocator" { + runtime.GC() } -} - -func TestNeg(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "-1"}, - {"-1", "1"}, - {"9223372036854775807", "-9223372036854775807"}, - {"-18446744073709551615", "18446744073709551615"}, + beforeBytes := readAllocatorBytes(t) + start := time.Now() + for i := 0; i < iterations; i++ { + fn() } + elapsed := time.Since(start) + afterBytes := readAllocatorBytes(t) - for _, tc := range tests { - z := MustFromDecimal(tc.x) - z.Neg(z) - - got := z.ToString() - if got != tc.want { - t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) - } + return MetricResult{ + Name: name, + Iterations: iterations, + DurationNs: elapsed.Nanoseconds(), + AllocDelta: afterBytes - beforeBytes, } } -func TestSet(t *testing.T) { - tests := []struct { - x string - want string - }{ - {"0", "0"}, - {"1", "1"}, - {"-1", "-1"}, - {"9223372036854775807", "9223372036854775807"}, - {"-18446744073709551615", "-18446744073709551615"}, - } +func TestPublicFunctionRuntimeMetrics(t *testing.T) { + const iterations = 200 + var results []MetricResult - for _, tc := range tests { - z := MustFromDecimal(tc.x) - z.Set(z) - - got := z.ToString() - if got != tc.want { - t.Errorf("set(%s) = %s, want %s", tc.x, got, tc.want) - } - } -} + // Test data + commonDecimal := "1234567890123456789012345678901234567890" -func TestToString(t *testing.T) { tests := []struct { - name string - setup func() *Int - expected string + name string + run func(t *testing.T) MetricResult }{ { - name: "Zero from subtraction", - setup: func() *Int { - minusThree := MustFromDecimal("-3") - three := MustFromDecimal("3") - return Zero().Add(minusThree, three) + name: "FromDecimal", + run: func(t *testing.T) MetricResult { + return runMetric(t, "FromDecimal", iterations, func() { + if _, err := FromDecimal(commonDecimal); err != nil { + t.Fatalf("FromDecimal error: %v", err) + } + }) }, - expected: "0", }, { - name: "Zero from right shift", - setup: func() *Int { - return Zero().Rsh(One(), 1234) + name: "SetString", + run: func(t *testing.T) MetricResult { + return runMetric(t, "SetString", iterations, func() { + var target Int + if _, err := target.SetString(commonDecimal); err != nil { + t.Fatalf("SetString error: %v", err) + } + }) }, - expected: "0", }, { - name: "Positive number", - setup: func() *Int { - return MustFromDecimal("42") + name: "MustFromDecimal", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MustFromDecimal", iterations, func() { + MustFromDecimal(commonDecimal) + }) }, - expected: "42", }, { - name: "Negative number", - setup: func() *Int { - return MustFromDecimal("-42") + name: "MaxInt256", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MaxInt256", iterations, func() { + MaxInt256() + }) }, - expected: "-42", }, { - name: "Large positive number", - setup: func() *Int { - return MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") + name: "MinInt256", + run: func(t *testing.T) MetricResult { + return runMetric(t, "MinInt256", iterations, func() { + MinInt256() + }) }, - expected: "57896044618658097711785492504343953926634992332820282019728792003956564819967", }, { - name: "Large negative number", - setup: func() *Int { - return MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968") + name: "ToString", + run: func(t *testing.T) MetricResult { + largeInt := MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") // max int256 + return runMetric(t, "ToString", iterations, func() { + _ = largeInt.ToString() + }) }, - expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := tt.setup() - result := z.ToString() - if result != tt.expected { - t.Errorf("ToString() = %s, want %s", result, tt.expected) - } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + result := test.run(t) + results = append(results, result) }) } + + // Print results as markdown table + t.Log("\n## Runtime Metrics Results\n") + t.Log("| Function | Iterations | Duration (ns) | Alloc Delta (bytes) |") + t.Log("|----------|------------|---------------|---------------------|") + for _, result := range results { + t.Logf("| %s | %d | %d | %d |", result.Name, result.Iterations, result.DurationNs, result.AllocDelta) + } } diff --git a/contract/p/gnoswap/int256/int256_test.gno b/contract/p/gnoswap/int256/int256_test.gno index a3aebebc6..3065dd37d 100644 --- a/contract/p/gnoswap/int256/int256_test.gno +++ b/contract/p/gnoswap/int256/int256_test.gno @@ -1,4 +1,3 @@ -// ported from github.com/mempooler/int256 package int256 import ( @@ -27,6 +26,1483 @@ func TestSign(t *testing.T) { } } +func TestAbs(t *testing.T) { + tests := []struct { + x, want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "1"}, + {"-2", "2"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.Abs() + + if got.ToString() != tc.want { + t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) + } + } +} + +func TestAbsWithPanicCases(t *testing.T) { + tests := []struct { + x, want string + wantErr bool + wantPanic bool + }{ + {"0", "0", false, false}, + {"1", "1", false, false}, + {"-1", "1", false, false}, + {"-2", "2", false, false}, + // Int256 min value overflow: out of range, FromDecimal should error + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "", true, false}, + // Int256 most negative value: FromDecimal succeeds, Abs() panics + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819968", false, true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + + // Check if FromDecimal returned expected error + if tc.wantErr { + if err == nil { + t.Errorf("FromDecimal(%s) expected error, but got nil", tc.x) + } + continue + } + + // If we expect no error, but got one + if err != nil { + t.Errorf("FromDecimal(%s) returned unexpected error: %v", tc.x, err) + continue + } + + // At this point, x should not be nil + if x == nil { + t.Errorf("FromDecimal(%s) returned nil without error", tc.x) + continue + } + + if tc.wantPanic { + uassert.PanicsContains(t, "int256: overflow", func() { + x.Abs() + }) + ufmt.Printf("Abs(%s) correctly panicked with overflow\n", tc.x) + } else { + got := x.Abs() + if got.ToString() != tc.want { + t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) + } + } + } +} + +func TestAdd(t *testing.T) { + maxIntStr := MaxInt256().ToString() + + tests := []struct { + x, y, want string + }{ + {"0", "1", "1"}, + {"1", "0", "1"}, + {"1", "1", "2"}, + {"1", "2", "3"}, + // NEGATIVE + {"-1", "1", "0"}, + {"1", "-1", "0"}, + {"3", "-3", "0"}, + {"-1", "-1", "-2"}, + {"-1", "-2", "-3"}, + {"-1", "3", "2"}, + {"3", "-1", "2"}, + // BOUNDARY + {maxIntStr, "0", maxIntStr}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := Zero() + got.Add(x, y) + + if got.Neq(want) { + t.Errorf("Add(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestAddOverflow(t *testing.T) { + maxInt256 := MaxInt256() + minInt256 := MinInt256() + + tests := []struct { + name string + x *Int + y *Int + wantResult string + wantOverflow bool + }{ + // Basic cases (no overflow) + { + name: "positive + positive (no overflow)", + x: NewInt(100), + y: NewInt(200), + wantResult: "300", + wantOverflow: false, + }, + { + name: "negative + negative (no overflow)", + x: NewInt(-100), + y: NewInt(-200), + wantResult: "-300", + wantOverflow: false, + }, + // Boundary cases - near maximum value + { + name: "max_int256 + 0", + x: maxInt256, + y: Zero(), + wantResult: maxInt256.ToString(), + wantOverflow: false, + }, + { + name: "max_int256 - 1 + 1", + x: new(Int).Sub(maxInt256, One()), + y: One(), + wantResult: maxInt256.ToString(), + wantOverflow: false, + }, + { + name: "max_int256 + 1", + x: maxInt256, + y: One(), + wantResult: "", // overflow + wantOverflow: true, + }, + + // Boundary cases - near minimum value + { + name: "min_int256 + 0", + x: minInt256, + y: Zero(), + wantResult: minInt256.ToString(), + wantOverflow: false, + }, + { + name: "min_int256 + 1 - 1", + x: new(Int).Add(minInt256, One()), + y: NewInt(-1), + wantResult: minInt256.ToString(), + wantOverflow: false, + }, + { + name: "min_int256 + (-1)", + x: minInt256, + y: NewInt(-1), + wantResult: "", // overflow + wantOverflow: true, + }, + + // Special cases + { + name: "max_int256 + min_int256", + x: maxInt256, + y: minInt256, + wantResult: "-1", + wantOverflow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := new(Int) + gotResult, gotOverflow := z.AddOverflow(tt.x, tt.y) + + if gotOverflow != tt.wantOverflow { + t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) + return + } + + if !gotOverflow { + if gotResult == nil { + t.Error("unexpected nil result for non-overflow case") + return + } + if gotResult.ToString() != tt.wantResult { + t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) + } + } + + // Commutativity test only for non-overflow cases + if !tt.wantOverflow { + reverseResult, reverseOverflow := z.AddOverflow(tt.y, tt.x) + if reverseOverflow != gotOverflow { + t.Error("addition is not commutative for overflow") + } + if reverseResult.ToString() != gotResult.ToString() { + t.Error("addition is not commutative for result") + } + } + }) + } +} + +func TestSub(t *testing.T) { + maxInt := MaxInt256() + maxIntStr := maxInt.ToString() + maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() + minIntStr := MinInt256().ToString() + + tests := []struct { + x, y, want string + }{ + {"1", "0", "1"}, + {"1", "1", "0"}, + {"-1", "1", "-2"}, + {"1", "-1", "2"}, + {"-1", "-1", "0"}, + {minIntStr, minIntStr, "0"}, + {minIntStr, "0", minIntStr}, + {maxIntStr, maxIntStr, "0"}, + {maxIntMinusOneStr, "-1", maxIntStr}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := Zero() + got.Sub(x, y) + + if got.Neq(want) { + t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestInt_SubOverflow(t *testing.T) { + maxInt256 := MaxInt256() + minInt256 := MinInt256() + + tests := []struct { + name string + x *Int + y *Int + wantResult string + wantOverflow bool + }{ + { + name: "positive - positive (no overflow)", + x: NewInt(200), + y: NewInt(100), + wantResult: "100", + wantOverflow: false, + }, + { + name: "negative - negative (no overflow)", + x: NewInt(-200), + y: NewInt(-300), + wantResult: "100", + wantOverflow: false, + }, + { + name: "positive - negative (no overflow)", + x: NewInt(200), + y: NewInt(-100), + wantResult: "300", + wantOverflow: false, + }, + { + name: "max_int256 - 0", + x: maxInt256, + y: Zero(), + wantResult: maxInt256.ToString(), + wantOverflow: false, + }, + { + name: "min_int256 - 0", + x: minInt256, + y: Zero(), + wantResult: minInt256.ToString(), + wantOverflow: false, + }, + { + name: "max_int256 - (-1)", // max_int256 + 1 -> overflow + x: maxInt256, + y: NewInt(-1), + wantResult: "", + wantOverflow: true, + }, + { + name: "min_int256 - 1", // min_int256 - 1 -> overflow + x: minInt256, + y: One(), + wantResult: "", + wantOverflow: true, + }, + { + name: "0 - 0", + x: Zero(), + y: Zero(), + wantResult: "0", + wantOverflow: false, + }, + { + name: "min_int256 - min_int256", + x: minInt256, + y: minInt256, + wantResult: "0", + wantOverflow: false, + }, + { + name: "max_int256 - max_int256", + x: maxInt256, + y: maxInt256, + wantResult: "0", + wantOverflow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := Zero() + gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) + + if gotOverflow != tt.wantOverflow { + t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) + return + } + + if !gotOverflow { + if gotResult == nil { + t.Error("unexpected nil result for non-overflow case") + return + } + if gotResult.ToString() != tt.wantResult { + t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) + } + } + }) + } +} + +func TestMul(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"5", "3", "15"}, + {"-5", "3", "-15"}, + {"5", "-3", "-15"}, + {"0", "3", "0"}, + {"3", "0", "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := Zero() + got.Mul(x, y) + + if got.Neq(want) { + t.Errorf("Mul(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestInt_MulOverflow(t *testing.T) { + maxInt256 := MaxInt256() + minInt256 := MinInt256() + + tests := []struct { + name string + x *Int + y *Int + wantResult string + wantOverflow bool + }{ + { + name: "positive * positive (no overflow)", + x: NewInt(100), + y: NewInt(100), + wantResult: "10000", + wantOverflow: false, + }, + { + name: "negative * negative (no overflow)", + x: NewInt(-100), + y: NewInt(-100), + wantResult: "10000", + wantOverflow: false, + }, + { + name: "positive * negative (no overflow)", + x: NewInt(100), + y: NewInt(-100), + wantResult: "-10000", + wantOverflow: false, + }, + { + name: "0 * positive", + x: Zero(), + y: NewInt(100), + wantResult: "0", + wantOverflow: false, + }, + { + name: "positive * 0", + x: NewInt(100), + y: Zero(), + wantResult: "0", + wantOverflow: false, + }, + { + name: "0 * 0", + x: Zero(), + y: Zero(), + wantResult: "0", + wantOverflow: false, + }, + { + name: "max_int256 * 1", + x: maxInt256, + y: One(), + wantResult: maxInt256.ToString(), + wantOverflow: false, + }, + { + name: "min_int256 * 1", + x: minInt256, + y: One(), + wantResult: minInt256.ToString(), + wantOverflow: false, + }, + { + name: "min_int256 * -1", + x: minInt256, + y: NewInt(-1), + wantResult: "", // overflow because abs(min_int256) > max_int256 + wantOverflow: true, + }, + { + name: "max_int256 * 2", + x: maxInt256, + y: NewInt(2), + wantResult: "", + wantOverflow: true, + }, + { + name: "min_int256 * 2", + x: minInt256, + y: NewInt(2), + wantResult: "", + wantOverflow: true, + }, + { + name: "half_max * 2", + x: MustFromDecimal("28948022309329048855892746252171976963317496332820282019728792003956564819983"), // (2^255-1)/2 + y: NewInt(2), + wantResult: "", + wantOverflow: true, + }, + { + name: "(half_max + 1) * 2", + x: new(Int).Add(new(Int).Div(maxInt256, NewInt(2)), One()), + y: NewInt(2), + wantResult: "", + wantOverflow: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := new(Int) + gotResult, gotOverflow := z.MulOverflow(tt.x, tt.y) + + if gotOverflow != tt.wantOverflow { + t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) + return + } + + if !gotOverflow { + if gotResult == nil { + t.Error("unexpected nil result for non-overflow case") + return + } + if gotResult.ToString() != tt.wantResult { + t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) + } + } + + if !tt.wantOverflow { + reverseResult, reverseOverflow := z.MulOverflow(tt.y, tt.x) + if reverseOverflow != gotOverflow { + t.Error("multiplication is not commutative for overflow") + } + if reverseResult.ToString() != gotResult.ToString() { + t.Error("multiplication is not commutative for result") + } + } + }) + } +} + +func TestDiv(t *testing.T) { + maxInt := MaxInt256() + maxIntStr := maxInt.ToString() + maxIntHalfStr := new(Int).Rsh(maxInt, 1).ToString() + + tests := []struct { + x, y, expected string + }{ + {"1", "1", "1"}, + {"0", "1", "0"}, + {"-1", "1", "-1"}, + {"1", "-1", "-1"}, + {"-1", "-1", "1"}, + {"-6", "3", "-2"}, + {"10", "-2", "-5"}, + {"-10", "3", "-3"}, + {"7", "3", "2"}, + {"-7", "3", "-2"}, + {maxIntStr, "2", maxIntHalfStr}, // Max int256 / 2 + } + + for _, tt := range tests { + t.Run(tt.x+"/"+tt.y, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + result := Zero().Div(x, y) + if result.ToString() != tt.expected { + t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) + } + if result.IsZero() && result.IsNeg() { + t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) + } + }) + } + + t.Run("Division by zero", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Div(1, 0) did not panic") + } + }() + x := MustFromDecimal("1") + y := MustFromDecimal("0") + Zero().Div(x, y) + }) +} + +func TestOr(t *testing.T) { + tests := []struct { + name string + x, y, want string + }{ + {"all zeroes", "0", "0", "0"}, + {"all ones", "-1", "-1", "-1"}, + {"mixed", "-1", "0", "-1"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + x := MustFromDecimal(tc.x) + y := MustFromDecimal(tc.y) + want := MustFromDecimal(tc.want) + + got := Zero() + got.Or(x, y) + + if got.Neq(want) { + t.Errorf("Or(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) + } + }) + } +} + +func TestAnd(t *testing.T) { + tests := []struct { + name string + x, y, want string + }{ + {"all zeroes", "0", "0", "0"}, + {"all ones", "-1", "-1", "-1"}, + {"mixed", "0", "-1", "0"}, + {"mixed 2", "-1", "0", "0"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + x := MustFromDecimal(tc.x) + y := MustFromDecimal(tc.y) + want := MustFromDecimal(tc.want) + + got := Zero() + got.And(x, y) + + if got.Neq(want) { + t.Errorf("And(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) + } + }) + } +} + +func TestLsh(t *testing.T) { + tests := []struct { + name string + x string + n uint + expected string + }{ + { + name: "n is equal 0", + x: "100", + n: 0, + expected: "100", + }, + { + name: "n is greater than or equal 256", + x: "-4125871947195612497219427349", + n: 256, + expected: "0", + }, + { + name: "x is non-neg & n is smaller than 256 and greater than or equal 192", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 200, + expected: "-1606938044258990275541962092341162602522202993782792835301376", + }, + { + name: "x is non-neg & n is smaller than 192 and greater than or equal 128", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 150, + expected: "-1427247692705959881058285969449495136382746624", + }, + { + name: "x is non-neg & n is smaller than 128 and greater than or equal 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 100, + expected: "-1267650600228229401496703205376", + }, + { + name: "x is non-neg & n is smaller than 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 32, + expected: "-4294967296", + }, + { + name: "x is neg & n is smaller than 256 and greater than or equal 192", + x: "-11111", + n: 200, + expected: "-17854688609761640951546740808002657676624197463920611193033588736", + }, + { + name: "x is neg & n is smaller than 192 and greater than or equal 128", + x: "-11111", + n: 150, + expected: "-15858149113655920238438615406553340460348697739264", + }, + { + name: "x is neg & n is smaller than 128 and greater than or equal 64", + x: "-11111", + n: 100, + expected: "-14084865819135856880029869314932736", + }, + { + name: "x is neg & n is smaller than 64", + x: "-11111", + n: 32, + expected: "-47721381625856", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + z := new(Int).Lsh(x, tt.n) + uassert.Equal(t, tt.expected, z.ToString()) + }) + } +} + +func TestRsh(t *testing.T) { + tests := []struct { + name string + x string + n uint + expected string + }{ + { + name: "n is equal 0", + x: "100", + n: 0, + expected: "100", + }, + { + name: "x is non-neg & n is greater than or equal 255", + x: "431247391574329147932", + n: 255, + expected: "0", + }, + { + name: "x is non-neg & n is smaller than 256 and greater than or equal 192", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 200, + expected: "36028797018963967", + }, + { + name: "x is non-neg & n is smaller than 192 and greater than or equal 128", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 150, + expected: "40564819207303340847894502572031", + }, + { + name: "x is non-neg & n is smaller than 128 and greater than or equal 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 100, + expected: "45671926166590716193865151022383844364247891967", + }, + { + name: "x is non-neg & n is smaller than 64", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + n: 32, + expected: "13479973333575319897333507543509815336818572211270286240551805124607", + }, + { + name: "x is neg & n is smaller than 256 and greater than or equal 192", + x: "-11111", + n: 200, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 192 and greater than or equal 128", + x: "-11111", + n: 150, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 128 and greater than or equal 64", + x: "-11111", + n: 100, + expected: "-1", + }, + { + name: "x is neg & n is smaller than 64", + x: "-11111", + n: 2, + expected: "-2778", + }, + { + name: "x is neg & n is greater than or equal 255", + x: "-11111", + n: 255, + expected: "-1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + z := new(Int).Rsh(x, tt.n) + uassert.Equal(t, tt.expected, z.ToString()) + }) + } +} + +func TestLsh_NoPanic(t *testing.T) { + x := MustFromDecimal("-12345678901234567890") + + for n := uint(0); n < 300; n++ { + uassert.NotPanics(t, func() { + _ = new(Int).Lsh(x, n) + }) + } +} + +func TestRsh_NoPanic(t *testing.T) { + x := MustFromDecimal("-12345678901234567890") + + for n := uint(0); n < 300; n++ { + uassert.NotPanics(t, func() { + _ = new(Int).Rsh(x, n) + }) + } +} + +func TestRsh_SignExtendProperty(t *testing.T) { + // For all negative integers, Rsh should satisfy: + // - Rsh(x, n) <= 0 + // - Rsh(x, n+1) == Rsh(Rsh(x,n), 1) + x := MustFromDecimal("-11111") + + for n := uint(0); n < 255; n++ { + z := new(Int).Rsh(x, n) + zz := new(Int).Rsh(z, 1) + z2 := new(Int).Rsh(x, n+1) + + uassert.Equal(t, z2.ToString(), zz.ToString()) + } +} + +func TestEq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", true}, + {"0", "1", false}, + {"1", "0", false}, + {"-1", "0", false}, + {"0", "-1", false}, + {"1", "1", true}, + {"-1", "-1", true}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Eq(y) + if got != tc.want { + t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestNeq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", true}, + {"-1", "0", true}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Neq(y) + if got != tc.want { + t.Errorf("Neq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestCmp(t *testing.T) { + tests := []struct { + x, y string + want int + }{ + {"0", "0", 0}, + {"0", "1", -1}, + {"1", "0", 1}, + {"-1", "0", -1}, + {"0", "-1", 1}, + {"1", "1", 0}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", 1}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Cmp(y) + if got != tc.want { + t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", true}, + {"-0", true}, + {"1", false}, + {"-1", false}, + {"10", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsZero() + if got != tc.want { + t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestIsNeg(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", false}, + {"-0", false}, // In two's complement, -0 is same as 0 + {"1", false}, + {"-1", true}, + {"10", false}, + {"-10", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsNeg() + if got != tc.want { + t.Errorf("IsNeg(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestLt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", false}, + {"-1", "0", true}, + {"0", "-1", false}, + {"1", "1", false}, + {"-1", "-1", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Lt(y) + if got != tc.want { + t.Errorf("Lt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestGt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", false}, + {"1", "0", true}, + {"-1", "0", false}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Gt(y) + if got != tc.want { + t.Errorf("Gt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestClone(t *testing.T) { + tests := []struct { + x string + }{ + {"0"}, + {"-0"}, + {"1"}, + {"-1"}, + {"10"}, + {"-10"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y := x.Clone() + + if x.Cmp(y) != 0 { + t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) + } + } +} + +func TestNilChecks(t *testing.T) { + validInt := NewInt(123) + + tests := []struct { + name string + fn func() + }{ + { + name: "Eq with nil", + fn: func() { validInt.Eq(nil) }, + }, + { + name: "Neq with nil", + fn: func() { validInt.Neq(nil) }, + }, + { + name: "Cmp with nil", + fn: func() { validInt.Cmp(nil) }, + }, + { + name: "Lt with nil", + fn: func() { validInt.Lt(nil) }, + }, + { + name: "Gt with nil", + fn: func() { validInt.Gt(nil) }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.Errorf("%s: expected panic but got none", tt.name) + } + // Any panic (nil pointer or custom message) is acceptable + }() + + tt.fn() + }) + } +} + +func TestSetInt64(t *testing.T) { + tests := []struct { + x int64 + want string + }{ + {0, "0"}, + {1, "1"}, + {-1, "-1"}, + {9223372036854775807, "9223372036854775807"}, + {-9223372036854775808, "-9223372036854775808"}, + } + + for _, tc := range tests { + var z Int + z.SetInt64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSetInt64MinValueOverflow(t *testing.T) { + const minInt64 = -9223372036854775808 // -2^63 + const maxInt64 = 9223372036854775807 // 2^63 - 1 + + tests := []struct { + name string + x int64 + want string + }{ + { + name: "MinInt64 should not cause overflow", + x: minInt64, + want: "-9223372036854775808", + }, + { + name: "MaxInt64 works correctly", + x: maxInt64, + want: "9223372036854775807", + }, + { + name: "MinInt64 + 1", + x: minInt64 + 1, + want: "-9223372036854775807", + }, + { + name: "Negative one", + x: -1, + want: "-1", + }, + { + name: "Zero", + x: 0, + want: "0", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var z Int + z.SetInt64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + } + + // Verify sign is correct + if tc.x < 0 { + if !z.IsNeg() { + t.Errorf("SetInt64(%d): expected IsNegative()=true, got false", tc.x) + } + } else if tc.x > 0 { + if z.IsNeg() { + t.Errorf("SetInt64(%d): expected IsNegative()=false, got true", tc.x) + } + } else { // tc.x == 0 + if z.IsNeg() { + t.Errorf("SetInt64(0): expected IsNegative()=false (no -0), got true") + } + } + }) + } +} + +func TestSetUint64(t *testing.T) { + tests := []struct { + x uint64 + want string + }{ + {0, "0"}, + {1, "1"}, + } + + for _, tc := range tests { + var z Int + z.SetUint64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestUint64(t *testing.T) { + tests := []struct { + x string + want uint64 + }{ + {"0", 0}, + {"1", 1}, + {"9223372036854775807", 9223372036854775807}, + {"9223372036854775808", 9223372036854775808}, + {"18446744073709551615", 18446744073709551615}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + // In two's complement, -1 = 0xFFFF...FFFF, so z[0] = max uint64 + {"-1", 18446744073709551615}, + {"-18446744073709551615", 1}, + {"-18446744073709551616", 0}, + {"-18446744073709551617", 18446744073709551615}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Uint64() + if got != tc.want { + t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestInt64(t *testing.T) { + tests := []struct { + x string + want int64 + }{ + {"0", 0}, + {"1", 1}, + {"-1", -1}, + {"9223372036854775807", 9223372036854775807}, + {"-9223372036854775808", -9223372036854775808}, + {"9223372036854775808", -9223372036854775808}, + {"-9223372036854775809", 9223372036854775807}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + {"18446744073709551615", -1}, + {"-18446744073709551615", 1}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Int64() + if got != tc.want { + t.Errorf("Int64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestInt64EdgeCases(t *testing.T) { + const minInt64 = -9223372036854775808 // -2^63 + const maxInt64 = 9223372036854775807 // 2^63 - 1 + + tests := []struct { + name string + setupInt func() *Int + want int64 + description string + }{ + { + name: "MinInt64 from SetInt64", + setupInt: func() *Int { + z := new(Int) + return z.SetInt64(minInt64) + }, + want: minInt64, + description: "SetInt64(MinInt64) should round-trip correctly", + }, + { + name: "MaxInt64 from SetInt64", + setupInt: func() *Int { + z := new(Int) + return z.SetInt64(maxInt64) + }, + want: maxInt64, + description: "SetInt64(MaxInt64) should round-trip correctly", + }, + { + name: "Zero", + setupInt: func() *Int { + return Zero() + }, + want: 0, + description: "Zero should return 0", + }, + { + name: "Large positive int256", + setupInt: func() *Int { + // Value larger than int64 max + return MustFromDecimal("18446744073709551615") + }, + want: -1, // Truncated to int64 + description: "Large positive wraps in int64", + }, + { + name: "Large negative int256", + setupInt: func() *Int { + return MustFromDecimal("-18446744073709551615") + }, + want: 1, // Truncated to int64 + description: "Large negative wraps in int64", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + z := tc.setupInt() + got := z.Int64() + + if got != tc.want { + t.Errorf("%s: got %d, want %d", tc.description, got, tc.want) + } + }) + } +} + +// TestInt64RoundTrip verifies that SetInt64 and Int64 work correctly together +func TestInt64RoundTrip(t *testing.T) { + // Test all interesting int64 values + values := []int64{ + 0, 1, -1, + 127, -128, // int8 boundaries + 32767, -32768, // int16 boundaries + 2147483647, -2147483648, // int32 boundaries + 9223372036854775807, -9223372036854775808, // int64 boundaries (MaxInt64, MinInt64) + 1234567890, -1234567890, + } + + for _, v := range values { + t.Run(ufmt.Sprintf("RoundTrip_%d", v), func(t *testing.T) { + z := new(Int) + z.SetInt64(v) + + got := z.Int64() + if got != v { + t.Errorf("Round trip failed: SetInt64(%d).Int64() = %d", v, got) + } + }) + } +} + +func TestNeg(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "-1"}, + {"-1", "1"}, + {"9223372036854775807", "-9223372036854775807"}, + {"-18446744073709551615", "18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Neg(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSet(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "-1"}, + {"9223372036854775807", "9223372036854775807"}, + {"-18446744073709551615", "-18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Set(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("set(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + func TestSetString(t *testing.T) { tests := []struct { input string @@ -90,55 +1566,65 @@ func TestSetString(t *testing.T) { } } -func TestAbsWithPanicCases(t *testing.T) { +func TestToString(t *testing.T) { tests := []struct { - x, want string - wantErr bool - wantPanic bool + name string + setup func() *Int + expected string }{ - {"0", "0", false, false}, - {"1", "1", false, false}, - {"-1", "1", false, false}, - {"-2", "2", false, false}, - // Int256 min value overflow: out of range, FromDecimal should error - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "", true, false}, - // Int256 most negative value: FromDecimal succeeds, Abs() panics - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819968", false, true}, + { + name: "Zero from subtraction", + setup: func() *Int { + minusThree := MustFromDecimal("-3") + three := MustFromDecimal("3") + return Zero().Add(minusThree, three) + }, + expected: "0", + }, + { + name: "Zero from right shift", + setup: func() *Int { + return Zero().Rsh(One(), 1234) + }, + expected: "0", + }, + { + name: "Positive number", + setup: func() *Int { + return MustFromDecimal("42") + }, + expected: "42", + }, + { + name: "Negative number", + setup: func() *Int { + return MustFromDecimal("-42") + }, + expected: "-42", + }, + { + name: "Large positive number", + setup: func() *Int { + return MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") + }, + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + }, + { + name: "Large negative number", + setup: func() *Int { + return MustFromDecimal("-57896044618658097711785492504343953926634992332820282019728792003956564819968") + }, + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - - // Check if FromDecimal returned expected error - if tc.wantErr { - if err == nil { - t.Errorf("FromDecimal(%s) expected error, but got nil", tc.x) - } - continue - } - - // If we expect no error, but got one - if err != nil { - t.Errorf("FromDecimal(%s) returned unexpected error: %v", tc.x, err) - continue - } - - // At this point, x should not be nil - if x == nil { - t.Errorf("FromDecimal(%s) returned nil without error", tc.x) - continue - } - - if tc.wantPanic { - uassert.PanicsContains(t, "int256: overflow", func() { - x.Abs() - }) - ufmt.Printf("Abs(%s) correctly panicked with overflow\n", tc.x) - } else { - got := x.Abs() - if got.ToString() != tc.want { - t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := tt.setup() + result := z.ToString() + if result != tt.expected { + t.Errorf("ToString() = %s, want %s", result, tt.expected) } - } + }) } } diff --git a/contract/p/gnoswap/int256/runtime_metrics_test.gno b/contract/p/gnoswap/int256/runtime_metrics_test.gno deleted file mode 100644 index 4fb9cbd3e..000000000 --- a/contract/p/gnoswap/int256/runtime_metrics_test.gno +++ /dev/null @@ -1,165 +0,0 @@ -package int256 - -import ( - "runtime" - "strconv" - "strings" - "testing" - "time" -) - -// readAllocatorBytes parses runtime.MemStats() into the current allocated bytes. -func readAllocatorBytes(t *testing.T) int64 { - t.Helper() - stats := runtime.MemStats() - if stats == "nil allocator" { - return 0 - } - if !strings.HasPrefix(stats, "Allocator{") || !strings.HasSuffix(stats, "}") { - t.Fatalf("unexpected runtime.MemStats output: %q", stats) - } - body := strings.TrimSuffix(strings.TrimPrefix(stats, "Allocator{"), "}") - parts := strings.Split(body, ", ") - if len(parts) != 2 { - t.Fatalf("unexpected runtime.MemStats content: %q", stats) - } - var ( - bytes int64 - found bool - ) - for _, part := range parts { - fields := strings.Split(part, ":") - if len(fields) != 2 { - t.Fatalf("unexpected runtime.MemStats pair %q", part) - } - if fields[0] == "bytes" { - val, err := strconv.ParseInt(fields[1], 10, 64) - if err != nil { - t.Fatalf("failed to parse bytes from %q: %v", part, err) - } - bytes = val - found = true - break - } - } - if !found { - t.Fatalf("bytes key not found in runtime.MemStats output: %q", stats) - } - return bytes -} - -type MetricResult struct { - Name string - Iterations int - DurationNs int64 - AllocDelta int64 -} - -// runMetric executes fn the given number of iterations while logging elapsed time and allocation deltas. -func runMetric(t *testing.T, name string, iterations int, fn func()) MetricResult { - t.Helper() - - // Skip GC if allocator is nil to avoid panic - stats := runtime.MemStats() - if stats != "nil allocator" { - runtime.GC() - } - beforeBytes := readAllocatorBytes(t) - start := time.Now() - for i := 0; i < iterations; i++ { - fn() - } - elapsed := time.Since(start) - afterBytes := readAllocatorBytes(t) - - return MetricResult{ - Name: name, - Iterations: iterations, - DurationNs: elapsed.Nanoseconds(), - AllocDelta: afterBytes - beforeBytes, - } -} - -func TestPublicFunctionRuntimeMetrics(t *testing.T) { - const iterations = 200 - var results []MetricResult - - // Test data - commonDecimal := "1234567890123456789012345678901234567890" - - tests := []struct { - name string - run func(t *testing.T) MetricResult - }{ - { - name: "FromDecimal", - run: func(t *testing.T) MetricResult { - return runMetric(t, "FromDecimal", iterations, func() { - if _, err := FromDecimal(commonDecimal); err != nil { - t.Fatalf("FromDecimal error: %v", err) - } - }) - }, - }, - { - name: "SetString", - run: func(t *testing.T) MetricResult { - return runMetric(t, "SetString", iterations, func() { - var target Int - if _, err := target.SetString(commonDecimal); err != nil { - t.Fatalf("SetString error: %v", err) - } - }) - }, - }, - { - name: "MustFromDecimal", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MustFromDecimal", iterations, func() { - MustFromDecimal(commonDecimal) - }) - }, - }, - { - name: "MaxInt256", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MaxInt256", iterations, func() { - MaxInt256() - }) - }, - }, - { - name: "MinInt256", - run: func(t *testing.T) MetricResult { - return runMetric(t, "MinInt256", iterations, func() { - MinInt256() - }) - }, - }, - { - name: "ToString", - run: func(t *testing.T) MetricResult { - largeInt := MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967") // max int256 - return runMetric(t, "ToString", iterations, func() { - _ = largeInt.ToString() - }) - }, - }, - } - - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - result := test.run(t) - results = append(results, result) - }) - } - - // Print results as markdown table - t.Log("\n## Runtime Metrics Results\n") - t.Log("| Function | Iterations | Duration (ns) | Alloc Delta (bytes) |") - t.Log("|----------|------------|---------------|---------------------|") - for _, result := range results { - t.Logf("| %s | %d | %d | %d |", result.Name, result.Iterations, result.DurationNs, result.AllocDelta) - } -} From 10346fb0595e1984ea22617342e8a0b94d3ecf32 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Mon, 1 Dec 2025 14:21:22 +0900 Subject: [PATCH 20/28] refactor(int256): remove unused functions Remove functions that are not used externally: - Pow: power calculation - Sqrt: square root calculation - WriteToArray32: byte array conversion --- contract/p/gnoswap/int256/int256.gno | 51 ---------------------------- 1 file changed, 51 deletions(-) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index a04285f47..d2ec1ce95 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -381,19 +381,6 @@ func (z *Int) urem(x, y *Int) *Int { return z.Set(&rem) } -func (z *Int) Pow(x *Int, n uint64) *Int { - c := x.Clone() - z.SetOne() - for n > 0 { - if n&1 == 1 { - z.Mul(z, c) - } - n >>= 1 - c.Mul(c, c) - } - return z -} - func (z *Int) Lt(x *Int) bool { return z.Cmp(x) < 0 } @@ -603,37 +590,6 @@ func (z *Int) negRsh(x *Int, n uint) *Int { return z } -func (z *Int) Sqrt(x *Int) *Int { - if x.IsNeg() { - panic(ErrNegativeNum) - } - if x.IsInt64() { - return z.SetInt64(int64(math.Sqrt(float64(x.Int64())))) - } - var ( - z1 = new(Int).SetOne() - z2 = new(Int) - ) - z1 = z1.Lsh(z1, uint(x.BitLen()+1)>>1) - for { - z2 = z2.Div(x, z1) - z2 = z2.Add(z2, z1) - { - a := z2[3] << 63 - z2[3] = z2[3] >> 1 - b := z2[2] << 63 - z2[2] = (z2[2] >> 1) | a - a = z2[1] << 63 - z2[1] = (z2[1] >> 1) | b - z2[0] = (z2[0] >> 1) | a - } - if z2.Cmp(z1) >= 0 { - return z.Set(z1) - } - z1, z2 = z2, z1 - } -} - func (z *Int) BitLen() int { switch { case z[3] != 0: @@ -656,13 +612,6 @@ func (z *Int) SetBytes32(in []byte) *Int { return z } -// WriteToArray32 writes all 32 bytes of z to the destination array, including zero-bytes -func (z *Int) WriteToArray32(dest *[32]byte) { - for i := 0; i < 32; i++ { - dest[31-i] = byte(z[i/8] >> uint64(8*(i%8))) - } -} - func (z *Int) NilToZero() *Int { if z == nil { return Zero() From b6055eaddee7c5859f82421663cb158c45415a68 Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Mon, 1 Dec 2025 14:57:25 +0900 Subject: [PATCH 21/28] refactor: remove unused error constant --- contract/p/gnoswap/int256/conversion.gno | 2 -- 1 file changed, 2 deletions(-) diff --git a/contract/p/gnoswap/int256/conversion.gno b/contract/p/gnoswap/int256/conversion.gno index 34161602e..6fafeb889 100644 --- a/contract/p/gnoswap/int256/conversion.gno +++ b/contract/p/gnoswap/int256/conversion.gno @@ -16,7 +16,6 @@ const ( var ( ErrOverflow = errors.New("int256: overflow") ErrEmptyString = errors.New("int256: empty string") - ErrInvalidCharacter = errors.New("int256: invalid character") ErrInvalidSignInMiddleOfNumber = errors.New("int256: invalid sign in middle of number") multipliers = [5]*Int{ @@ -228,4 +227,3 @@ func FromUint256(x *u256.Uint) *Int { z[3] = x[3] return z } - From 049da0504a1e86094fa61f6fdfa4d6a109d8db86 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 1 Dec 2025 15:08:01 +0900 Subject: [PATCH 22/28] test: add unit tests for public functions --- contract/p/gnoswap/int256/arithmetic_test.gno | 392 +++++ contract/p/gnoswap/int256/int256_test.gno | 1264 ++++++++++------- 2 files changed, 1171 insertions(+), 485 deletions(-) create mode 100644 contract/p/gnoswap/int256/arithmetic_test.gno diff --git a/contract/p/gnoswap/int256/arithmetic_test.gno b/contract/p/gnoswap/int256/arithmetic_test.gno new file mode 100644 index 000000000..a0efea9a8 --- /dev/null +++ b/contract/p/gnoswap/int256/arithmetic_test.gno @@ -0,0 +1,392 @@ +package int256 + +import ( + "math" + "testing" + + "gno.land/p/nt/uassert" +) + +func TestNewInt(t *testing.T) { + tests := []struct { + name string + v int64 + }{ + { + name: "positive", + v: int64(math.MaxInt64), + }, + { + name: "negative", + v: int64(math.MinInt64), + }, + { + name: "zero", + v: int64(0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := NewInt(tt.v) + uassert.True(t, z.IsInt64()) + uassert.Equal(t, tt.v, z.Int64()) + }) + } +} + +func TestGetSetInt64(t *testing.T) { + tests := []struct { + name string + v int64 + }{ + { + name: "positive", + v: int64(math.MaxInt64), + }, + { + name: "negative", + v: int64(math.MinInt64), + }, + { + name: "zero", + v: int64(0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := new(Int).SetInt64(tt.v) + uassert.Equal(t, tt.v, z.Int64()) + }) + } +} + +func TestIsInt64(t *testing.T) { + tests := []struct { + name string + x string + expected bool + }{ + { + name: "max int64", + x: "9223372036854775807", + expected: true, + }, + { + name: "min int64", + x: "-9223372036854775808", + expected: true, + }, + { + name: "max int64 + 1", + x: "9223372036854775808", + expected: false, + }, + { + name: "min int64 - 1", + x: "-9223372036854775809", + expected: false, + }, + { + name: "zero", + x: "0", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + uassert.Equal(t, tt.expected, x.IsInt64()) + }) + } +} + +func TestIsUint64(t *testing.T) { + tests := []struct { + name string + x string + expected bool + }{ + { + name: "max uint64", + x: "18446744073709551615", + expected: true, + }, + { + name: "max uint64 + 1", + x: "18446744073709551616", + expected: false, + }, + { + name: "zero", + x: "0", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + uassert.Equal(t, tt.expected, x.IsUint64()) + }) + } +} + +func TestRem(t *testing.T) { + tests := []struct { + name string + x string + y string + expected string + shouldPanic bool + }{ + { + name: "positive % negative", + x: "3", + y: "-2", + expected: "1", + }, + { + name: "min negative % positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "35719473219571942749314729421", + expected: "-34167184328512991083512640217", + }, + { + name: "max positive % negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-35719473219571942749314729421", + expected: "34167184328512991083512640216", + }, + { + name: "large negative % large negative", + x: "-43179374921751324719573491471294", + y: "-43127519734921524", + expected: "-22155214380278890", + }, + { + name: "zero % negative", + x: "0", + y: "-2", + expected: "0", + }, + { + name: "large positive % smaller positive", + x: "4372195701247205721942146816424", + y: "1974924792517421647328142", + expected: "549633341738318150337156", + }, + { + name: "same values", + x: "1974924792517421647328142", + y: "1974924792517421647328142", + expected: "0", + }, + { + name: "remainder by zero", + x: "1", + y: "0", + expected: "", + shouldPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Error("Rem should panic on division by zero") + } + }() + } + + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Rem(x, y) + if !tt.shouldPanic { + uassert.Equal(t, tt.expected, z.ToString()) + } + }) + } +} + +func TestIsPositive(t *testing.T) { + tests := []struct { + name string + x string + expected bool + }{ + { + name: "positive number", + x: "100", + expected: true, + }, + { + name: "negative number", + x: "-100", + expected: false, + }, + { + name: "zero", + x: "0", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + uassert.Equal(t, tt.expected, x.IsPositive()) + }) + } +} + +func TestLte(t *testing.T) { + tests := []struct { + name string + x string + y string + expected bool + }{ + { + name: "min negative <= max positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: true, + }, + { + name: "equal values", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: true, + }, + { + name: "max positive > min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + uassert.Equal(t, tt.expected, x.Lte(y)) + }) + } +} + +func TestGte(t *testing.T) { + tests := []struct { + name string + x string + y string + expected bool + }{ + { + name: "min negative < max positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: false, + }, + { + name: "equal values", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: true, + }, + { + name: "max positive >= min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + uassert.Equal(t, tt.expected, x.Gte(y)) + }) + } +} + +func TestXor(t *testing.T) { + tests := []struct { + name string + x string + y string + expected string + }{ + { + name: "min negative ^ negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-100", + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819868", + }, + { + name: "max positive ^ positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "41457129491534261876432718654783265437285638275", + expected: "57896044618658097711785492504302496797143458070943849301074008738519279181692", + }, + { + name: "min negative ^ max positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "-1", + }, + { + name: "min negative ^ zero", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "0", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Xor(x, y) + uassert.Equal(t, tt.expected, z.ToString()) + }) + } +} + +func TestNot(t *testing.T) { + tests := []struct { + name string + x string + expected string + }{ + { + name: "positive number", + x: "100", + expected: "-101", + }, + { + name: "negative number", + x: "-100", + expected: "99", + }, + { + name: "zero", + x: "0", + expected: "-1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + z := new(Int).Not(x) + uassert.Equal(t, tt.expected, z.ToString()) + }) + } +} diff --git a/contract/p/gnoswap/int256/int256_test.gno b/contract/p/gnoswap/int256/int256_test.gno index 3065dd37d..3faf1bc64 100644 --- a/contract/p/gnoswap/int256/int256_test.gno +++ b/contract/p/gnoswap/int256/int256_test.gno @@ -107,597 +107,857 @@ func TestAbsWithPanicCases(t *testing.T) { } func TestAdd(t *testing.T) { - maxIntStr := MaxInt256().ToString() - tests := []struct { - x, y, want string + name string + x string + y string + expected string }{ - {"0", "1", "1"}, - {"1", "0", "1"}, - {"1", "1", "2"}, - {"1", "2", "3"}, - // NEGATIVE - {"-1", "1", "0"}, - {"1", "-1", "0"}, - {"3", "-3", "0"}, - {"-1", "-1", "-2"}, - {"-1", "-2", "-3"}, - {"-1", "3", "2"}, - {"3", "-1", "2"}, - // BOUNDARY - {maxIntStr, "0", maxIntStr}, + { + name: "large negative + small negative overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564606844", + y: "-431294739547329532759", + expected: "57896044618658097711785492504343953926634992332820282019297497264409235500333", + }, + { + name: "min negative + min negative overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", + }, + { + name: "max positive + min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-1", + }, + { + name: "small positive + large negative", + x: "10000", + y: "-2000000", + expected: "-1990000", + }, + { + name: "max positive + max positive overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "-2", + }, + { + name: "large positive + large positive overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819961", + y: "431405283104328105143242031570231414", + expected: "-57896044618658097711785492504343953926634560927537177691623648761924994588561", + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Add(x, y) - - if got.Neq(want) { - t.Errorf("Add(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Add(x, y) + uassert.Equal(t, tt.expected, z.ToString()) + }) } } func TestAddOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool + name string + x string + y string + expected string + expectedOverflow bool }{ - // Basic cases (no overflow) { - name: "positive + positive (no overflow)", - x: NewInt(100), - y: NewInt(200), - wantResult: "300", - wantOverflow: false, + name: "large negative + small negative overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564606844", + y: "-431294739547329532759", + expected: "57896044618658097711785492504343953926634992332820282019297497264409235500333", + expectedOverflow: true, }, { - name: "negative + negative (no overflow)", - x: NewInt(-100), - y: NewInt(-200), - wantResult: "-300", - wantOverflow: false, + name: "min negative + min negative overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", + expectedOverflow: true, }, - // Boundary cases - near maximum value { - name: "max_int256 + 0", - x: maxInt256, - y: Zero(), - wantResult: maxInt256.ToString(), - wantOverflow: false, + name: "max positive + min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-1", + expectedOverflow: false, }, { - name: "max_int256 - 1 + 1", - x: new(Int).Sub(maxInt256, One()), - y: One(), - wantResult: maxInt256.ToString(), - wantOverflow: false, + name: "small positive + large negative", + x: "10000", + y: "-2000000", + expected: "-1990000", + expectedOverflow: false, }, { - name: "max_int256 + 1", - x: maxInt256, - y: One(), - wantResult: "", // overflow - wantOverflow: true, + name: "max positive + max positive overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "-2", + expectedOverflow: true, }, + { + name: "large positive + large positive overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819961", + y: "431405283104328105143242031570231414", + expected: "-57896044618658097711785492504343953926634560927537177691623648761924994588561", + expectedOverflow: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z, overflow := new(Int).AddOverflow(x, y) + uassert.Equal(t, tt.expected, z.ToString()) + uassert.Equal(t, tt.expectedOverflow, overflow) + }) + } +} + +// func TestSub(t *testing.T) { +// maxInt := MaxInt256() +// maxIntStr := maxInt.ToString() +// maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() +// minIntStr := MinInt256().ToString() + +// tests := []struct { +// x, y, want string +// }{ +// {"1", "0", "1"}, +// {"1", "1", "0"}, +// {"-1", "1", "-2"}, +// {"1", "-1", "2"}, +// {"-1", "-1", "0"}, +// {minIntStr, minIntStr, "0"}, +// {minIntStr, "0", minIntStr}, +// {maxIntStr, maxIntStr, "0"}, +// {maxIntMinusOneStr, "-1", maxIntStr}, +// } + +// for _, tc := range tests { +// x, err := FromDecimal(tc.x) +// if err != nil { +// t.Error(err) +// continue +// } + +// y, err := FromDecimal(tc.y) +// if err != nil { +// t.Error(err) +// continue +// } + +// want, err := FromDecimal(tc.want) +// if err != nil { +// t.Error(err) +// continue +// } + +// got := Zero() +// got.Sub(x, y) + +// if got.Neq(want) { +// t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) +// } +// } +// } - // Boundary cases - near minimum value +func TestSub(t *testing.T) { + tests := []struct { + name string + x string + y string + expected string + }{ { - name: "min_int256 + 0", - x: minInt256, - y: Zero(), - wantResult: minInt256.ToString(), - wantOverflow: false, + name: "min negative - positive underflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "100", + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819868", }, { - name: "min_int256 + 1 - 1", - x: new(Int).Add(minInt256, One()), - y: NewInt(-1), - wantResult: minInt256.ToString(), - wantOverflow: false, + name: "zero - positive", + x: "0", + y: "9999", + expected: "-9999", }, { - name: "min_int256 + (-1)", - x: minInt256, - y: NewInt(-1), - wantResult: "", // overflow - wantOverflow: true, + name: "max positive - min negative overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-1", }, - - // Special cases { - name: "max_int256 + min_int256", - x: maxInt256, - y: minInt256, - wantResult: "-1", - wantOverflow: false, + name: "min negative - max positive underflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.AddOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - // Commutativity test only for non-overflow cases - if !tt.wantOverflow { - reverseResult, reverseOverflow := z.AddOverflow(tt.y, tt.x) - if reverseOverflow != gotOverflow { - t.Error("addition is not commutative for overflow") - } - if reverseResult.ToString() != gotResult.ToString() { - t.Error("addition is not commutative for result") - } - } + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Sub(x, y) + uassert.Equal(t, tt.expected, z.ToString()) }) } } -func TestSub(t *testing.T) { - maxInt := MaxInt256() - maxIntStr := maxInt.ToString() - maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() - minIntStr := MinInt256().ToString() - +// func TestInt_SubOverflow(t *testing.T) { +// maxInt256 := MaxInt256() +// minInt256 := MinInt256() + +// tests := []struct { +// name string +// x *Int +// y *Int +// wantResult string +// wantOverflow bool +// }{ +// { +// name: "positive - positive (no overflow)", +// x: NewInt(200), +// y: NewInt(100), +// wantResult: "100", +// wantOverflow: false, +// }, +// { +// name: "negative - negative (no overflow)", +// x: NewInt(-200), +// y: NewInt(-300), +// wantResult: "100", +// wantOverflow: false, +// }, +// { +// name: "positive - negative (no overflow)", +// x: NewInt(200), +// y: NewInt(-100), +// wantResult: "300", +// wantOverflow: false, +// }, +// { +// name: "max_int256 - 0", +// x: maxInt256, +// y: Zero(), +// wantResult: maxInt256.ToString(), +// wantOverflow: false, +// }, +// { +// name: "min_int256 - 0", +// x: minInt256, +// y: Zero(), +// wantResult: minInt256.ToString(), +// wantOverflow: false, +// }, +// { +// name: "max_int256 - (-1)", // max_int256 + 1 -> overflow +// x: maxInt256, +// y: NewInt(-1), +// wantResult: "", +// wantOverflow: true, +// }, +// { +// name: "min_int256 - 1", // min_int256 - 1 -> overflow +// x: minInt256, +// y: One(), +// wantResult: "", +// wantOverflow: true, +// }, +// { +// name: "0 - 0", +// x: Zero(), +// y: Zero(), +// wantResult: "0", +// wantOverflow: false, +// }, +// { +// name: "min_int256 - min_int256", +// x: minInt256, +// y: minInt256, +// wantResult: "0", +// wantOverflow: false, +// }, +// { +// name: "max_int256 - max_int256", +// x: maxInt256, +// y: maxInt256, +// wantResult: "0", +// wantOverflow: false, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// z := Zero() +// gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) + +// if gotOverflow != tt.wantOverflow { +// t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) +// return +// } + +// if !gotOverflow { +// if gotResult == nil { +// t.Error("unexpected nil result for non-overflow case") +// return +// } +// if gotResult.ToString() != tt.wantResult { +// t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) +// } +// } +// }) +// } +// } + +func TestSubOverflow(t *testing.T) { tests := []struct { - x, y, want string + name string + x string + y string + expected string + expectedOverflow bool }{ - {"1", "0", "1"}, - {"1", "1", "0"}, - {"-1", "1", "-2"}, - {"1", "-1", "2"}, - {"-1", "-1", "0"}, - {minIntStr, minIntStr, "0"}, - {minIntStr, "0", minIntStr}, - {maxIntStr, maxIntStr, "0"}, - {maxIntMinusOneStr, "-1", maxIntStr}, + { + name: "min negative - positive underflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "100", + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819868", + expectedOverflow: true, + }, + { + name: "zero - positive", + x: "0", + y: "9999", + expected: "-9999", + expectedOverflow: false, + }, + { + name: "max positive - min negative overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-1", + expectedOverflow: true, + }, + { + name: "min negative - max positive underflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "1", + expectedOverflow: true, + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Sub(x, y) - - if got.Neq(want) { - t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z, overflow := new(Int).SubOverflow(x, y) + uassert.Equal(t, tt.expected, z.ToString()) + uassert.Equal(t, tt.expectedOverflow, overflow) + }) } } -func TestInt_SubOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() - +func TestMulOverFlow(t *testing.T) { tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool + name string + x string + y string + expected string + expectedOverflow bool }{ { - name: "positive - positive (no overflow)", - x: NewInt(200), - y: NewInt(100), - wantResult: "100", - wantOverflow: false, + name: "positive * negative", + x: "100", + y: "-2", + expected: "-200", + expectedOverflow: false, + }, + { + name: "max positive * min negative overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expectedOverflow: true, + }, + { + name: "max positive * max positive overflow", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "1", + expectedOverflow: true, }, { - name: "negative - negative (no overflow)", - x: NewInt(-200), - y: NewInt(-300), - wantResult: "100", - wantOverflow: false, + name: "min negative * min negative overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", + expectedOverflow: true, }, { - name: "positive - negative (no overflow)", - x: NewInt(200), - y: NewInt(-100), - wantResult: "300", - wantOverflow: false, + name: "zero * min negative", + x: "0", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", + expectedOverflow: false, }, { - name: "max_int256 - 0", - x: maxInt256, - y: Zero(), - wantResult: maxInt256.ToString(), - wantOverflow: false, + name: "large positive * large positive", + x: "412421424314214830214", + y: "491735014023482390148157914", + expected: "202802054868735010725494286098171881252370413596", + expectedOverflow: false, }, { - name: "min_int256 - 0", - x: minInt256, - y: Zero(), - wantResult: minInt256.ToString(), - wantOverflow: false, + name: "small positive * small positive", + x: "12", + y: "12", + expected: "144", + expectedOverflow: false, }, { - name: "max_int256 - (-1)", // max_int256 + 1 -> overflow - x: maxInt256, - y: NewInt(-1), - wantResult: "", - wantOverflow: true, + name: "negative * positive", + x: "-500000000000000", + y: "5000000000000", + expected: "-2500000000000000000000000000", + expectedOverflow: false, }, { - name: "min_int256 - 1", // min_int256 - 1 -> overflow - x: minInt256, - y: One(), - wantResult: "", - wantOverflow: true, + name: "large negative * large positive", + x: "-412421424314214830214", + y: "491735014023482390148157914", + expected: "-202802054868735010725494286098171881252370413596", + expectedOverflow: false, }, { - name: "0 - 0", - x: Zero(), - y: Zero(), - wantResult: "0", - wantOverflow: false, + name: "large positive * large negative overflow", + x: "202802054868735010725494286098171881252370413596", + y: "-202802054868735010725494286098171881252370413596", + expected: "17633904406147578101277522337283808353988667996273383726665993364273483857136", + expectedOverflow: true, }, { - name: "min_int256 - min_int256", - x: minInt256, - y: minInt256, - wantResult: "0", - wantOverflow: false, + name: "min negative * 1", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "1", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expectedOverflow: false, }, { - name: "max_int256 - max_int256", - x: maxInt256, - y: maxInt256, - wantResult: "0", - wantOverflow: false, + name: "min negative * -1 overflow", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-1", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expectedOverflow: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - z := Zero() - gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z, overflow := new(Int).MulOverflow(x, y) + uassert.Equal(t, tt.expected, z.ToString()) + uassert.Equal(t, tt.expectedOverflow, overflow) }) } } -func TestMul(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"5", "3", "15"}, - {"-5", "3", "-15"}, - {"5", "-3", "-15"}, - {"0", "3", "0"}, - {"3", "0", "0"}, - } - - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := Zero() - got.Mul(x, y) - - if got.Neq(want) { - t.Errorf("Mul(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - -func TestInt_MulOverflow(t *testing.T) { - maxInt256 := MaxInt256() - minInt256 := MinInt256() +// func TestInt_MulOverflow(t *testing.T) { +// maxInt256 := MaxInt256() +// minInt256 := MinInt256() + +// tests := []struct { +// name string +// x *Int +// y *Int +// wantResult string +// wantOverflow bool +// }{ +// { +// name: "positive * positive (no overflow)", +// x: NewInt(100), +// y: NewInt(100), +// wantResult: "10000", +// wantOverflow: false, +// }, +// { +// name: "negative * negative (no overflow)", +// x: NewInt(-100), +// y: NewInt(-100), +// wantResult: "10000", +// wantOverflow: false, +// }, +// { +// name: "positive * negative (no overflow)", +// x: NewInt(100), +// y: NewInt(-100), +// wantResult: "-10000", +// wantOverflow: false, +// }, +// { +// name: "0 * positive", +// x: Zero(), +// y: NewInt(100), +// wantResult: "0", +// wantOverflow: false, +// }, +// { +// name: "positive * 0", +// x: NewInt(100), +// y: Zero(), +// wantResult: "0", +// wantOverflow: false, +// }, +// { +// name: "0 * 0", +// x: Zero(), +// y: Zero(), +// wantResult: "0", +// wantOverflow: false, +// }, +// { +// name: "max_int256 * 1", +// x: maxInt256, +// y: One(), +// wantResult: maxInt256.ToString(), +// wantOverflow: false, +// }, +// { +// name: "min_int256 * 1", +// x: minInt256, +// y: One(), +// wantResult: minInt256.ToString(), +// wantOverflow: false, +// }, +// { +// name: "min_int256 * -1", +// x: minInt256, +// y: NewInt(-1), +// wantResult: "", // overflow because abs(min_int256) > max_int256 +// wantOverflow: true, +// }, +// { +// name: "max_int256 * 2", +// x: maxInt256, +// y: NewInt(2), +// wantResult: "", +// wantOverflow: true, +// }, +// { +// name: "min_int256 * 2", +// x: minInt256, +// y: NewInt(2), +// wantResult: "", +// wantOverflow: true, +// }, +// { +// name: "half_max * 2", +// x: MustFromDecimal("28948022309329048855892746252171976963317496332820282019728792003956564819983"), // (2^255-1)/2 +// y: NewInt(2), +// wantResult: "", +// wantOverflow: true, +// }, +// { +// name: "(half_max + 1) * 2", +// x: new(Int).Add(new(Int).Div(maxInt256, NewInt(2)), One()), +// y: NewInt(2), +// wantResult: "", +// wantOverflow: true, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// z := new(Int) +// gotResult, gotOverflow := z.MulOverflow(tt.x, tt.y) + +// if gotOverflow != tt.wantOverflow { +// t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) +// return +// } + +// if !gotOverflow { +// if gotResult == nil { +// t.Error("unexpected nil result for non-overflow case") +// return +// } +// if gotResult.ToString() != tt.wantResult { +// t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) +// } +// } + +// if !tt.wantOverflow { +// reverseResult, reverseOverflow := z.MulOverflow(tt.y, tt.x) +// if reverseOverflow != gotOverflow { +// t.Error("multiplication is not commutative for overflow") +// } +// if reverseResult.ToString() != gotResult.ToString() { +// t.Error("multiplication is not commutative for result") +// } +// } +// }) +// } +// } +func TestMul(t *testing.T) { tests := []struct { - name string - x *Int - y *Int - wantResult string - wantOverflow bool + name string + x string + y string + expected string }{ { - name: "positive * positive (no overflow)", - x: NewInt(100), - y: NewInt(100), - wantResult: "10000", - wantOverflow: false, - }, - { - name: "negative * negative (no overflow)", - x: NewInt(-100), - y: NewInt(-100), - wantResult: "10000", - wantOverflow: false, - }, - { - name: "positive * negative (no overflow)", - x: NewInt(100), - y: NewInt(-100), - wantResult: "-10000", - wantOverflow: false, - }, - { - name: "0 * positive", - x: Zero(), - y: NewInt(100), - wantResult: "0", - wantOverflow: false, + name: "positive * negative", + x: "100", + y: "-2", + expected: "-200", }, { - name: "positive * 0", - x: NewInt(100), - y: Zero(), - wantResult: "0", - wantOverflow: false, + name: "max positive * min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", }, { - name: "0 * 0", - x: Zero(), - y: Zero(), - wantResult: "0", - wantOverflow: false, + name: "max positive * max positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "1", }, { - name: "max_int256 * 1", - x: maxInt256, - y: One(), - wantResult: maxInt256.ToString(), - wantOverflow: false, + name: "min negative * min negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", }, { - name: "min_int256 * 1", - x: minInt256, - y: One(), - wantResult: minInt256.ToString(), - wantOverflow: false, + name: "zero * min negative", + x: "0", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", }, { - name: "min_int256 * -1", - x: minInt256, - y: NewInt(-1), - wantResult: "", // overflow because abs(min_int256) > max_int256 - wantOverflow: true, + name: "large positive * large positive", + x: "412421424314214830214", + y: "491735014023482390148157914", + expected: "202802054868735010725494286098171881252370413596", }, { - name: "max_int256 * 2", - x: maxInt256, - y: NewInt(2), - wantResult: "", - wantOverflow: true, + name: "small positive * small positive", + x: "12", + y: "12", + expected: "144", }, { - name: "min_int256 * 2", - x: minInt256, - y: NewInt(2), - wantResult: "", - wantOverflow: true, + name: "negative * positive", + x: "-500000000000000", + y: "5000000000000", + expected: "-2500000000000000000000000000", }, { - name: "half_max * 2", - x: MustFromDecimal("28948022309329048855892746252171976963317496332820282019728792003956564819983"), // (2^255-1)/2 - y: NewInt(2), - wantResult: "", - wantOverflow: true, + name: "large negative * large positive", + x: "-412421424314214830214", + y: "491735014023482390148157914", + expected: "-202802054868735010725494286098171881252370413596", }, { - name: "(half_max + 1) * 2", - x: new(Int).Add(new(Int).Div(maxInt256, NewInt(2)), One()), - y: NewInt(2), - wantResult: "", - wantOverflow: true, + name: "large positive * large negative overflow", + x: "202802054868735010725494286098171881252370413596", + y: "-202802054868735010725494286098171881252370413596", + expected: "17633904406147578101277522337283808353988667996273383726665993364273483857136", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - z := new(Int) - gotResult, gotOverflow := z.MulOverflow(tt.x, tt.y) - - if gotOverflow != tt.wantOverflow { - t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) - return - } - - if !gotOverflow { - if gotResult == nil { - t.Error("unexpected nil result for non-overflow case") - return - } - if gotResult.ToString() != tt.wantResult { - t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) - } - } - - if !tt.wantOverflow { - reverseResult, reverseOverflow := z.MulOverflow(tt.y, tt.x) - if reverseOverflow != gotOverflow { - t.Error("multiplication is not commutative for overflow") - } - if reverseResult.ToString() != gotResult.ToString() { - t.Error("multiplication is not commutative for result") - } - } + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Mul(x, y) + uassert.Equal(t, tt.expected, z.ToString()) }) } } func TestDiv(t *testing.T) { - maxInt := MaxInt256() - maxIntStr := maxInt.ToString() - maxIntHalfStr := new(Int).Rsh(maxInt, 1).ToString() - tests := []struct { - x, y, expected string + name string + x string + y string + expected string + shoudPanic bool }{ - {"1", "1", "1"}, - {"0", "1", "0"}, - {"-1", "1", "-1"}, - {"1", "-1", "-1"}, - {"-1", "-1", "1"}, - {"-6", "3", "-2"}, - {"10", "-2", "-5"}, - {"-10", "3", "-3"}, - {"7", "3", "2"}, - {"-7", "3", "-2"}, - {maxIntStr, "2", maxIntHalfStr}, // Max int256 / 2 + { + name: "positive / negative", + x: "100", + y: "-2", + expected: "-50", + }, + { + name: "min negative / positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "3194721952341", + expected: "-18122404854742851078740194629148605029554483750497671811411863963", + }, + { + name: "large positive / negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819961", + y: "-4134320184329015710493829104", + expected: "-14003764110508630918697735423914335136046266814161", + }, + { + name: "large negative / large negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819961", + y: "-4134320184329015710493829104", + expected: "14003764110508630918697735423914335136046266814161", + }, + { + name: "small negative / large negative", + x: "-578960446186580977117854925043439539266349923328202564819961", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: "0", + }, + { + name: "large / small", + x: "3214732915732914729142135321421", + y: "510482085320157124", + expected: "6297445117426", + }, + { + name: "same values", + x: "510482085320157124", + y: "510482085320157124", + expected: "1", + }, + { + name: "zero dividend", + x: "0", + y: "510482085320157124", + expected: "0", + }, + { + name: "division by zero", + x: "1", + y: "0", + expected: "", + shoudPanic: true, + }, } for _, tt := range tests { - t.Run(tt.x+"/"+tt.y, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + if tt.shoudPanic { + defer func() { + if r := recover(); r == nil { + t.Error("Div should panic on division by zero") + } + }() + } + x := MustFromDecimal(tt.x) y := MustFromDecimal(tt.y) - result := Zero().Div(x, y) - if result.ToString() != tt.expected { - t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) - } - if result.IsZero() && result.IsNeg() { - t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) + z := new(Int).Div(x, y) + if !tt.shoudPanic { + uassert.Equal(t, tt.expected, z.ToString()) } }) } - - t.Run("Division by zero", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("Div(1, 0) did not panic") - } - }() - x := MustFromDecimal("1") - y := MustFromDecimal("0") - Zero().Div(x, y) - }) } func TestOr(t *testing.T) { tests := []struct { - name string - x, y, want string + name string + x string + y string + expected string }{ - {"all zeroes", "0", "0", "0"}, - {"all ones", "-1", "-1", "-1"}, - {"mixed", "-1", "0", "-1"}, + { + name: "min negative | negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-100", + expected: "-100", + }, + { + name: "max positive | positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "41457129491534261876432718654783265437285638275", + expected: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + }, + { + name: "min negative | max positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: "-1", + }, + { + name: "min negative | zero", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "0", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - x := MustFromDecimal(tc.x) - y := MustFromDecimal(tc.y) - want := MustFromDecimal(tc.want) - - got := Zero() - got.Or(x, y) - - if got.Neq(want) { - t.Errorf("Or(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).Or(x, y) + uassert.Equal(t, tt.expected, z.ToString()) }) } } func TestAnd(t *testing.T) { tests := []struct { - name string - x, y, want string + name string + x string + y string + expected string }{ - {"all zeroes", "0", "0", "0"}, - {"all ones", "-1", "-1", "-1"}, - {"mixed", "0", "-1", "0"}, - {"mixed 2", "-1", "0", "0"}, + { + name: "min negative & negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-100", + expected: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + }, + { + name: "max positive & positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "41457129491534261876432718654783265437285638275", + expected: "41457129491534261876432718654783265437285638275", + }, + { + name: "negative & small positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "7", + expected: "1", + }, + { + name: "min negative & zero", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "0", + expected: "0", + }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - x := MustFromDecimal(tc.x) - y := MustFromDecimal(tc.y) - want := MustFromDecimal(tc.want) - - got := Zero() - got.And(x, y) - - if got.Neq(want) { - t.Errorf("And(%s, %s) = %s, want %s", tc.x, tc.y, got.ToString(), tc.want) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + z := new(Int).And(x, y) + uassert.Equal(t, tt.expected, z.ToString()) }) } } @@ -973,35 +1233,67 @@ func TestNeq(t *testing.T) { func TestCmp(t *testing.T) { tests := []struct { - x, y string - want int + name string + x string + y string + expected int }{ - {"0", "0", 0}, - {"0", "1", -1}, - {"1", "0", 1}, - {"-1", "0", -1}, - {"0", "-1", 1}, - {"1", "1", 0}, - {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", 1}, + { + name: "min negative < max positive", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: -1, + }, + { + name: "min negative == min negative", + x: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: 0, + }, + { + name: "max positive > min negative", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + expected: 1, + }, + { + name: "max positive == max positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: 0, + }, + { + name: "zero == zero", + x: "0", + y: "0", + expected: 0, + }, + { + name: "zero > negative", + x: "0", + y: "-10", + expected: 1, + }, + { + name: "smaller positive < larger positive", + x: "57896044618658097711785492504343953926634992332820282019728792003956564", + y: "57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: -1, + }, + { + name: "larger negative > smaller negative", + x: "-578960446186580977117854925043439539266349923328202820197287920039565648", + y: "-57896044618658097711785492504343953926634992332820282019728792003956564819967", + expected: 1, + }, } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue - } - - y, err := FromDecimal(tc.y) - if err != nil { - t.Error(err) - continue - } - - got := x.Cmp(y) - if got != tc.want { - t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + uassert.Equal(t, tt.expected, x.Cmp(y)) + }) } } @@ -1063,6 +1355,7 @@ func TestLt(t *testing.T) { x, y string want bool }{ + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819967", true}, {"0", "0", false}, {"0", "1", true}, {"1", "0", false}, @@ -1070,6 +1363,7 @@ func TestLt(t *testing.T) { {"0", "-1", false}, {"1", "1", false}, {"-1", "-1", false}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "-57896044618658097711785492504343953926634992332820282019728792003956564819967", false}, } From a6fc2b45033672f4bd87240109c4cdd73e590a96 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 1 Dec 2025 16:28:50 +0900 Subject: [PATCH 23/28] =?UTF-8?q?test:=20Add=20comprehensive=20Knuth=20Alg?= =?UTF-8?q?orithm=20D=20test=20suite=20(Hacker=E2=80=99s=20Delight=20vecto?= =?UTF-8?q?rs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a full set of division test cases derived from the Hacker’s Delight “divmnu” test vectors, adapted to the 64-bit word (256-bit Int) representation used by this package. The added tests validate the correctness of the entire udivrem() implementation, including both the 2-by-1 fast path and the full Knuth Algorithm D multi-word division path. Each test case contains: - u : dividend - v : divisor - cq : expected quotient - cr : expected remainder These vectors are intentionally constructed to exercise every edge case in Knuth’s division algorithm, not just normal divisions. The suite covers: • q̂ (quotient estimate) calculation using the top two words • q̂ over-estimation and the required decrement correction step • multiply-subtract behavior and borrow propagation across multiple words • the “add-back” correction step when subtraction underflows • normalization/unnormalization of dividend and divisor • remainder reconstruction after right-shifting • single-word divisor path (reciprocal2by1 / udivrem2by1) • boundary cases such as u < v, u == v, and very large multi-word inputs • division-by-zero behavior (panic detection) Because these test vectors originate from a mathematically verified reference implementation, they provide strong confidence that the 256-bit udivrem implementation is correct in all algorithmic branches, including those that are traditionally easy to get wrong (q̂ correction, borrow chain, signed/unsigned handling, etc.). This brings the division test coverage to a level where both correctness and algorithmic soundness are ensured without relying on big.Int or reflection. --- contract/p/gnoswap/int256/arithmetic.gno | 4 + contract/p/gnoswap/int256/internal_test.gno | 153 ++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 contract/p/gnoswap/int256/internal_test.gno diff --git a/contract/p/gnoswap/int256/arithmetic.gno b/contract/p/gnoswap/int256/arithmetic.gno index 7907ab308..471be0fa7 100644 --- a/contract/p/gnoswap/int256/arithmetic.gno +++ b/contract/p/gnoswap/int256/arithmetic.gno @@ -11,6 +11,10 @@ func udivrem(quot, u []uint64, d *Int) (rem Int) { } } + if dLen == 0 { + panic("integer divide by zero") + } + shift := uint(bits.LeadingZeros64(d[dLen-1])) var dnStorage Int diff --git a/contract/p/gnoswap/int256/internal_test.gno b/contract/p/gnoswap/int256/internal_test.gno new file mode 100644 index 000000000..3355d3088 --- /dev/null +++ b/contract/p/gnoswap/int256/internal_test.gno @@ -0,0 +1,153 @@ +package int256 + +import ( + "testing" + + "gno.land/p/nt/uassert" + "gno.land/p/nt/ufmt" +) + +func pack32to64(words []uint32) []uint64 { + var out []uint64 + for i := 0; i < len(words); i += 2 { + lo := uint64(words[i]) + hi := uint64(0) + if i+1 < len(words) { + hi = uint64(words[i+1]) + } + out = append(out, (hi<<32)|lo) + } + return out +} + +func toInt(xs []uint64) Int { + var z Int + for i := 0; i < len(xs) && i < len(z); i++ { + z[i] = xs[i] + } + return z +} + +// Source: https://skanthak.hier-im-netz.de/division.html#proper (Section: Generic Implementation in Hacker’s Delight) +var tests = []uint32{ + // m, n, u..., v..., cq..., cr.... + 1, 1, 3, 0, 1, 1, // Error, divide by 0. + 1, 2, 7, 1, 3, 0, 7, 0, + 2, 2, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 3, 2, 1, 1, + 1, 1, 3, 3, 1, 0, + 1, 1, 3, 4, 0, 3, + 1, 1, 0, 0xffffffff, 0, 0, + 1, 1, 0xffffffff, 1, 0xffffffff, 0, + 1, 1, 0xffffffff, 0xffffffff, 1, 0, + 1, 1, 0xffffffff, 3, 0x55555555, 0, + 2, 1, 0xffffffff, 0xffffffff, 1, 0xffffffff, 0xffffffff, 0, + 2, 1, 0xffffffff, 0xffffffff, 0xffffffff, 1, 1, 0, + 2, 1, 0xffffffff, 0xfffffffe, 0xffffffff, 0xffffffff, 0, 0xfffffffe, + 2, 1, 0x00005678, 0x00001234, 0x00009abc, 0x1e1dba76, 0, 0x6bd0, + 2, 2, 0, 0, 0, 1, 0, 0, 0, + 2, 2, 0, 7, 0, 3, 2, 0, 1, + 2, 2, 5, 7, 0, 3, 2, 5, 1, + 2, 2, 0, 6, 0, 2, 3, 0, 0, + 1, 1, 0x80000000, 0x40000001, 0x00000001, 0x3fffffff, + 2, 1, 0x00000000, 0x80000000, 0x40000001, 0xfffffff8, 0x00000001, 0x00000008, + 2, 2, 0x00000000, 0x80000000, 0x00000001, 0x40000000, 0x00000001, 0xffffffff, 0x3fffffff, + 2, 2, 0x0000789a, 0x0000bcde, 0x0000789a, 0x0000bcde, 1, 0, 0, + 2, 2, 0x0000789b, 0x0000bcde, 0x0000789a, 0x0000bcde, 1, 1, 0, + 2, 2, 0x00007899, 0x0000bcde, 0x0000789a, 0x0000bcde, 0, 0x00007899, 0x0000bcde, + 2, 2, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 1, 0, 0, + 2, 2, 0x0000ffff, 0x0000ffff, 0x00000000, 0x00000001, 0x0000ffff, 0x0000ffff, 0, + 3, 2, 0x000089ab, 0x00004567, 0x00000123, 0x00000000, 0x00000001, 0x00004567, 0x00000123, 0x000089ab, 0, + 3, 2, 0x00000000, 0x0000fffe, 0x00008000, 0x0000ffff, 0x00008000, 0xffffffff, 0x00000000, 0x0000ffff, 0x00007fff, // Shows that first qhat can = b + 1. + 3, 3, 0x00000003, 0x00000000, 0x80000000, 0x00000001, 0x00000000, 0x20000000, 0x00000003, 0, 0, 0x20000000, // Adding back step req'd. + 3, 3, 0x00000003, 0x00000000, 0x00008000, 0x00000001, 0x00000000, 0x00002000, 0x00000003, 0, 0, 0x00002000, // Adding back step req'd. + 4, 3, 0, 0, 0x00008000, 0x00007fff, 1, 0, 0x00008000, 0xfffe0000, 0, 0x00020000, 0xffffffff, 0x00007fff, // Add back req'd. + 4, 3, 0, 0x0000fffe, 0, 0x00008000, 0x0000ffff, 0, 0x00008000, 0xffffffff, 0, 0x0000ffff, 0xffffffff, 0x00007fff, // Shows that mult-sub quantity cannot be treated as signed. + 4, 3, 0, 0xfffffffe, 0, 0x80000000, 0x0000ffff, 0, 0x80000000, 0x00000000, 1, 0x00000000, 0xfffeffff, 0x00000000, // Shows that mult-sub quantity cannot be treated as signed. + 4, 3, 0, 0xfffffffe, 0, 0x80000000, 0xffffffff, 0, 0x80000000, 0xffffffff, 0, 0xffffffff, 0xffffffff, 0x7fffffff, // Shows that mult-sub quantity cannot be treated as signed. +} + +func TestUdivrem(t *testing.T) { + i := 0 + caseNo := 0 + + for i < len(tests) { + caseNo++ + + m := int(tests[i]) + n := int(tests[i+1]) + + u32 := tests[i+2 : i+2+m] + v32 := tests[i+2+m : i+2+m+n] + + qlen := max(m-n+1, 1) + cq32 := tests[i+2+m+n : i+2+m+n+qlen] + cr32 := tests[i+2+m+n+qlen : i+2+m+n+qlen+n] + + // transform + u := toInt(pack32to64(u32)) + v := toInt(pack32to64(v32)) + cq := toInt(pack32to64(cq32)) + cr := toInt(pack32to64(cr32)) + + shouldError := false + + // Division by zero case + isZero := true + for i := 0; i < len(v32); i++ { + if v32[i] != 0 { + isZero = false + break + } + } + if isZero { + shouldError = true + } + + if shouldError { + mustPanic(t, func() { + var q [4]uint64 + _ = udivrem(q[:], u[:], &v) + }) + } else { + var quot [4]uint64 + + mustNotPanic(t, func() { + rem := udivrem(quot[:], u[:], &v) + q := toInt(quot[:]) + + uassert.Equal(t, cq.ToString(), q.ToString(), ufmt.Sprintf("[case %d] quotient mismatch:\n got=%v\n want=%v\n", caseNo, q, cq)) + uassert.Equal(t, cr.ToString(), rem.ToString(), ufmt.Sprintf("[case %d] remainder mismatch:\n got=%v\n want=%v\n", caseNo, rem, cr)) + }) + } + + i += 2 + m + n + qlen + n + } +} + +func mustPanic(t *testing.T, f func()) { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic but did not panic") + } + }() + f() +} + +func mustNotPanic(t *testing.T, f func()) { + t.Helper() + defer func() { + if r := recover(); r != nil { + t.Fatalf("unexpected panic: %v", r) + } + }() + f() +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} From 092625919687f55ddb3dc4aa059986da0e0436f9 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 1 Dec 2025 16:37:31 +0900 Subject: [PATCH 24/28] chore: Remove invalid license --- contract/p/gnoswap/int256/LICENSE | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 contract/p/gnoswap/int256/LICENSE diff --git a/contract/p/gnoswap/int256/LICENSE b/contract/p/gnoswap/int256/LICENSE deleted file mode 100644 index fc7e78a48..000000000 --- a/contract/p/gnoswap/int256/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Trịnh Đức Bảo Linh(Kevin) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file From 845e85266031412a57194db114307a18ece451eb Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 1 Dec 2025 16:59:15 +0900 Subject: [PATCH 25/28] test: multiplier test --- contract/p/gnoswap/int256/internal_test.gno | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/contract/p/gnoswap/int256/internal_test.gno b/contract/p/gnoswap/int256/internal_test.gno index 3355d3088..87b47f2e0 100644 --- a/contract/p/gnoswap/int256/internal_test.gno +++ b/contract/p/gnoswap/int256/internal_test.gno @@ -1,6 +1,7 @@ package int256 import ( + "math/bits" "testing" "gno.land/p/nt/uassert" @@ -151,3 +152,114 @@ func max(a, b int) int { } return b } + +// Implementation of schoolbook multiplication +// ref: https://en.wikipedia.org/wiki/Multiplication_algorithm#Example +func referenceMul(x, y *Int) [8]uint64 { + var out [8]uint64 + for i := 0; i < 4; i++ { + var carry uint64 + for j := 0; j < 4; j++ { + hi, lo := bits.Mul64(x[j], y[i]) + + // out[i+j] += lo + lo2, c1 := bits.Add64(out[i+j], lo, 0) + out[i+j] = lo2 + hi += c1 + + // add previous carry + lo3, c2 := bits.Add64(out[i+j], carry, 0) + out[i+j] = lo3 + hi += c2 + + // update carry + carry = hi + } + out[i+4] = carry + } + return out +} + +func TestUmul(t *testing.T) { + tests := []struct { + name string + x Int + y Int + }{ + { + name: "zero * random", + x: Int{0, 0, 0, 0}, + y: Int{1, 2, 3, 4}, + }, + { + name: "random * zero", + x: Int{5, 6, 7, 8}, + y: Int{0, 0, 0, 0}, + }, + { + name: "one * value", + x: Int{1, 0, 0, 0}, + y: Int{9, 10, 11, 12}, + }, + { + name: "value * one", + x: Int{9, 10, 11, 12}, + y: Int{1, 0, 0, 0}, + }, + { + name: "maxword low 64 bits", + x: Int{^uint64(0), 0, 0, 0}, + y: Int{^uint64(0), 0, 0, 0}, + }, + { + name: "max whole 256-bit", + x: Int{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}, + y: Int{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}, + }, + { + name: "carry chain test A", + x: Int{^uint64(0), 0, 0, 0}, + y: Int{1, 1, 1, 1}, + }, + { + name: "carry chain test B", + x: Int{1, ^uint64(0), 1, ^uint64(0)}, + y: Int{^uint64(0), 1, ^uint64(0), 1}, + }, + { + name: "diagonal test 0 (res[0] only)", + x: Int{1, 0, 0, 0}, + y: Int{1, 0, 0, 0}, + }, + { + name: "diagonal test 1", + x: Int{1, 0, 0, 0}, + y: Int{0, 1, 0, 0}, + }, + { + name: "diagonal test 2", + x: Int{1, 0, 0, 0}, + y: Int{0, 0, 1, 0}, + }, + { + name: "diagonal test 3", + x: Int{0, 0, 0, 1}, + y: Int{0, 0, 1, 0}, + }, + { + name: "sparse pattern", + x: Int{1, ^uint64(0), 0, 1}, + y: Int{0, 1, 0, ^uint64(0)}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := umul(&tt.x, &tt.y) + want := referenceMul(&tt.x, &tt.y) + for i := 0; i < 8; i++ { + uassert.Equal(t, got[i], want[i], ufmt.Sprintf("umul(x,y) mismatch at index %d\nx=%v\ny=%v\ngot = %v\nwant= %v", i, tt.x, tt.y, got, want)) + } + }) + } +} From 0949d903451eb8d8d1f2ee1862a73aec6f94f6eb Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Mon, 1 Dec 2025 17:01:59 +0900 Subject: [PATCH 26/28] fix(int256): add missing return in uquo and urem for zero dividend When x is zero in uquo/urem, z.Clear() was called but execution continued to subsequent logic. Added return to exit early. --- contract/p/gnoswap/int256/int256.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contract/p/gnoswap/int256/int256.gno b/contract/p/gnoswap/int256/int256.gno index d2ec1ce95..29312591d 100644 --- a/contract/p/gnoswap/int256/int256.gno +++ b/contract/p/gnoswap/int256/int256.gno @@ -333,7 +333,7 @@ func (z *Int) uquo(x, y *Int) *Int { panic(ErrZeroDivision) } if x.IsZero() { - z.Clear() + return z.Clear() } if x.Eq(y) { return z.SetOne() @@ -366,7 +366,7 @@ func (z *Int) urem(x, y *Int) *Int { panic(ErrZeroDivision) } if x.IsZero() { - z.Clear() + return z.Clear() } if x.Eq(y) { return z.Clear() From 970f37cb1ecf5818b9e22029a93c184e7c8dad89 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 1 Dec 2025 18:07:48 +0900 Subject: [PATCH 27/28] test: add min int256 / -1 case --- contract/p/gnoswap/int256/int256_test.gno | 297 +--------------------- 1 file changed, 6 insertions(+), 291 deletions(-) diff --git a/contract/p/gnoswap/int256/int256_test.gno b/contract/p/gnoswap/int256/int256_test.gno index 3faf1bc64..b67d7f2ae 100644 --- a/contract/p/gnoswap/int256/int256_test.gno +++ b/contract/p/gnoswap/int256/int256_test.gno @@ -224,54 +224,6 @@ func TestAddOverflow(t *testing.T) { } } -// func TestSub(t *testing.T) { -// maxInt := MaxInt256() -// maxIntStr := maxInt.ToString() -// maxIntMinusOneStr := new(Int).Sub(maxInt, One()).ToString() -// minIntStr := MinInt256().ToString() - -// tests := []struct { -// x, y, want string -// }{ -// {"1", "0", "1"}, -// {"1", "1", "0"}, -// {"-1", "1", "-2"}, -// {"1", "-1", "2"}, -// {"-1", "-1", "0"}, -// {minIntStr, minIntStr, "0"}, -// {minIntStr, "0", minIntStr}, -// {maxIntStr, maxIntStr, "0"}, -// {maxIntMinusOneStr, "-1", maxIntStr}, -// } - -// for _, tc := range tests { -// x, err := FromDecimal(tc.x) -// if err != nil { -// t.Error(err) -// continue -// } - -// y, err := FromDecimal(tc.y) -// if err != nil { -// t.Error(err) -// continue -// } - -// want, err := FromDecimal(tc.want) -// if err != nil { -// t.Error(err) -// continue -// } - -// got := Zero() -// got.Sub(x, y) - -// if got.Neq(want) { -// t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) -// } -// } -// } - func TestSub(t *testing.T) { tests := []struct { name string @@ -315,112 +267,6 @@ func TestSub(t *testing.T) { } } -// func TestInt_SubOverflow(t *testing.T) { -// maxInt256 := MaxInt256() -// minInt256 := MinInt256() - -// tests := []struct { -// name string -// x *Int -// y *Int -// wantResult string -// wantOverflow bool -// }{ -// { -// name: "positive - positive (no overflow)", -// x: NewInt(200), -// y: NewInt(100), -// wantResult: "100", -// wantOverflow: false, -// }, -// { -// name: "negative - negative (no overflow)", -// x: NewInt(-200), -// y: NewInt(-300), -// wantResult: "100", -// wantOverflow: false, -// }, -// { -// name: "positive - negative (no overflow)", -// x: NewInt(200), -// y: NewInt(-100), -// wantResult: "300", -// wantOverflow: false, -// }, -// { -// name: "max_int256 - 0", -// x: maxInt256, -// y: Zero(), -// wantResult: maxInt256.ToString(), -// wantOverflow: false, -// }, -// { -// name: "min_int256 - 0", -// x: minInt256, -// y: Zero(), -// wantResult: minInt256.ToString(), -// wantOverflow: false, -// }, -// { -// name: "max_int256 - (-1)", // max_int256 + 1 -> overflow -// x: maxInt256, -// y: NewInt(-1), -// wantResult: "", -// wantOverflow: true, -// }, -// { -// name: "min_int256 - 1", // min_int256 - 1 -> overflow -// x: minInt256, -// y: One(), -// wantResult: "", -// wantOverflow: true, -// }, -// { -// name: "0 - 0", -// x: Zero(), -// y: Zero(), -// wantResult: "0", -// wantOverflow: false, -// }, -// { -// name: "min_int256 - min_int256", -// x: minInt256, -// y: minInt256, -// wantResult: "0", -// wantOverflow: false, -// }, -// { -// name: "max_int256 - max_int256", -// x: maxInt256, -// y: maxInt256, -// wantResult: "0", -// wantOverflow: false, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// z := Zero() -// gotResult, gotOverflow := z.SubOverflow(tt.x, tt.y) - -// if gotOverflow != tt.wantOverflow { -// t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) -// return -// } - -// if !gotOverflow { -// if gotResult == nil { -// t.Error("unexpected nil result for non-overflow case") -// return -// } -// if gotResult.ToString() != tt.wantResult { -// t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) -// } -// } -// }) -// } -// } - func TestSubOverflow(t *testing.T) { tests := []struct { name string @@ -575,143 +421,6 @@ func TestMulOverFlow(t *testing.T) { } } -// func TestInt_MulOverflow(t *testing.T) { -// maxInt256 := MaxInt256() -// minInt256 := MinInt256() - -// tests := []struct { -// name string -// x *Int -// y *Int -// wantResult string -// wantOverflow bool -// }{ -// { -// name: "positive * positive (no overflow)", -// x: NewInt(100), -// y: NewInt(100), -// wantResult: "10000", -// wantOverflow: false, -// }, -// { -// name: "negative * negative (no overflow)", -// x: NewInt(-100), -// y: NewInt(-100), -// wantResult: "10000", -// wantOverflow: false, -// }, -// { -// name: "positive * negative (no overflow)", -// x: NewInt(100), -// y: NewInt(-100), -// wantResult: "-10000", -// wantOverflow: false, -// }, -// { -// name: "0 * positive", -// x: Zero(), -// y: NewInt(100), -// wantResult: "0", -// wantOverflow: false, -// }, -// { -// name: "positive * 0", -// x: NewInt(100), -// y: Zero(), -// wantResult: "0", -// wantOverflow: false, -// }, -// { -// name: "0 * 0", -// x: Zero(), -// y: Zero(), -// wantResult: "0", -// wantOverflow: false, -// }, -// { -// name: "max_int256 * 1", -// x: maxInt256, -// y: One(), -// wantResult: maxInt256.ToString(), -// wantOverflow: false, -// }, -// { -// name: "min_int256 * 1", -// x: minInt256, -// y: One(), -// wantResult: minInt256.ToString(), -// wantOverflow: false, -// }, -// { -// name: "min_int256 * -1", -// x: minInt256, -// y: NewInt(-1), -// wantResult: "", // overflow because abs(min_int256) > max_int256 -// wantOverflow: true, -// }, -// { -// name: "max_int256 * 2", -// x: maxInt256, -// y: NewInt(2), -// wantResult: "", -// wantOverflow: true, -// }, -// { -// name: "min_int256 * 2", -// x: minInt256, -// y: NewInt(2), -// wantResult: "", -// wantOverflow: true, -// }, -// { -// name: "half_max * 2", -// x: MustFromDecimal("28948022309329048855892746252171976963317496332820282019728792003956564819983"), // (2^255-1)/2 -// y: NewInt(2), -// wantResult: "", -// wantOverflow: true, -// }, -// { -// name: "(half_max + 1) * 2", -// x: new(Int).Add(new(Int).Div(maxInt256, NewInt(2)), One()), -// y: NewInt(2), -// wantResult: "", -// wantOverflow: true, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// z := new(Int) -// gotResult, gotOverflow := z.MulOverflow(tt.x, tt.y) - -// if gotOverflow != tt.wantOverflow { -// t.Errorf("overflow = %v, want %v", gotOverflow, tt.wantOverflow) -// return -// } - -// if !gotOverflow { -// if gotResult == nil { -// t.Error("unexpected nil result for non-overflow case") -// return -// } -// if gotResult.ToString() != tt.wantResult { -// t.Errorf("result = %v, want %v", gotResult.ToString(), tt.wantResult) -// } -// } - -// if !tt.wantOverflow { -// reverseResult, reverseOverflow := z.MulOverflow(tt.y, tt.x) -// if reverseOverflow != gotOverflow { -// t.Error("multiplication is not commutative for overflow") -// } -// if reverseResult.ToString() != gotResult.ToString() { -// t.Error("multiplication is not commutative for result") -// } -// } -// }) -// } -// } - func TestMul(t *testing.T) { tests := []struct { name string @@ -854,6 +563,12 @@ func TestDiv(t *testing.T) { expected: "", shoudPanic: true, }, + { + name: "min int256 / -1", + x: MinInt256().ToString(), + y: "-1", + expected: MinInt256().ToString(), + }, } for _, tt := range tests { From 0ac7400901c88ffc4b61da43eabb575222911f6e Mon Sep 17 00:00:00 2001 From: Junghoon Ban Date: Mon, 1 Dec 2025 18:29:06 +0900 Subject: [PATCH 28/28] chore: Increase gas parameters for int256 package deployment in testdata. --- tests/integration/testdata/deploy.txtar | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/testdata/deploy.txtar b/tests/integration/testdata/deploy.txtar index 640522e1e..2b183e76e 100644 --- a/tests/integration/testdata/deploy.txtar +++ b/tests/integration/testdata/deploy.txtar @@ -70,8 +70,8 @@ gnokey maketx addpkg -pkgdir ../../../examples/gno.land/p/gnoswap/uint256 -pkgpa # stdout 'STORAGE FEE: (1|2)[0-9]{7}ugnot' ## deploy int256 -gnokey maketx addpkg -pkgdir ../../../examples/gno.land/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -gas-fee 14936ugnot -gas-wanted 14936000 -broadcast -chainid=tendermint_test test1 -# stdout 'GAS USED: 14[0-9]{6}' +gnokey maketx addpkg -pkgdir ../../../examples/gno.land/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -gas-fee 17000ugnot -gas-wanted 17000000 -broadcast -chainid=tendermint_test test1 +# stdout 'GAS USED: 16[0-9]{6}' # stdout 'STORAGE DELTA: (5|6)[0-9]{4} bytes' # stdout 'STORAGE FEE: (5|6)[0-9]{6}ugnot'