diff --git a/api/api.go b/api/api.go index 056c94c8..f0c57a19 100644 --- a/api/api.go +++ b/api/api.go @@ -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 @@ -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, diff --git a/integration/e2e_test.go b/integration/e2e_test.go index a95c7132..a29e8408 100644 --- a/integration/e2e_test.go +++ b/integration/e2e_test.go @@ -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) diff --git a/integration/helpers.go b/integration/helpers.go index 1acaa092..f2e35556 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -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) } diff --git a/services/requester/cadence/get_code.cdc b/services/requester/cadence/get_code.cdc new file mode 100644 index 00000000..7349f4cf --- /dev/null +++ b/services/requester/cadence/get_code.cdc @@ -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()) +} diff --git a/services/requester/requester.go b/services/requester/requester.go index e446c239..fb715833 100644 --- a/services/requester/requester.go +++ b/services/requester/requester.go @@ -43,6 +43,9 @@ var ( //go:embed cadence/get_nonce.cdc getNonceScript []byte + + //go:embed cadence/get_code.cdc + getCodeScript []byte ) const minFlowBalance = 2 @@ -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{} @@ -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 } @@ -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 } @@ -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 } @@ -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) { @@ -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) @@ -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) {