diff --git a/pkg/rpcclient/unwrap/unwrap.go b/pkg/rpcclient/unwrap/unwrap.go index fad8bcffa..f4c224308 100644 --- a/pkg/rpcclient/unwrap/unwrap.go +++ b/pkg/rpcclient/unwrap/unwrap.go @@ -196,6 +196,42 @@ func Array(r *result.Invoke, err error) ([]stackitem.Item, error) { return arr, nil } +// ArrayOfBools checks the result for correct state (HALT) and then extracts a +// slice of boolean values from the returned stack item. +func ArrayOfBools(r *result.Invoke, err error) ([]bool, error) { + a, err := Array(r, err) + if err != nil { + return nil, err + } + res := make([]bool, len(a)) + for i := range a { + b, err := a[i].TryBool() + if err != nil { + return nil, fmt.Errorf("element %d is not a boolean: %w", i, err) + } + res[i] = b + } + return res, nil +} + +// ArrayOfBigInts checks the result for correct state (HALT) and then extracts a +// slice of (big) integer values from the returned stack item. +func ArrayOfBigInts(r *result.Invoke, err error) ([]*big.Int, error) { + a, err := Array(r, err) + if err != nil { + return nil, err + } + res := make([]*big.Int, len(a)) + for i := range a { + v, err := a[i].TryInteger() + if err != nil { + return nil, fmt.Errorf("element %d is not an integer: %w", i, err) + } + res[i] = v + } + return res, nil +} + // ArrayOfBytes checks the result for correct state (HALT) and then extracts a // slice of byte slices from the returned stack item. func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) { @@ -214,6 +250,27 @@ func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) { return res, nil } +// ArrayOfUTB8Strings checks the result for correct state (HALT) and then extracts a +// slice of UTF-8 strings from the returned stack item. +func ArrayOfUTF8Strings(r *result.Invoke, err error) ([]string, error) { + a, err := Array(r, err) + if err != nil { + return nil, err + } + res := make([]string, len(a)) + for i := range a { + b, err := a[i].TryBytes() + if err != nil { + return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) + } + if !utf8.Valid(b) { + return nil, fmt.Errorf("element %d is not a UTF-8 string", i) + } + res[i] = string(b) + } + return res, nil +} + // ArrayOfUint160 checks the result for correct state (HALT) and then extracts a // slice of util.Uint160 from the returned stack item. func ArrayOfUint160(r *result.Invoke, err error) ([]util.Uint160, error) { @@ -236,6 +293,28 @@ func ArrayOfUint160(r *result.Invoke, err error) ([]util.Uint160, error) { return res, nil } +// ArrayOfUint256 checks the result for correct state (HALT) and then extracts a +// slice of util.Uint256 from the returned stack item. +func ArrayOfUint256(r *result.Invoke, err error) ([]util.Uint256, error) { + a, err := Array(r, err) + if err != nil { + return nil, err + } + res := make([]util.Uint256, len(a)) + for i := range a { + b, err := a[i].TryBytes() + if err != nil { + return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return nil, fmt.Errorf("element %d is not a uint256: %w", i, err) + } + res[i] = u + } + return res, nil +} + // ArrayOfPublicKeys checks the result for correct state (HALT) and then // extracts a slice of public keys from the returned stack item. func ArrayOfPublicKeys(r *result.Invoke, err error) (keys.PublicKeys, error) { diff --git a/pkg/rpcclient/unwrap/unwrap_test.go b/pkg/rpcclient/unwrap/unwrap_test.go index 5544216cc..b26876a58 100644 --- a/pkg/rpcclient/unwrap/unwrap_test.go +++ b/pkg/rpcclient/unwrap/unwrap_test.go @@ -53,9 +53,24 @@ func TestStdErrors(t *testing.T) { func(r *result.Invoke, err error) (interface{}, error) { return Array(r, err) }, + func(r *result.Invoke, err error) (interface{}, error) { + return ArrayOfBools(r, err) + }, + func(r *result.Invoke, err error) (interface{}, error) { + return ArrayOfBigInts(r, err) + }, func(r *result.Invoke, err error) (interface{}, error) { return ArrayOfBytes(r, err) }, + func(r *result.Invoke, err error) (interface{}, error) { + return ArrayOfUTF8Strings(r, err) + }, + func(r *result.Invoke, err error) (interface{}, error) { + return ArrayOfUint160(r, err) + }, + func(r *result.Invoke, err error) (interface{}, error) { + return ArrayOfUint256(r, err) + }, func(r *result.Invoke, err error) (interface{}, error) { return ArrayOfPublicKeys(r, err) }, @@ -233,6 +248,32 @@ func TestArray(t *testing.T) { require.Equal(t, stackitem.Make(42), a[0]) } +func TestArrayOfBools(t *testing.T) { + _, err := ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) + require.Error(t, err) + + _, err = ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make("reallybigstringthatcantbeanumberandthuscantbeconvertedtobool")})}}, nil) + require.Error(t, err) + + a, err := ArrayOfBools(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(true)})}}, nil) + require.NoError(t, err) + require.Equal(t, 1, len(a)) + require.Equal(t, true, a[0]) +} + +func TestArrayOfBigInts(t *testing.T) { + _, err := ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) + require.Error(t, err) + + _, err = ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil) + require.Error(t, err) + + a, err := ArrayOfBigInts(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(42)})}}, nil) + require.NoError(t, err) + require.Equal(t, 1, len(a)) + require.Equal(t, big.NewInt(42), a[0]) +} + func TestArrayOfBytes(t *testing.T) { _, err := ArrayOfBytes(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) require.Error(t, err) @@ -246,6 +287,22 @@ func TestArrayOfBytes(t *testing.T) { require.Equal(t, []byte("some"), a[0]) } +func TestArrayOfUTF8Strings(t *testing.T) { + _, err := ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) + require.Error(t, err) + + _, err = ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil) + require.Error(t, err) + + _, err = ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte{0, 0xff})})}}, nil) + require.Error(t, err) + + a, err := ArrayOfUTF8Strings(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make("some")})}}, nil) + require.NoError(t, err) + require.Equal(t, 1, len(a)) + require.Equal(t, "some", a[0]) +} + func TestArrayOfUint160(t *testing.T) { _, err := ArrayOfUint160(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) require.Error(t, err) @@ -263,6 +320,23 @@ func TestArrayOfUint160(t *testing.T) { require.Equal(t, u160, uints[0]) } +func TestArrayOfUint256(t *testing.T) { + _, err := ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) + require.Error(t, err) + + _, err = ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{})})}}, nil) + require.Error(t, err) + + _, err = ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make([]byte("some"))})}}, nil) + require.Error(t, err) + + u256 := util.Uint256{1, 2, 3} + uints, err := ArrayOfUint256(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{stackitem.Make(u256.BytesBE())})}}, nil) + require.NoError(t, err) + require.Equal(t, 1, len(uints)) + require.Equal(t, u256, uints[0]) +} + func TestArrayOfPublicKeys(t *testing.T) { _, err := ArrayOfPublicKeys(&result.Invoke{State: "HALT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil) require.Error(t, err)