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))
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
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/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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue