mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
add Client interface and MockClient for unit tests
This commit is contained in:
parent
494d69c269
commit
f778e8b1fc
7 changed files with 285 additions and 15 deletions
72
core/vm/algorand/client.go
Normal file
72
core/vm/algorand/client.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -93,6 +93,10 @@ func abiType(name string, typ reflect.Type) string {
|
||||||
fields = append(fields, abiType(typ.Field(i).Name, typ.Field(i).Type))
|
fields = append(fields, abiType(typ.Field(i).Name, typ.Field(i).Type))
|
||||||
}
|
}
|
||||||
return strings.Join(fields, ", ")
|
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 {
|
} else {
|
||||||
return fmt.Sprintf(`{"name": "%s", "type": "%s"}`, name, typ.String())
|
return fmt.Sprintf(`{"name": "%s", "type": "%s"}`, name, typ.String())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,23 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// pack encodes the value into bytes.
|
||||||
func pack(value reflect.Value) ([]byte, error) {
|
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()))
|
defn := fmt.Sprintf(`[{"type": "constructor", "inputs": [%s]}]`, abiType("", value.Type()))
|
||||||
abi, err := abi.JSON(strings.NewReader(defn))
|
abi, err := abi.JSON(strings.NewReader(defn))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := abi.Pack("", value.Interface())
|
data, err := abi.Pack("", value.Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
112
core/vm/algorand/output_test.go
Normal file
112
core/vm/algorand/output_test.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -17,34 +17,29 @@
|
||||||
package algorand
|
package algorand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/algorand/go-algorand-sdk/client/v2/algod"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Algorand implements the precompile to access the Algorand blockchain.
|
// Algorand implements the precompile to access the Algorand blockchain.
|
||||||
type Algorand struct {
|
type Algorand struct {
|
||||||
algodAddress string
|
algodClient Client
|
||||||
algodToken string
|
|
||||||
algodClient *algod.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Algorand precompiled contract with a client to access Algorand blockchain.
|
// New creates a new Algorand precompiled contract with a client to access Algorand blockchain.
|
||||||
func New() *Algorand {
|
func New() *Algorand {
|
||||||
algodAddress := os.Getenv("ALGOD_ADDRESS")
|
algodAddress := os.Getenv("ALGOD_ADDRESS")
|
||||||
algodToken := os.Getenv("ALGOD_TOKEN")
|
algodToken := os.Getenv("ALGOD_TOKEN")
|
||||||
algorand := &Algorand{
|
if !strings.HasPrefix(algodAddress, "http://") {
|
||||||
algodAddress: algodAddress,
|
algodAddress = "http://" + algodAddress
|
||||||
algodToken: algodToken,
|
|
||||||
}
|
}
|
||||||
algodClient, err := algod.MakeClient(algodAddress, algodToken)
|
algorand := &Algorand{
|
||||||
if err == nil {
|
algodClient: NewClient(algodAddress, algodToken),
|
||||||
algorand.algodClient = algodClient
|
|
||||||
}
|
}
|
||||||
return algorand
|
return algorand
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +51,9 @@ func (a *Algorand) RequiredGas(input []byte) uint64 {
|
||||||
|
|
||||||
// Run executes the Algorand precompile with the given input.
|
// Run executes the Algorand precompile with the given input.
|
||||||
func (a *Algorand) Run(input []byte) ([]byte, error) {
|
func (a *Algorand) Run(input []byte) ([]byte, error) {
|
||||||
if a.algodClient == nil {
|
err := a.algodClient.CheckStatus()
|
||||||
return nil, fmt.Errorf("cannot connect to Algorand node")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err := UnpackInput(input)
|
params, err := UnpackInput(input)
|
||||||
|
|
@ -67,13 +63,13 @@ func (a *Algorand) Run(input []byte) ([]byte, error) {
|
||||||
var info interface{}
|
var info interface{}
|
||||||
switch params.GetCmdType() {
|
switch params.GetCmdType() {
|
||||||
case AccountCmd:
|
case AccountCmd:
|
||||||
info, err = a.algodClient.AccountInformation(params.(*AccountInput).Address).Do(context.Background())
|
info, err = a.algodClient.GetAccount(params.(*AccountInput).Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info("Algorand.Run", "info", info)
|
log.Info("Algorand.Run", "info", info)
|
||||||
value := reflect.ValueOf(info).FieldByName(params.GetFieldName())
|
value := reflect.ValueOf(info).Elem().FieldByName(params.GetFieldName())
|
||||||
if !value.IsValid() {
|
if !value.IsValid() {
|
||||||
return nil, fmt.Errorf("field %s does not exist", params.GetFieldName())
|
return nil, fmt.Errorf("field %s does not exist", params.GetFieldName())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
core/vm/algorand/precompiled_test.go
Normal file
73
core/vm/algorand/precompiled_test.go
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto/bls12381"
|
"github.com/ethereum/go-ethereum/crypto/bls12381"
|
||||||
"github.com/ethereum/go-ethereum/crypto/bn256"
|
"github.com/ethereum/go-ethereum/crypto/bn256"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
)
|
)
|
||||||
|
|
@ -177,6 +178,7 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
|
||||||
}
|
}
|
||||||
suppliedGas -= gasCost
|
suppliedGas -= gasCost
|
||||||
output, err := p.Run(input)
|
output, err := p.Run(input)
|
||||||
|
log.Info("RunPrecompiledContract", "err", err)
|
||||||
return output, suppliedGas, err
|
return output, suppliedGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue