diff --git a/core/vm/algorand/client.go b/core/vm/algorand/client.go
new file mode 100644
index 0000000000..b83575c0c8
--- /dev/null
+++ b/core/vm/algorand/client.go
@@ -0,0 +1,72 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package algorand
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/algorand/go-algorand-sdk/client/v2/algod"
+ "github.com/algorand/go-algorand-sdk/client/v2/common/models"
+)
+
+// Client is the interface for the Algorand client.
+type Client interface {
+ // GetAccount returns the account information.
+ GetAccount(address string) (*models.Account, error)
+ // CheckStatus checks the status of the client.
+ CheckStatus() error
+}
+
+// AlgorandClient implements the Client interface.
+type AlgorandClient struct {
+ algodAddress string
+ algodToken string
+ algodClient *algod.Client
+}
+
+// NewClient creates a new Algorand client.
+func NewClient(algodAddress, algodToken string) *AlgorandClient {
+ algorandClient := &AlgorandClient{
+ algodAddress: algodAddress,
+ algodToken: algodToken,
+ }
+ algodClient, err := algod.MakeClient(algodAddress, algodToken)
+ if err == nil {
+ algorandClient.algodClient = algodClient
+ }
+ return algorandClient
+}
+
+func (c *AlgorandClient) CheckStatus() error {
+ if c.algodAddress == "" {
+ return fmt.Errorf("algodAddress is not set")
+ }
+ if c.algodToken == "" {
+ return fmt.Errorf("algodToken is not set")
+ }
+ if c.algodClient == nil {
+ return fmt.Errorf("algodClient is not initialized")
+ }
+ return nil
+}
+
+// GetAccount returns the account information.
+func (c *AlgorandClient) GetAccount(address string) (*models.Account, error) {
+ account, err := c.algodClient.AccountInformation(address).Do(context.Background())
+ return &account, err
+}
diff --git a/core/vm/algorand/input.go b/core/vm/algorand/input.go
index d47b00af75..c8cb79c87b 100644
--- a/core/vm/algorand/input.go
+++ b/core/vm/algorand/input.go
@@ -93,6 +93,10 @@ func abiType(name string, typ reflect.Type) string {
fields = append(fields, abiType(typ.Field(i).Name, typ.Field(i).Type))
}
return strings.Join(fields, ", ")
+ } else if typ.Kind() == reflect.Int {
+ return fmt.Sprintf(`{"name": "%s", "type": "int256"}`, name)
+ } else if typ.Kind() == reflect.Uint {
+ return fmt.Sprintf(`{"name": "%s", "type": "uint256"}`, name)
} else {
return fmt.Sprintf(`{"name": "%s", "type": "%s"}`, name, typ.String())
}
diff --git a/core/vm/algorand/output.go b/core/vm/algorand/output.go
index f8df444677..6102407d6b 100644
--- a/core/vm/algorand/output.go
+++ b/core/vm/algorand/output.go
@@ -26,12 +26,23 @@ import (
"github.com/ethereum/go-ethereum/log"
)
+// pack encodes the value into bytes.
func pack(value reflect.Value) ([]byte, error) {
+ // Convert int to int64 or uint to uint64.
+ // This is because the ABI does not support int or uint.
+ // The cast is safe because int/uint is either 32-bit or 64-bit.
+ if value.Kind() == reflect.Int {
+ value = reflect.ValueOf(int64(value.Int()))
+ } else if value.Kind() == reflect.Uint {
+ value = reflect.ValueOf(uint64(value.Uint()))
+ }
+
defn := fmt.Sprintf(`[{"type": "constructor", "inputs": [%s]}]`, abiType("", value.Type()))
abi, err := abi.JSON(strings.NewReader(defn))
if err != nil {
return nil, err
}
+
data, err := abi.Pack("", value.Interface())
if err != nil {
return nil, err
diff --git a/core/vm/algorand/output_test.go b/core/vm/algorand/output_test.go
new file mode 100644
index 0000000000..66285d0a07
--- /dev/null
+++ b/core/vm/algorand/output_test.go
@@ -0,0 +1,112 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package algorand
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func packValue(v any) (any, error) {
+ data, err := pack(reflect.ValueOf(v))
+ if err != nil {
+ return nil, err
+ }
+
+ var vv reflect.Value
+ switch v.(type) {
+ case int:
+ vv = reflect.New(reflect.TypeOf(int64(0)))
+ case uint:
+ vv = reflect.New(reflect.TypeOf(uint64(0)))
+ default:
+ vv = reflect.New(reflect.TypeOf(v))
+ }
+ err = unpack(data, vv.Interface())
+ if err != nil {
+ return nil, err
+ }
+ return vv.Elem().Interface(), nil
+}
+
+func TestPackBool(t *testing.T) {
+ v, err := packValue(true)
+ require.NoError(t, err)
+ require.Equal(t, true, v)
+
+ v, err = packValue(false)
+ require.NoError(t, err)
+ require.Equal(t, false, v)
+}
+
+func TestPackString(t *testing.T) {
+ v, err := packValue("hello")
+ require.NoError(t, err)
+ require.Equal(t, "hello", v)
+}
+
+func TestPackByte(t *testing.T) {
+ v, err := packValue(byte('a'))
+ require.NoError(t, err)
+ require.Equal(t, byte('a'), v)
+}
+
+func TestPackInteger(t *testing.T) {
+ i := 1000000
+
+ v, err := packValue(int(i))
+ require.NoError(t, err)
+ require.Equal(t, int64(i), v)
+
+ v, err = packValue(int8(i))
+ require.NoError(t, err)
+ require.Equal(t, int8(i), v)
+
+ v, err = packValue(int16(i))
+ require.NoError(t, err)
+ require.Equal(t, int16(i), v)
+
+ v, err = packValue(int32(i))
+ require.NoError(t, err)
+ require.Equal(t, int32(i), v)
+
+ v, err = packValue(int64(i))
+ require.NoError(t, err)
+ require.Equal(t, int64(i), v)
+
+ v, err = packValue(uint(i))
+ require.NoError(t, err)
+ require.Equal(t, uint64(i), v)
+
+ v, err = packValue(uint8(i))
+ require.NoError(t, err)
+ require.Equal(t, uint8(i), v)
+
+ v, err = packValue(uint16(i))
+ require.NoError(t, err)
+ require.Equal(t, uint16(i), v)
+
+ v, err = packValue(uint32(i))
+ require.NoError(t, err)
+ require.Equal(t, uint32(i), v)
+
+ v, err = packValue(uint64(i))
+ require.NoError(t, err)
+ require.Equal(t, uint64(i), v)
+}
diff --git a/core/vm/algorand/precompiled.go b/core/vm/algorand/precompiled.go
index 50ed73ede2..b65950a405 100644
--- a/core/vm/algorand/precompiled.go
+++ b/core/vm/algorand/precompiled.go
@@ -17,34 +17,29 @@
package algorand
import (
- "context"
"fmt"
"os"
"reflect"
+ "strings"
- "github.com/algorand/go-algorand-sdk/client/v2/algod"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
// Algorand implements the precompile to access the Algorand blockchain.
type Algorand struct {
- algodAddress string
- algodToken string
- algodClient *algod.Client
+ algodClient Client
}
// New creates a new Algorand precompiled contract with a client to access Algorand blockchain.
func New() *Algorand {
algodAddress := os.Getenv("ALGOD_ADDRESS")
algodToken := os.Getenv("ALGOD_TOKEN")
- algorand := &Algorand{
- algodAddress: algodAddress,
- algodToken: algodToken,
+ if !strings.HasPrefix(algodAddress, "http://") {
+ algodAddress = "http://" + algodAddress
}
- algodClient, err := algod.MakeClient(algodAddress, algodToken)
- if err == nil {
- algorand.algodClient = algodClient
+ algorand := &Algorand{
+ algodClient: NewClient(algodAddress, algodToken),
}
return algorand
}
@@ -56,8 +51,9 @@ func (a *Algorand) RequiredGas(input []byte) uint64 {
// Run executes the Algorand precompile with the given input.
func (a *Algorand) Run(input []byte) ([]byte, error) {
- if a.algodClient == nil {
- return nil, fmt.Errorf("cannot connect to Algorand node")
+ err := a.algodClient.CheckStatus()
+ if err != nil {
+ return nil, err
}
params, err := UnpackInput(input)
@@ -67,13 +63,13 @@ func (a *Algorand) Run(input []byte) ([]byte, error) {
var info interface{}
switch params.GetCmdType() {
case AccountCmd:
- info, err = a.algodClient.AccountInformation(params.(*AccountInput).Address).Do(context.Background())
+ info, err = a.algodClient.GetAccount(params.(*AccountInput).Address)
if err != nil {
return nil, err
}
}
log.Info("Algorand.Run", "info", info)
- value := reflect.ValueOf(info).FieldByName(params.GetFieldName())
+ value := reflect.ValueOf(info).Elem().FieldByName(params.GetFieldName())
if !value.IsValid() {
return nil, fmt.Errorf("field %s does not exist", params.GetFieldName())
}
diff --git a/core/vm/algorand/precompiled_test.go b/core/vm/algorand/precompiled_test.go
new file mode 100644
index 0000000000..3d6636b2d3
--- /dev/null
+++ b/core/vm/algorand/precompiled_test.go
@@ -0,0 +1,73 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package algorand
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand-sdk/client/v2/common/models"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+)
+
+// MockClient is a mock client for testing.
+type MockClient struct {
+ // store is mapping from Algorand addresses to Algorand accounts.
+ store map[string]*models.Account
+}
+
+// NewMockClient creates a new mock client.
+func NewMockClient() *MockClient {
+ store := make(map[string]*models.Account)
+ store["737777777777777777777777777777777777777777777777777UFEJ2CI"] = &models.Account{
+ Address: "737777777777777777777777777777777777777777777777777UFEJ2CI",
+ Amount: 10000000,
+ }
+ return &MockClient{
+ store: store,
+ }
+}
+
+// GetAccount returns the account information.
+func (c *MockClient) GetAccount(address string) (*models.Account, error) {
+ if _, ok := c.store[address]; !ok {
+ return nil, fmt.Errorf("account not found")
+ }
+ return c.store[address], nil
+}
+
+// CheckStatus of MockClient is always successful.
+func (c *MockClient) CheckStatus() error {
+ return nil
+}
+
+// TestRun tests the Run function on an Algorand object with a MockClient.
+func TestRun(t *testing.T) {
+ algorand := &Algorand{
+ algodClient: NewMockClient(),
+ }
+ rawInput, err := common.ParseHexOrString("0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000006416d6f756e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a3733373737373737373737373737373737373737373737373737373737373737373737373737373737373737373737373737375546454a324349000000000000")
+ require.NoError(t, err)
+
+ rawOutput, err := algorand.Run(rawInput)
+ require.NoError(t, err)
+ v := new(uint64)
+ err = unpack(rawOutput, v)
+ require.NoError(t, err)
+ require.Equal(t, uint64(10000000), *v)
+}
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index a7707c6a52..1b61877ad1 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/bls12381"
"github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/crypto/ripemd160"
)
@@ -177,6 +178,7 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
}
suppliedGas -= gasCost
output, err := p.Run(input)
+ log.Info("RunPrecompiledContract", "err", err)
return output, suppliedGas, err
}