summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHang Su <87964331+ahangsu@users.noreply.github.com>2022-01-13 14:02:06 -0500
committerGitHub <noreply@github.com>2022-01-13 14:02:06 -0500
commit326d04a5e4ebe5b500684db45f10abe4468e21d4 (patch)
tree3e0c31a10f2d42d3e02eb1729741cdea45120a95
parenta1edadc6b3890c7aa396636538a06896b1c8cb14 (diff)
Removing C/crypto dependencies from `data/abi` package (#3375)
-rw-r--r--data/abi/abi_encode.go16
-rw-r--r--data/abi/abi_encode_test.go159
-rw-r--r--data/abi/abi_json.go57
-rw-r--r--data/abi/abi_json_test.go27
-rw-r--r--data/abi/abi_type.go16
-rw-r--r--data/basics/address.go17
6 files changed, 185 insertions, 107 deletions
diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go
index 1f14af71a..8e5a49086 100644
--- a/data/abi/abi_encode.go
+++ b/data/abi/abi_encode.go
@@ -170,14 +170,14 @@ func encodeInt(intValue interface{}, bitSize uint16) ([]byte, error) {
return nil, fmt.Errorf("passed in numeric value should be non negative")
}
- bytes := bigInt.Bytes()
- if len(bytes) > int(bitSize/8) {
- return nil, fmt.Errorf("input value bit size %d > abi type bit size %d", len(bytes)*8, bitSize)
+ castedBytes := make([]byte, bitSize/8)
+
+ if bigInt.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bitSize))) >= 0 {
+ return nil, fmt.Errorf("input value bit size %d > abi type bit size %d", bigInt.BitLen(), bitSize)
}
- zeroPadding := make([]byte, bitSize/8-uint16(len(bytes)))
- buffer := append(zeroPadding, bytes...)
- return buffer, nil
+ bigInt.FillBytes(castedBytes)
+ return castedBytes, nil
}
// inferToSlice infers an interface element to a slice of interface{}, returns error if it cannot infer successfully
@@ -201,7 +201,7 @@ func inferToSlice(value interface{}) ([]interface{}, error) {
// encodeTuple encodes slice-of-interface of golang values to bytes, following ABI encoding rules
func encodeTuple(value interface{}, childT []Type) ([]byte, error) {
- if len(childT) >= (1 << 16) {
+ if len(childT) >= abiEncodingLengthLimit {
return nil, fmt.Errorf("abi child type number exceeds uint16 maximum")
}
values, err := inferToSlice(value)
@@ -277,7 +277,7 @@ func encodeTuple(value interface{}, childT []Type) ([]byte, error) {
if isDynamicIndex[i] {
// calculate where the index of dynamic value encoding byte start
headValue := headLength + tailCurrLength
- if headValue >= (1 << 16) {
+ if headValue >= abiEncodingLengthLimit {
return nil, fmt.Errorf("cannot encode abi tuple: encode length exceeds uint16 maximum")
}
binary.BigEndian.PutUint16(heads[i], uint16(headValue))
diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go
index 64296680b..66060f5ce 100644
--- a/data/abi/abi_encode_test.go
+++ b/data/abi/abi_encode_test.go
@@ -29,21 +29,21 @@ import (
)
const (
- UintStepLength = 8
- UintBegin = 8
- UintEnd = 512
- UintRandomTestPoints = 1000
- UintTestCaseCount = 200
- UfixedPrecision = 160
- UfixedRandomTestPoints = 20
- TupleMaxLength = 10
- ByteTestCaseCount = 1 << 8
- BoolTestCaseCount = 2
- AddressTestCaseCount = 300
- StringTestCaseCount = 10
- StringTestCaseSpecLenCount = 5
- TakeNum = 10
- TupleTestCaseCount = 100
+ uintStepLength = 8
+ uintBegin = 8
+ uintEnd = 512
+ uintRandomTestPoints = 1000
+ uintTestCaseCount = 200
+ ufixedPrecision = 160
+ ufixedRandomTestPoints = 20
+ tupleMaxLength = 10
+ byteTestCaseCount = 1 << 8
+ boolTestCaseCount = 2
+ addressTestCaseCount = 300
+ stringTestCaseCount = 10
+ stringTestCaseSpecLenCount = 5
+ takeNum = 10
+ tupleTestCaseCount = 100
)
/*
@@ -74,18 +74,17 @@ func TestEncodeValid(t *testing.T) {
// encoding test for uint type, iterating through all uint sizes
// randomly pick 1000 valid uint values and check if encoded value match with expected
- for intSize := UintBegin; intSize <= UintEnd; intSize += UintStepLength {
- upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize))
+ for intSize := uintBegin; intSize <= uintEnd; intSize += uintStepLength {
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(intSize))
uintType, err := makeUintType(intSize)
require.NoError(t, err, "make uint type fail")
- for i := 0; i < UintRandomTestPoints; i++ {
+ for i := 0; i < uintRandomTestPoints; i++ {
randomInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
- randomIntByte := randomInt.Bytes()
- expected := make([]byte, intSize/8-len(randomIntByte))
- expected = append(expected, randomIntByte...)
+ expected := make([]byte, intSize/8)
+ randomInt.FillBytes(expected)
uintEncode, err := uintType.Encode(randomInt)
require.NoError(t, err, "encoding from uint type fail")
@@ -94,9 +93,9 @@ func TestEncodeValid(t *testing.T) {
}
// 2^[bitSize] - 1 test
// check if uint<bitSize> can contain max uint value (2^bitSize - 1)
- largest := big.NewInt(0).Add(
+ largest := new(big.Int).Add(
upperLimit,
- big.NewInt(1).Neg(big.NewInt(1)),
+ new(big.Int).Neg(big.NewInt(1)),
)
encoded, err := uintType.Encode(largest)
require.NoError(t, err, "largest uint encode error")
@@ -106,27 +105,26 @@ func TestEncodeValid(t *testing.T) {
// encoding test for ufixed, iterating through all the valid ufixed bitSize and precision
// randomly generate 10 big int values for ufixed numerator and check if encoded value match with expected
// also check if ufixed can fit max numerator (2^bitSize - 1) under specific byte bitSize
- for size := UintBegin; size <= UintEnd; size += UintStepLength {
- upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size))
+ for size := uintBegin; size <= uintEnd; size += uintStepLength {
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(size))
largest := big.NewInt(0).Add(
upperLimit,
- big.NewInt(1).Neg(big.NewInt(1)),
+ new(big.Int).Neg(big.NewInt(1)),
)
- for precision := 1; precision <= UfixedPrecision; precision++ {
+ for precision := 1; precision <= ufixedPrecision; precision++ {
typeUfixed, err := makeUfixedType(size, precision)
require.NoError(t, err, "make ufixed type fail")
- for i := 0; i < UfixedRandomTestPoints; i++ {
+ for i := 0; i < ufixedRandomTestPoints; i++ {
randomInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
encodedUfixed, err := typeUfixed.Encode(randomInt)
require.NoError(t, err, "ufixed encode fail")
- randomBytes := randomInt.Bytes()
- buffer := make([]byte, size/8-len(randomBytes))
- buffer = append(buffer, randomBytes...)
- require.Equal(t, buffer, encodedUfixed, "encode ufixed not match with expected")
+ expected := make([]byte, size/8)
+ randomInt.FillBytes(expected)
+ require.Equal(t, expected, encodedUfixed, "encode ufixed not match with expected")
}
// (2^[bitSize] - 1) / (10^[precision]) test
ufixedLargestEncode, err := typeUfixed.Encode(largest)
@@ -138,14 +136,13 @@ func TestEncodeValid(t *testing.T) {
// encoding test for address, since address is 32 byte, it can be considered as 256 bit uint
// randomly generate 1000 uint256 and make address values, check if encoded value match with expected
- upperLimit := big.NewInt(0).Lsh(big.NewInt(1), addressByteSize<<3)
- for i := 0; i < UintRandomTestPoints; i++ {
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3)
+ for i := 0; i < uintRandomTestPoints; i++ {
randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
- rand256Bytes := randomAddrInt.Bytes()
- addrBytesExpected := make([]byte, addressByteSize-len(rand256Bytes))
- addrBytesExpected = append(addrBytesExpected, rand256Bytes...)
+ addrBytesExpected := make([]byte, addressByteSize)
+ randomAddrInt.FillBytes(addrBytesExpected)
addrBytesActual, err := addressType.Encode(addrBytesExpected)
require.NoError(t, err, "address encode fail")
@@ -153,7 +150,7 @@ func TestEncodeValid(t *testing.T) {
}
// encoding test for bool values
- for i := 0; i < BoolTestCaseCount; i++ {
+ for i := 0; i < boolTestCaseCount; i++ {
boolEncode, err := boolType.Encode(i == 1)
require.NoError(t, err, "bool encode fail")
expected := []byte{0x00}
@@ -164,7 +161,7 @@ func TestEncodeValid(t *testing.T) {
}
// encoding test for byte values
- for i := 0; i < ByteTestCaseCount; i++ {
+ for i := 0; i < byteTestCaseCount; i++ {
byteEncode, err := byteType.Encode(byte(i))
require.NoError(t, err, "byte encode fail")
expected := []byte{byte(i)}
@@ -175,8 +172,8 @@ func TestEncodeValid(t *testing.T) {
// we use `gobberish` to generate random utf-8 symbols
// randomly generate utf-8 str from length 1 to 100, each length draw 10 random strs
// check if encoded ABI str match with expected value
- for length := 1; length <= StringTestCaseCount; length++ {
- for i := 0; i < StringTestCaseSpecLenCount; i++ {
+ for length := 1; length <= stringTestCaseCount; length++ {
+ for i := 0; i < stringTestCaseSpecLenCount; i++ {
// generate utf8 strings from `gobberish` at some length
utf8Str := gobberish.GenerateString(length)
// since string is just type alias of `byte[]`, we need to store number of bytes in encoding
@@ -350,11 +347,11 @@ func TestDecodeValid(t *testing.T) {
// decoding test for uint, iterating through all valid uint bitSize
// randomly take 1000 tests on each valid bitSize
// generate bytes from random uint values and decode bytes with additional type information
- for intSize := 8; intSize <= 512; intSize += 8 {
- upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize))
+ for intSize := uintBegin; intSize <= uintEnd; intSize += uintStepLength {
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(intSize))
uintType, err := makeUintType(intSize)
require.NoError(t, err, "make uint type failure")
- for i := 0; i < 1000; i++ {
+ for i := 0; i < uintRandomTestPoints; i++ {
randBig, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
@@ -383,12 +380,12 @@ func TestDecodeValid(t *testing.T) {
// decoding test for ufixed, iterating through all valid ufixed bitSize and precision
// randomly take 10 tests on each valid setting
// generate ufixed bytes and try to decode back with additional type information
- for size := 8; size <= 512; size += 8 {
+ for size := uintBegin; size <= uintEnd; size += uintStepLength {
upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size))
- for precision := 1; precision <= 160; precision++ {
+ for precision := 1; precision <= ufixedPrecision; precision++ {
ufixedType, err := makeUfixedType(size, precision)
require.NoError(t, err, "make ufixed type failure")
- for i := 0; i < 10; i++ {
+ for i := 0; i < ufixedRandomTestPoints; i++ {
randBig, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
@@ -416,17 +413,16 @@ func TestDecodeValid(t *testing.T) {
}
}
- // decoding test for address, randomly take 1000 tests
+ // decoding test for address, randomly take 300 tests
// address is type alias of byte[32], we generate address value with random 256 bit big int values
// we make the expected address value and decode the encoding of expected, check if they match
- upperLimit := big.NewInt(0).Lsh(big.NewInt(1), 256)
- for i := 0; i < 1000; i++ {
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3)
+ for i := 0; i < addressTestCaseCount; i++ {
randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
require.NoError(t, err, "cryptographic random int init fail")
- addressBytes := randomAddrInt.Bytes()
- expected := make([]byte, 32-len(addressBytes))
- expected = append(expected, addressBytes...)
+ expected := make([]byte, addressByteSize)
+ randomAddrInt.FillBytes(expected)
actual, err := addressType.Decode(expected)
require.NoError(t, err, "decoding address should not return error")
@@ -443,7 +439,7 @@ func TestDecodeValid(t *testing.T) {
}
// byte value decoding test, iterating through 256 valid byte value
- for i := 0; i < (1 << 8); i++ {
+ for i := 0; i < byteTestCaseCount; i++ {
byteEncode, err := byteType.Encode(byte(i))
require.NoError(t, err, "byte encode fail")
actual, err := byteType.Decode(byteEncode)
@@ -451,11 +447,11 @@ func TestDecodeValid(t *testing.T) {
require.Equal(t, byte(i), actual, "decode byte not match with expected")
}
- // string value decoding test, test from utf string length 1 to 100
- // randomly take 10 utf-8 strings to make ABI string values
+ // string value decoding test, test from utf string length 1 to 10
+ // randomly take 5 utf-8 strings to make ABI string values
// decode the encoded expected value and check if they match
- for length := 1; length <= 100; length++ {
- for i := 0; i < 10; i++ {
+ for length := 1; length <= stringTestCaseCount; length++ {
+ for i := 0; i < stringTestCaseSpecLenCount; i++ {
expected := gobberish.GenerateString(length)
strEncode, err := stringType.Encode(expected)
require.NoError(t, err, "string encode fail")
@@ -898,20 +894,20 @@ func categorySelfRoundTripTest(t *testing.T, category []testUnit) {
}
func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
- (*pool)[Uint] = make([]testUnit, UintTestCaseCount*UintEnd/UintStepLength)
- (*pool)[Ufixed] = make([]testUnit, UfixedPrecision*UintEnd/UintStepLength)
+ (*pool)[Uint] = make([]testUnit, uintTestCaseCount*uintEnd/uintStepLength)
+ (*pool)[Ufixed] = make([]testUnit, ufixedPrecision*uintEnd/uintStepLength)
uintIndex := 0
ufixedIndex := 0
- for bitSize := UintBegin; bitSize <= UintEnd; bitSize += UintStepLength {
+ for bitSize := uintBegin; bitSize <= uintEnd; bitSize += uintStepLength {
max := new(big.Int).Lsh(big.NewInt(1), uint(bitSize))
uintT, err := makeUintType(bitSize)
require.NoError(t, err, "make uint type failure")
uintTstr := uintT.String()
- for j := 0; j < UintTestCaseCount; j++ {
+ for j := 0; j < uintTestCaseCount; j++ {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random uint, should be no error")
@@ -922,7 +918,7 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
uintIndex++
}
- for precision := 1; precision <= UfixedPrecision; precision++ {
+ for precision := 1; precision <= ufixedPrecision; precision++ {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random ufixed, should be no error")
@@ -939,33 +935,32 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
categorySelfRoundTripTest(t, (*pool)[Uint])
categorySelfRoundTripTest(t, (*pool)[Ufixed])
- (*pool)[Byte] = make([]testUnit, ByteTestCaseCount)
- for i := 0; i < ByteTestCaseCount; i++ {
+ (*pool)[Byte] = make([]testUnit, byteTestCaseCount)
+ for i := 0; i < byteTestCaseCount; i++ {
(*pool)[Byte][i] = testUnit{serializedType: byteType.String(), value: byte(i)}
}
categorySelfRoundTripTest(t, (*pool)[Byte])
- (*pool)[Bool] = make([]testUnit, BoolTestCaseCount)
+ (*pool)[Bool] = make([]testUnit, boolTestCaseCount)
(*pool)[Bool][0] = testUnit{serializedType: boolType.String(), value: false}
(*pool)[Bool][1] = testUnit{serializedType: boolType.String(), value: true}
categorySelfRoundTripTest(t, (*pool)[Bool])
maxAddress := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3)
- (*pool)[Address] = make([]testUnit, AddressTestCaseCount)
- for i := 0; i < AddressTestCaseCount; i++ {
+ (*pool)[Address] = make([]testUnit, addressTestCaseCount)
+ for i := 0; i < addressTestCaseCount; i++ {
randAddrVal, err := rand.Int(rand.Reader, maxAddress)
require.NoError(t, err, "generate random value for address, should be no error")
- addrBytes := randAddrVal.Bytes()
- remainBytes := make([]byte, addressByteSize-len(addrBytes))
- addrBytes = append(remainBytes, addrBytes...)
+ addrBytes := make([]byte, addressByteSize)
+ randAddrVal.FillBytes(addrBytes)
(*pool)[Address][i] = testUnit{serializedType: addressType.String(), value: addrBytes}
}
categorySelfRoundTripTest(t, (*pool)[Address])
- (*pool)[String] = make([]testUnit, StringTestCaseCount*StringTestCaseSpecLenCount)
+ (*pool)[String] = make([]testUnit, stringTestCaseCount*stringTestCaseSpecLenCount)
stringIndex := 0
- for length := 1; length <= StringTestCaseCount; length++ {
- for i := 0; i < StringTestCaseSpecLenCount; i++ {
+ for length := 1; length <= stringTestCaseCount; length++ {
+ for i := 0; i < stringTestCaseSpecLenCount; i++ {
(*pool)[String][stringIndex] = testUnit{
serializedType: stringType.String(),
value: gobberish.GenerateString(length),
@@ -1000,21 +995,21 @@ func takeSomeFromCategoryAndGenerateArray(
}
func addArrayRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
- for intIndex := 0; intIndex < len((*pool)[Uint]); intIndex += UintTestCaseCount {
- takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, TakeNum, pool)
+ for intIndex := 0; intIndex < len((*pool)[Uint]); intIndex += uintTestCaseCount {
+ takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, takeNum, pool)
}
- takeSomeFromCategoryAndGenerateArray(t, Byte, 0, TakeNum, pool)
- takeSomeFromCategoryAndGenerateArray(t, Address, 0, TakeNum, pool)
- takeSomeFromCategoryAndGenerateArray(t, String, 0, TakeNum, pool)
- takeSomeFromCategoryAndGenerateArray(t, Bool, 0, TakeNum, pool)
+ takeSomeFromCategoryAndGenerateArray(t, Byte, 0, takeNum, pool)
+ takeSomeFromCategoryAndGenerateArray(t, Address, 0, takeNum, pool)
+ takeSomeFromCategoryAndGenerateArray(t, String, 0, takeNum, pool)
+ takeSomeFromCategoryAndGenerateArray(t, Bool, 0, takeNum, pool)
categorySelfRoundTripTest(t, (*pool)[ArrayStatic])
categorySelfRoundTripTest(t, (*pool)[ArrayDynamic])
}
func addTupleRandomValues(t *testing.T, slotRange BaseType, pool *map[BaseType][]testUnit) {
- for i := 0; i < TupleTestCaseCount; i++ {
- tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(TupleMaxLength))
+ for i := 0; i < tupleTestCaseCount; i++ {
+ tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(tupleMaxLength))
require.NoError(t, err, "generate random tuple length should not return error")
tupleLen := tupleLenBig.Int64() + 1
testUnits := make([]testUnit, tupleLen)
diff --git a/data/abi/abi_json.go b/data/abi/abi_json.go
index a3177c4b2..a71823f0c 100644
--- a/data/abi/abi_json.go
+++ b/data/abi/abi_json.go
@@ -18,12 +18,26 @@ package abi
import (
"bytes"
+ "crypto/sha512"
+ "encoding/base32"
"encoding/json"
"fmt"
- "github.com/algorand/go-algorand/data/basics"
"math/big"
)
+// NOTE: discussion about go-algorand-sdk
+// https://github.com/algorand/go-algorand/pull/3375#issuecomment-1007536841
+
+var base32Encoder = base32.StdEncoding.WithPadding(base32.NoPadding)
+
+func addressCheckSum(addressBytes []byte) ([]byte, error) {
+ if len(addressBytes) != addressByteSize {
+ return nil, fmt.Errorf("address bytes should be of length 32")
+ }
+ hashed := sha512.Sum512_256(addressBytes[:])
+ return hashed[addressByteSize-checksumByteSize:], nil
+}
+
func castBigIntToNearestPrimitive(num *big.Int, bitSize uint16) (interface{}, error) {
if num.BitLen() > int(bitSize) {
return nil, fmt.Errorf("cast big int to nearest primitive failure: %v >= 2^%d", num, bitSize)
@@ -74,17 +88,24 @@ func (t Type) MarshalToJSON(value interface{}) ([]byte, error) {
}
return json.Marshal(byteValue)
case Address:
- var addressInternal basics.Address
+ var addressValueInternal []byte
switch valueCasted := value.(type) {
case []byte:
- copy(addressInternal[:], valueCasted[:])
- return json.Marshal(addressInternal.String())
+ if len(valueCasted) != addressByteSize {
+ return nil, fmt.Errorf("address byte slice length not equal to 32 byte")
+ }
+ addressValueInternal = valueCasted
case [addressByteSize]byte:
- addressInternal = valueCasted
- return json.Marshal(addressInternal.String())
+ copy(addressValueInternal[:], valueCasted[:])
default:
return nil, fmt.Errorf("cannot infer to byte slice/array for marshal to JSON")
}
+ checksum, err := addressCheckSum(addressValueInternal)
+ if err != nil {
+ return nil, err
+ }
+ addressValueInternal = append(addressValueInternal, checksum...)
+ return json.Marshal(base32Encoder.EncodeToString(addressValueInternal))
case ArrayStatic, ArrayDynamic:
values, err := inferToSlice(value)
if err != nil {
@@ -175,13 +196,29 @@ func (t Type) UnmarshalFromJSON(jsonEncoded []byte) (interface{}, error) {
case Address:
var addrStr string
if err := json.Unmarshal(jsonEncoded, &addrStr); err != nil {
- return nil, fmt.Errorf("cannot cast JSON encoded to string: %v", err)
+ return nil, fmt.Errorf("cannot cast JSON encoded to address string: %v", err)
}
- addr, err := basics.UnmarshalChecksumAddress(addrStr)
+ decoded, err := base32Encoder.DecodeString(addrStr)
if err != nil {
- return nil, fmt.Errorf("cannot cast JSON encoded (%s) to address: %v", string(jsonEncoded), err)
+ return nil,
+ fmt.Errorf("cannot cast JSON encoded address string (%s) to address: %v", addrStr, err)
+ }
+ if len(decoded) != addressByteSize+checksumByteSize {
+ return nil,
+ fmt.Errorf(
+ "cannot cast JSON encoded address string (%s) to address: "+
+ "decoded byte length should equal to 36 with address and checksum",
+ string(jsonEncoded),
+ )
+ }
+ checksum, err := addressCheckSum(decoded[:addressByteSize])
+ if err != nil {
+ return nil, err
+ }
+ if !bytes.Equal(checksum, decoded[addressByteSize:]) {
+ return nil, fmt.Errorf("cannot cast JSON encoded address string (%s) to address: decoded checksum unmatch", addrStr)
}
- return addr[:], nil
+ return decoded[:addressByteSize], nil
case ArrayStatic, ArrayDynamic:
if t.childTypes[0].abiTypeID == Byte && bytes.HasPrefix(jsonEncoded, []byte{'"'}) {
var byteArr []byte
diff --git a/data/abi/abi_json_test.go b/data/abi/abi_json_test.go
index b5290c9d5..49083fdea 100644
--- a/data/abi/abi_json_test.go
+++ b/data/abi/abi_json_test.go
@@ -17,12 +17,39 @@
package abi
import (
+ "crypto/rand"
+ "math/big"
"testing"
+ "github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/test/partitiontest"
"github.com/stretchr/testify/require"
)
+func TestRandomAddressEquality(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3)
+ var addrBasics basics.Address
+ var addrABI []byte = make([]byte, addressByteSize)
+
+ for testCaseIndex := 0; testCaseIndex < addressTestCaseCount; testCaseIndex++ {
+ randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ randomAddrInt.FillBytes(addrBasics[:])
+ randomAddrInt.FillBytes(addrABI)
+
+ checkSumBasics := addrBasics.GetChecksum()
+ checkSumABI, err := addressCheckSum(addrABI)
+ require.NoError(t, err, "ABI compute checksum for address slice failed")
+
+ require.Equal(t, checkSumBasics, checkSumABI,
+ "basics.Address computed checksum %v not equal to data.abi computed checksum %v",
+ )
+ }
+}
+
func TestJSONtoInterfaceValid(t *testing.T) {
partitiontest.PartitionTest(t)
var testCases = []struct {
diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go
index 0ff240950..f403916b2 100644
--- a/data/abi/abi_type.go
+++ b/data/abi/abi_type.go
@@ -60,6 +60,15 @@ const (
Tuple
)
+const (
+ addressByteSize = 32
+ checksumByteSize = 4
+ singleByteSize = 1
+ singleBoolSize = 1
+ lengthEncodeByteSize = 2
+ abiEncodingLengthLimit = 1 << 16
+)
+
// Type is the struct that stores information about an ABI value's type.
type Type struct {
abiTypeID BaseType
@@ -405,13 +414,6 @@ func findBoolLR(typeList []Type, index int, delta int) int {
return until
}
-const (
- addressByteSize = 32
- singleByteSize = 1
- singleBoolSize = 1
- lengthEncodeByteSize = 2
-)
-
// ByteLen method calculates the byte length of a static ABI type.
func (t Type) ByteLen() (int, error) {
switch t.abiTypeID {
diff --git a/data/basics/address.go b/data/basics/address.go
index 412b7bf75..5eed1c512 100644
--- a/data/basics/address.go
+++ b/data/basics/address.go
@@ -24,6 +24,23 @@ import (
"github.com/algorand/go-algorand/crypto"
)
+// NOTE: Another (partial) implementation of `basics.Address` is in `data/abi`.
+// The reason of not using this `Address` in `data/abi` is that:
+// - `data/basics` has C dependencies (`go-algorand/crypto`)
+// - `go-algorand-sdk` has dependency to `go-algorand` for `ABI`
+// - if `go-algorand`'s ABI uses `basics.Address`, then it would be
+// impossible to up the version of `go-algorand` in `go-algorand-sdk`
+
+// This is discussed in:
+// - ISSUE https://github.com/algorand/go-algorand/issues/3355
+// - PR https://github.com/algorand/go-algorand/pull/3375
+
+// There are two solutions:
+// - One is to refactoring `crypto.Digest`, `crypto.Hash` and `basics.Address`
+// into packages that does not need `libsodium` crypto dependency
+// - The other is wrapping `libsodium` in a driver interface to make crypto
+// package importable (even if `libsodium` does not exist)
+
type (
// Address is a unique identifier corresponding to ownership of money
Address crypto.Digest