diff --git a/_deploy/r/gnoswap/common/address_and_username.gno b/_deploy/r/gnoswap/common/address_and_username.gno new file mode 100644 index 000000000..708c55e2c --- /dev/null +++ b/_deploy/r/gnoswap/common/address_and_username.gno @@ -0,0 +1,29 @@ +package common + +import ( + "std" + + pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" +) + +// AddrToUser converts a type from address to AddressOrName. +// It panics if the address is invalid. +func AddrToUser(addr std.Address) pusers.AddressOrName { + assertValidAddr(addr) + return pusers.AddressOrName(addr) +} + +// UserToAddr converts a type from AddressOrName to address. +// by resolving the user through the users realms. +func UserToAddr(user pusers.AddressOrName) std.Address { + return users.Resolve(user) +} + +// assertValidAddr checks if the given address is valid. +// It panics with a detailed error message if the address is invalid. +func assertValidAddr(addr std.Address) { + if !addr.IsValid() { + panic(addDetailToError(errInvalidAddr, addr.String())) + } +} diff --git a/_deploy/r/gnoswap/common/address_and_username_test.gno b/_deploy/r/gnoswap/common/address_and_username_test.gno new file mode 100644 index 000000000..9f62bd8ca --- /dev/null +++ b/_deploy/r/gnoswap/common/address_and_username_test.gno @@ -0,0 +1,115 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" +) + +func TestAddrToUser(t *testing.T) { + tests := []struct { + name string + addr std.Address + want pusers.AddressOrName + shouldPanic bool + panicMsg string + }{ + { + name: "valid address", + addr: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + want: pusers.AddressOrName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + { + name: "empty address", + addr: std.Address(""), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || `, + }, + { + name: "invalid address", + addr: std.Address("invalid"), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || invalid`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + AddrToUser(tt.addr) + }) + } else { + got := AddrToUser(tt.addr) + if got != tt.want { + t.Errorf("AddrToUser() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func TestUserToAddr(t *testing.T) { + tests := []struct { + name string + user pusers.AddressOrName + want std.Address + }{ + { + name: "address string with user type", + user: pusers.AddressOrName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + want: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UserToAddr(tt.user) + if got != tt.want { + t.Errorf("UserToAddr() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAssertValidAddr(t *testing.T) { + tests := []struct { + name string + addr std.Address + shouldPanic bool + panicMsg string + }{ + { + name: "valid address", + addr: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + { + name: "empty address", + addr: std.Address(""), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || `, + }, + { + name: "invalid address", + addr: std.Address("invalid"), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || invalid`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + assertValidAddr(tt.addr) + }) + } else { + uassert.NotPanics(t, func() { + assertValidAddr(tt.addr) + }) + } + }) + } +} diff --git a/_deploy/r/gnoswap/common/errors.gno b/_deploy/r/gnoswap/common/errors.gno index 43bd31337..168d8b859 100644 --- a/_deploy/r/gnoswap/common/errors.gno +++ b/_deploy/r/gnoswap/common/errors.gno @@ -7,10 +7,13 @@ import ( ) var ( - errNoPermission = errors.New("[GNOSWAP-COMMON-001] caller has no permission") - errHalted = errors.New("[GNOSWAP-COMMON-002] halted") - errOutOfRange = errors.New("[GNOSWAP-COMMON-003] value out of range") - errNotRegistered = errors.New("[GNOSWAP-COMMON-004] token is not registered") + errNoPermission = errors.New("[GNOSWAP-COMMON-001] caller has no permission") + errHalted = errors.New("[GNOSWAP-COMMON-002] halted") + errOutOfRange = errors.New("[GNOSWAP-COMMON-003] value out of range") + errNotRegistered = errors.New("[GNOSWAP-COMMON-004] token is not registered") + errInvalidAddr = errors.New("[GNOSWAP-COMMON-005] invalid address") + errOverflow = errors.New("[GNOSWAP-COMMON-006] overflow") + errInvalidTokenId = errors.New("[GNOSWAP-COMMON-007] invalid tokenId") ) func addDetailToError(err error, detail string) string { diff --git a/_deploy/r/gnoswap/common/grc20reg_helper.gno b/_deploy/r/gnoswap/common/grc20reg_helper.gno index 50f945e02..992a02702 100644 --- a/_deploy/r/gnoswap/common/grc20reg_helper.gno +++ b/_deploy/r/gnoswap/common/grc20reg_helper.gno @@ -1,6 +1,8 @@ package common import ( + "std" + "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" "gno.land/r/demo/grc20reg" @@ -51,7 +53,22 @@ func MustRegistered(path string) { if err := IsRegistered(path); err != nil { panic(addDetailToError( errNotRegistered, - ufmt.Sprintf("token(%s) is not registered", path), + ufmt.Sprintf("token(%s)", path), )) } } + +// TotalSupply returns the total supply of the token +func TotalSupply(path string) uint64 { + return GetToken(path).TotalSupply() +} + +// BalanceOf returns the balance of the token for the given address +func BalanceOf(path string, addr std.Address) uint64 { + return GetToken(path).BalanceOf(addr) +} + +// Allowance returns the allowance of the token for the given owner and spender +func Allowance(path string, owner, spender std.Address) uint64 { + return GetToken(path).Allowance(owner, spender) +} diff --git a/_deploy/r/gnoswap/common/grc20reg_helper_test.gno b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno index 086320ed2..0ae8b6379 100644 --- a/_deploy/r/gnoswap/common/grc20reg_helper_test.gno +++ b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno @@ -8,7 +8,7 @@ import ( "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" - _ "gno.land/r/demo/foo20" + "gno.land/r/demo/foo20" ) var ( @@ -152,3 +152,23 @@ func TestMustRegistered(t *testing.T) { MustRegistered("not_registered") }) } + +func TestTotalSupply(t *testing.T) { + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.TotalSupply(), TotalSupply(tokenPath)) +} + +func TestBalanceOf(t *testing.T) { + defaultHolder := std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.BalanceOf(AddrToUser(defaultHolder)), BalanceOf(tokenPath, defaultHolder)) +} + +func TestAllowance(t *testing.T) { + owner := std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + spender := std.Address("g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6") + + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.Allowance(AddrToUser(owner), AddrToUser(spender)), Allowance(tokenPath, owner, spender)) +} diff --git a/_deploy/r/gnoswap/common/grc721_token_id.gno b/_deploy/r/gnoswap/common/grc721_token_id.gno new file mode 100644 index 000000000..2cc4c5620 --- /dev/null +++ b/_deploy/r/gnoswap/common/grc721_token_id.gno @@ -0,0 +1,41 @@ +package common + +import ( + "strconv" + + "gno.land/p/demo/ufmt" + + "gno.land/p/demo/grc/grc721" +) + +// TokenIdFrom converts tokenId to grc721.TokenID type +// NOTE: input parameter tokenId can be string, int, uint64, or grc721.TokenID +// if tokenId is nil or not supported, it will panic +// if input type is not supported, it will panic +// input: tokenId interface{} +// output: grc721.TokenID +func TokenIdFrom(tokenId interface{}) grc721.TokenID { + if tokenId == nil { + panic(addDetailToError( + errInvalidTokenId, + "can not be nil", + )) + } + + switch tokenId.(type) { + case string: + return grc721.TokenID(tokenId.(string)) + case int: + return grc721.TokenID(strconv.Itoa(tokenId.(int))) + case uint64: + return grc721.TokenID(strconv.Itoa(int(tokenId.(uint64)))) + case grc721.TokenID: + return tokenId.(grc721.TokenID) + default: + estimatedType := ufmt.Sprintf("%T", tokenId) + panic(addDetailToError( + errInvalidTokenId, + ufmt.Sprintf("unsupported tokenId type: %s", estimatedType), + )) + } +} diff --git a/_deploy/r/gnoswap/common/grc721_token_id_test.gno b/_deploy/r/gnoswap/common/grc721_token_id_test.gno new file mode 100644 index 000000000..6f15e955e --- /dev/null +++ b/_deploy/r/gnoswap/common/grc721_token_id_test.gno @@ -0,0 +1,64 @@ +package common + +import ( + "testing" + + "gno.land/p/demo/grc/grc721" +) + +func TestTokenIdFrom(t *testing.T) { + tests := []struct { + name string + input interface{} + want grc721.TokenID + wantPanic bool + }{ + { + name: "string input", + input: "123", + want: grc721.TokenID("123"), + }, + { + name: "int input", + input: 123, + want: grc721.TokenID("123"), + }, + { + name: "uint64 input", + input: uint64(123), + want: grc721.TokenID("123"), + }, + { + name: "grc721.TokenID input", + input: grc721.TokenID("123"), + want: grc721.TokenID("123"), + }, + { + name: "nil input", + input: nil, + wantPanic: true, + }, + { + name: "unsupported type (byte)", + input: []byte("123"), + wantPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("TokenIdFrom() should have panicked") + } + }() + } + + got := TokenIdFrom(tt.input) + if got != tt.want { + t.Errorf("TokenIdFrom() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/_deploy/r/gnoswap/common/math.gno b/_deploy/r/gnoswap/common/math.gno new file mode 100644 index 000000000..ae626926b --- /dev/null +++ b/_deploy/r/gnoswap/common/math.gno @@ -0,0 +1,100 @@ +package common + +import ( + "gno.land/p/demo/ufmt" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" + + "gno.land/r/gnoswap/v1/consts" +) + +// I64Min returns the minimum of two int64 values +func I64Min(x, y int64) int64 { + if x < y { + return x + } + return y +} + +// I64Max returns the maximum of two int64 values +func I64Max(x, y int64) int64 { + if x > y { + return x + } + return y +} + +// U64Min returns the minimum of two uint64 values +func U64Min(x, y uint64) uint64 { + if x < y { + return x + } + return y +} + +// U64Max returns the maximum of two uint64 values +func U64Max(x, y uint64) uint64 { + if x > y { + return x + } + return y +} + +// I256Min returns the minimum of two Int256 values +func I256Min(x, y *i256.Int) *i256.Int { + if x.Cmp(y) < 0 { + return x + } + return y +} + +// I256Max returns the maximum of two Int256 values +func I256Max(x, y *i256.Int) *i256.Int { + if x.Cmp(y) > 0 { + return x + } + return y +} + +// U256Min returns the minimum of two Uint256 values +func U256Min(x, y *u256.Uint) *u256.Uint { + if x.Cmp(y) < 0 { + return x + } + return y +} + +// U256Max returns the maximum of two Uint256 values +func U256Max(x, y *u256.Uint) *u256.Uint { + if x.Cmp(y) > 0 { + return x + } + return y +} + +// SafeConvertUint256ToInt256 converts a uint256.Uint to int256.Int and returns it. +// If the value is greater than the maximum int256 value, it panics. +func SafeConvertUint256ToInt256(x *u256.Uint) *i256.Int { + if x.Gt(u256.MustFromDecimal(consts.MAX_INT256)) { + panic(addDetailToError( + errOverflow, + ufmt.Sprintf("can not convert %s to int256", x.ToString()), + )) + } + return i256.FromUint256(x) +} + +// SafeConvertUint256ToUint64 converts a uint256.Uint to uint64 and returns it. +// If the value is greater than the maximum uint64 value, it panics. +func SafeConvertUint256ToUint64(x *u256.Uint) uint64 { + value, overflow := x.Uint64WithOverflow() + if overflow { + panic(addDetailToError( + errOverflow, + ufmt.Sprintf("can not convert %s to uint64", x.ToString()), + )) + } + + return value +} diff --git a/_deploy/r/gnoswap/common/math_test.gno b/_deploy/r/gnoswap/common/math_test.gno new file mode 100644 index 000000000..8c9088a96 --- /dev/null +++ b/_deploy/r/gnoswap/common/math_test.gno @@ -0,0 +1,234 @@ +package common + +import ( + "testing" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestI64Min(t *testing.T) { + tests := []struct { + x, y, want int64 + }{ + {1, 2, 1}, + {-1, 1, -1}, + {5, 5, 5}, + {-10, -5, -10}, + {9223372036854775807, 0, 0}, // Max int64 + } + + for _, tt := range tests { + got := I64Min(tt.x, tt.y) + if got != tt.want { + t.Errorf("I64Min(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestI64Max(t *testing.T) { + tests := []struct { + x, y, want int64 + }{ + {1, 2, 2}, + {-1, 1, 1}, + {5, 5, 5}, + {-10, -5, -5}, + {9223372036854775807, 0, 9223372036854775807}, // Max int64 + } + + for _, tt := range tests { + got := I64Max(tt.x, tt.y) + if got != tt.want { + t.Errorf("I64Max(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestU64Min(t *testing.T) { + tests := []struct { + x, y, want uint64 + }{ + {1, 2, 1}, + {0, 1, 0}, + {5, 5, 5}, + {10, 5, 5}, + {18446744073709551615, 0, 0}, // Max uint64 + } + + for _, tt := range tests { + got := U64Min(tt.x, tt.y) + if got != tt.want { + t.Errorf("U64Min(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestU64Max(t *testing.T) { + tests := []struct { + x, y, want uint64 + }{ + {1, 2, 2}, + {0, 1, 1}, + {5, 5, 5}, + {10, 5, 10}, + {18446744073709551615, 0, 18446744073709551615}, // Max uint64 + } + + for _, tt := range tests { + got := U64Max(tt.x, tt.y) + if got != tt.want { + t.Errorf("U64Max(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestI256Min(t *testing.T) { + tests := []struct { + x, y string // hex strings for creating Int + want string + }{ + {"1", "2", "1"}, + {"-1", "1", "-1"}, + {"5", "5", "5"}, + {"-10", "-5", "-10"}, + } + + for _, tt := range tests { + x, _ := i256.FromDecimal(tt.x) + y, _ := i256.FromDecimal(tt.y) + want, _ := i256.FromDecimal(tt.want) + got := I256Min(x, y) + if got.Cmp(want) != 0 { + t.Errorf("I256Min(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestI256Max(t *testing.T) { + tests := []struct { + x, y string // hex strings for creating Int + want string + }{ + {"1", "2", "2"}, + {"-1", "1", "1"}, + {"5", "5", "5"}, + {"-10", "-5", "-5"}, + } + + for _, tt := range tests { + x, _ := i256.FromDecimal(tt.x) + y, _ := i256.FromDecimal(tt.y) + want, _ := i256.FromDecimal(tt.want) + got := I256Max(x, y) + if got.Cmp(want) != 0 { + t.Errorf("I256Max(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestU256Min(t *testing.T) { + tests := []struct { + x, y string // decimal strings for creating Uint + want string + }{ + {"1", "2", "1"}, + {"0", "1", "0"}, + {"5", "5", "5"}, + {"10", "5", "5"}, + } + + for _, tt := range tests { + x, _ := u256.FromDecimal(tt.x) + y, _ := u256.FromDecimal(tt.y) + want, _ := u256.FromDecimal(tt.want) + got := U256Min(x, y) + if got.Cmp(want) != 0 { + t.Errorf("U256Min(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestU256Max(t *testing.T) { + tests := []struct { + x, y string // decimal strings for creating Uint + want string + }{ + {"1", "2", "2"}, + {"0", "1", "1"}, + {"5", "5", "5"}, + {"10", "5", "10"}, + } + + for _, tt := range tests { + x, _ := u256.FromDecimal(tt.x) + y, _ := u256.FromDecimal(tt.y) + want, _ := u256.FromDecimal(tt.want) + got := U256Max(x, y) + if got.Cmp(want) != 0 { + t.Errorf("U256Max(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestSafeConvertUint256ToInt256(t *testing.T) { + tests := []struct { + x, want string + shouldPanic bool + }{ + {"0", "0", false}, + {"1", "1", false}, + // max int256 + {"57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819968", false}, + // max int256 + 1 (overflow) + {"57896044618658097711785492504343953926634992332820282019728792003956564819969", "", true}, + } + + for _, tt := range tests { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("SafeConvertUint256ToInt256(%v) did not panic", tt.x) + } + }() + } + + x := u256.MustFromDecimal(tt.x) + got := SafeConvertUint256ToInt256(x) + want := i256.MustFromDecimal(tt.want) + if got.Cmp(want) != 0 { + t.Errorf("SafeConvertUint256ToInt256(%v) = %v, want %v", tt.x, got, want) + } + } +} + +func TestSafeConvertUint256ToUint64(t *testing.T) { + tests := []struct { + x string + want uint64 + shouldPanic bool + }{ + {"0", 0, false}, + {"1", 1, false}, + // max uint64 + {"18446744073709551615", 18446744073709551615, false}, + // max uint64 + 1 (overflow) + {"18446744073709551616", 0, true}, + } + + for _, tt := range tests { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("SafeConvertUint256ToUint64(%v) did not panic", tt.x) + } + }() + } + + x := u256.MustFromDecimal(tt.x) + got := SafeConvertUint256ToUint64(x) + if got != tt.want { + t.Errorf("SafeConvertUint256ToUint64(%v) = %v, want %v", tt.x, got, tt.want) + } + } +}