Skip to content

Commit

Permalink
AVM: Use avm-abi repo (#4375)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonpaulos authored Aug 16, 2022
1 parent fd488f8 commit f156f69
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 3,469 deletions.
75 changes: 73 additions & 2 deletions cmd/goal/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -32,9 +33,9 @@ import (

"github.com/spf13/cobra"

"github.com/algorand/avm-abi/abi"
"github.com/algorand/go-algorand/crypto"
apiclient "github.com/algorand/go-algorand/daemon/algod/api/client"
"github.com/algorand/go-algorand/data/abi"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
Expand Down Expand Up @@ -1169,6 +1170,76 @@ func populateMethodCallReferenceArgs(sender string, currentApp uint64, types []s
return resolvedIndexes, nil
}

// maxAppArgs is the maximum number of arguments for an application call transaction, in compliance
// with ARC-4. Currently this is the same as the MaxAppArgs consensus parameter, but the
// difference is that the consensus parameter is liable to change in a future consensus upgrade.
// However, the ARC-4 ABI argument encoding **MUST** always remain the same.
const maxAppArgs = 16

// The tuple threshold is maxAppArgs, minus 1 for the method selector in the first app arg,
// minus 1 for the final app argument becoming a tuple of the remaining method args
const methodArgsTupleThreshold = maxAppArgs - 2

// parseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes
// it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format)
// if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple
func parseMethodArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error {
abiTypes := make([]abi.Type, len(argTypes))
for i, typeString := range argTypes {
abiType, err := abi.TypeOf(typeString)
if err != nil {
return err
}
abiTypes[i] = abiType
}

if len(abiTypes) != len(jsonArgs) {
return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTypes))
}

// Up to 16 app arguments can be passed to app call. First is reserved for method selector,
// and the rest are for method call arguments. But if more than 15 method call arguments
// are present, then the method arguments after the 14th are placed in a tuple in the last
// app argument slot
if len(abiTypes) > maxAppArgs-1 {
typesForTuple := make([]abi.Type, len(abiTypes)-methodArgsTupleThreshold)
copy(typesForTuple, abiTypes[methodArgsTupleThreshold:])

compactedType, err := abi.MakeTupleType(typesForTuple)
if err != nil {
return err
}

abiTypes = append(abiTypes[:methodArgsTupleThreshold], compactedType)

tupleValues := make([]json.RawMessage, len(jsonArgs)-methodArgsTupleThreshold)
for i, jsonArg := range jsonArgs[methodArgsTupleThreshold:] {
tupleValues[i] = []byte(jsonArg)
}

remainingJSON, err := json.Marshal(tupleValues)
if err != nil {
return err
}

jsonArgs = append(jsonArgs[:methodArgsTupleThreshold], string(remainingJSON))
}

// parse JSON value to ABI encoded bytes
for i := 0; i < len(jsonArgs); i++ {
interfaceVal, err := abiTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i]))
if err != nil {
return err
}
abiEncoded, err := abiTypes[i].Encode(interfaceVal)
if err != nil {
return err
}
*applicationArgs = append(*applicationArgs, abiEncoded)
}
return nil
}

var methodAppCmd = &cobra.Command{
Use: "method",
Short: "Invoke an ABI method",
Expand Down Expand Up @@ -1284,7 +1355,7 @@ var methodAppCmd = &cobra.Command{
basicArgValues[basicArgIndex] = strconv.Itoa(resolved)
}

err = abi.ParseArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs)
err = parseMethodArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs)
if err != nil {
reportErrorf("cannot parse arguments to ABI encoding: %v", err)
}
Expand Down
143 changes: 143 additions & 0 deletions cmd/goal/application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (C) 2019-2022 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package main

import (
"fmt"
"testing"

"github.com/algorand/go-algorand/test/partitiontest"
"github.com/stretchr/testify/require"
)

func TestParseMethodArgJSONtoByteSlice(t *testing.T) {
partitiontest.PartitionTest(t)

makeRepeatSlice := func(size int, value string) []string {
slice := make([]string, size)
for i := range slice {
slice[i] = value
}
return slice
}

tests := []struct {
argTypes []string
jsonArgs []string
expectedAppArgs [][]byte
}{
{
argTypes: []string{},
jsonArgs: []string{},
expectedAppArgs: [][]byte{},
},
{
argTypes: []string{"uint8"},
jsonArgs: []string{"100"},
expectedAppArgs: [][]byte{{100}},
},
{
argTypes: []string{"uint8", "uint16"},
jsonArgs: []string{"100", "65535"},
expectedAppArgs: [][]byte{{100}, {255, 255}},
},
{
argTypes: makeRepeatSlice(15, "string"),
jsonArgs: []string{
`"a"`,
`"b"`,
`"c"`,
`"d"`,
`"e"`,
`"f"`,
`"g"`,
`"h"`,
`"i"`,
`"j"`,
`"k"`,
`"l"`,
`"m"`,
`"n"`,
`"o"`,
},
expectedAppArgs: [][]byte{
{00, 01, 97},
{00, 01, 98},
{00, 01, 99},
{00, 01, 100},
{00, 01, 101},
{00, 01, 102},
{00, 01, 103},
{00, 01, 104},
{00, 01, 105},
{00, 01, 106},
{00, 01, 107},
{00, 01, 108},
{00, 01, 109},
{00, 01, 110},
{00, 01, 111},
},
},
{
argTypes: makeRepeatSlice(16, "string"),
jsonArgs: []string{
`"a"`,
`"b"`,
`"c"`,
`"d"`,
`"e"`,
`"f"`,
`"g"`,
`"h"`,
`"i"`,
`"j"`,
`"k"`,
`"l"`,
`"m"`,
`"n"`,
`"o"`,
`"p"`,
},
expectedAppArgs: [][]byte{
{00, 01, 97},
{00, 01, 98},
{00, 01, 99},
{00, 01, 100},
{00, 01, 101},
{00, 01, 102},
{00, 01, 103},
{00, 01, 104},
{00, 01, 105},
{00, 01, 106},
{00, 01, 107},
{00, 01, 108},
{00, 01, 109},
{00, 01, 110},
{00, 04, 00, 07, 00, 01, 111, 00, 01, 112},
},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("index=%d", i), func(t *testing.T) {
applicationArgs := [][]byte{}
err := parseMethodArgJSONtoByteSlice(test.argTypes, test.jsonArgs, &applicationArgs)
require.NoError(t, err)
require.Equal(t, test.expectedAppArgs, applicationArgs)
})
}
}
Loading

0 comments on commit f156f69

Please sign in to comment.