Skip to content

Commit

Permalink
Merge pull request #130 from m-Peter/eth-get-code-rpc-endpoint
Browse files Browse the repository at this point in the history
Implement the `eth_getCode` JSON-RPC endpoint
  • Loading branch information
m-Peter authored Mar 5, 2024
2 parents e805a1d + 3929b93 commit 2c1d22c
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 28 deletions.
31 changes: 22 additions & 9 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,28 @@ func (b *BlockChainAPI) EstimateGas(
return hexutil.Uint64(estimatedGas), nil
}

// GetCode returns the code stored at the given address in
// the state for the given block number.
func (b *BlockChainAPI) GetCode(
ctx context.Context,
address common.Address,
blockNumberOrHash *rpc.BlockNumberOrHash,
) (hexutil.Bytes, error) {
// todo support previous block heights
if blockNumberOrHash != nil && *blockNumberOrHash.BlockNumber != rpc.LatestBlockNumber {
// but still return latest for now
b.logger.Warn().Msg("get code for special blocks not supported")
}

code, err := b.evm.GetCode(ctx, address, 0)
if err != nil {
b.logger.Error().Err(err).Msg("failed to retrieve account code")
return nil, err
}

return hexutil.Bytes(code), nil
}

// handleError takes in an error and in case the error is of type ErrNotFound
// it returns nil instead of an error since that is according to the API spec,
// if the error is not of type ErrNotFound it will return the error and the generic
Expand Down Expand Up @@ -696,15 +718,6 @@ func (b *BlockChainAPI) SendTransaction(
return common.Hash{}, errs.ErrNotSupported
}

// GetCode returns the code stored at the given address in the state for the given block number.
func (b *BlockChainAPI) GetCode(
ctx context.Context,
address common.Address,
blockNumberOrHash *rpc.BlockNumberOrHash,
) (hexutil.Bytes, error) {
return nil, errs.ErrNotSupported
}

// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
func (b *BlockChainAPI) GetProof(
ctx context.Context,
Expand Down
7 changes: 7 additions & 0 deletions integration/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,13 @@ func TestE2E_API_DeployEvents(t *testing.T) {

contractAddress := rcp.ContractAddress

// Check code retrieval
code, err := rpcTester.getCode(contractAddress)
require.NoError(t, err)
// The deployed byte code is a subset of the byte code provided in
// contract deployment tx.
assert.Contains(t, hex.EncodeToString(deployData), hex.EncodeToString(code))

callRetrieve, err := storeContract.call("retrieve")
require.NoError(t, err)

Expand Down
21 changes: 21 additions & 0 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,27 @@ func (r *rpcTest) call(
return result, nil
}

func (r *rpcTest) getCode(from common.Address) ([]byte, error) {
rpcRes, err := r.request(
"eth_getCode",
fmt.Sprintf(
`["%s","latest"]`,
from.Hex(),
),
)
if err != nil {
return nil, err
}

var code hexutil.Bytes
err = json.Unmarshal(rpcRes, &code)
if err != nil {
return nil, err
}

return code, nil
}

func uintHex(x uint64) string {
return fmt.Sprintf("0x%x", x)
}
Expand Down
9 changes: 9 additions & 0 deletions services/requester/cadence/get_code.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import EVM

access(all)
fun main(hexEncodedAddress: String): String {
let addressBytes = hexEncodedAddress.decodeHex().toConstantSized<[UInt8; 20]>()!
let address = EVM.EVMAddress(bytes: addressBytes)

return String.encodeHex(address.code())
}
91 changes: 72 additions & 19 deletions services/requester/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ var (

//go:embed cadence/get_nonce.cdc
getNonceScript []byte

//go:embed cadence/get_code.cdc
getCodeScript []byte
)

const minFlowBalance = 2
Expand Down Expand Up @@ -70,6 +73,10 @@ type Requester interface {

// GetNonce gets nonce from the network.
GetNonce(ctx context.Context, address common.Address) (uint64, error)

// GetCode returns the code stored at the given address in
// the state for the given block number.
GetCode(ctx context.Context, address common.Address, height uint64) ([]byte, error)
}

var _ Requester = &EVM{}
Expand Down Expand Up @@ -242,9 +249,7 @@ func (e *EVM) signAndSend(ctx context.Context, script []byte, args ...cadence.Va

func (e *EVM) GetBalance(ctx context.Context, address common.Address, height uint64) (*big.Int, error) {
// todo make sure provided height is used
hexEncodedAddress, err := cadence.NewString(
strings.TrimPrefix(address.Hex(), "0x"),
)
hexEncodedAddress, err := addressToCadenceString(address)
if err != nil {
return nil, err
}
Expand All @@ -269,9 +274,7 @@ func (e *EVM) GetBalance(ctx context.Context, address common.Address, height uin
}

func (e *EVM) GetNonce(ctx context.Context, address common.Address) (uint64, error) {
hexEncodedAddress, err := cadence.NewString(
strings.TrimPrefix(address.Hex(), "0x"),
)
hexEncodedAddress, err := addressToCadenceString(address)
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -302,9 +305,7 @@ func (e *EVM) Call(ctx context.Context, address common.Address, data []byte) ([]
}

// todo make "to" address optional, this can be used for contract deployment simulations
hexEncodedAddress, err := cadence.NewString(
strings.TrimPrefix(address.Hex(), "0x"),
)
hexEncodedAddress, err := addressToCadenceString(address)
if err != nil {
return nil, err
}
Expand All @@ -323,24 +324,18 @@ func (e *EVM) Call(ctx context.Context, address common.Address, data []byte) ([]
return nil, fmt.Errorf("failed to execute script: %w", err)
}

// sanity check, should never occur
if _, ok := scriptResult.(cadence.String); !ok {
e.logger.Panic().Msg(fmt.Sprintf("failed to convert script result %v to String", scriptResult))
}

output := scriptResult.(cadence.String).ToGoValue().(string)
byteOutput, err := hex.DecodeString(output)
output, err := cadenceStringToBytes(scriptResult)
if err != nil {
return nil, fmt.Errorf("failed to convert call output: %w", err)
return nil, err
}

e.logger.Info().
Str("address", address.Hex()).
Str("data", fmt.Sprintf("%x", data)).
Str("result", output).
Str("result", hex.EncodeToString(output)).
Msg("call executed")

return byteOutput, nil
return output, nil
}

func (e *EVM) EstimateGas(ctx context.Context, data []byte) (uint64, error) {
Expand Down Expand Up @@ -380,6 +375,43 @@ func (e *EVM) EstimateGas(ctx context.Context, data []byte) (uint64, error) {
return gasUsed, nil
}

func (e *EVM) GetCode(
ctx context.Context,
address common.Address,
height uint64,
) ([]byte, error) {
e.logger.Debug().
Str("addess", address.Hex()).
Str("height", fmt.Sprintf("%d", height)).
Msg("get code")

hexEncodedAddress, err := addressToCadenceString(address)
if err != nil {
return nil, err
}

value, err := e.client.ExecuteScriptAtLatestBlock(
ctx,
e.replaceAddresses(getCodeScript),
[]cadence.Value{hexEncodedAddress},
)
if err != nil {
return nil, fmt.Errorf("failed to execute script for get code: %w", err)
}

code, err := cadenceStringToBytes(value)
if err != nil {
return nil, err
}

e.logger.Info().
Str("address", address.Hex()).
Str("code size", fmt.Sprintf("%d", len(code))).
Msg("get code executed")

return code, nil
}

// getSignerNetworkInfo loads the signer account from network and returns key index and sequence number
func (e *EVM) getSignerNetworkInfo(ctx context.Context) (int, uint64, error) {
account, err := e.client.GetAccount(ctx, e.address)
Expand Down Expand Up @@ -418,6 +450,27 @@ func (e *EVM) replaceAddresses(script []byte) []byte {
return []byte(s)
}

func addressToCadenceString(address common.Address) (cadence.String, error) {
return cadence.NewString(
strings.TrimPrefix(address.Hex(), "0x"),
)
}

func cadenceStringToBytes(value cadence.Value) ([]byte, error) {
cdcString, ok := value.(cadence.String)
if !ok {
return nil, fmt.Errorf("failed to convert cadence value to string: %v", value)
}

hexEncodedCode := cdcString.ToGoValue().(string)
code, err := hex.DecodeString(hexEncodedCode)
if err != nil {
return nil, fmt.Errorf("failed to decode string to byte array: %w", err)
}

return code, nil
}

// TODO(m-Peter): Consider moving this to flow-go repository
func getErrorForCode(errorCode uint64) error {
switch evmTypes.ErrorCode(errorCode) {
Expand Down

0 comments on commit 2c1d22c

Please sign in to comment.