From cb5521ecebeb5447a786f2c6366f9f8dba17f2c0 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Thu, 16 Feb 2023 15:09:01 -0500 Subject: [PATCH] Add eth_getCode support for getting address bytecode (#17215) This is intended to be used to check if an address is an EOA or a contract You can query ```js const apiProxy = getWalletPanelApiProxy() const jsonRpcService = apiProxy.jsonRpcService console.log('BAT rpc service get code: ', await jsonRpcService.getCode('0x0d8775f648430679a709e98d2b0cb6250d2887ef', BraveWallet.CoinType.ETH, '0x1')) console.log('EOA rpc service get code: ', await jsonRpcService.getCode('0x...', BraveWallet.CoinType.ETH, '0x1')) ``` This would output the bytecode in hex format for the BAT price and the string 0x for the EOA address. --- .../brave_wallet/browser/json_rpc_service.cc | 40 +++++++++++++ .../brave_wallet/browser/json_rpc_service.h | 5 ++ .../browser/json_rpc_service_unittest.cc | 60 +++++++++++++++++++ .../brave_wallet/common/brave_wallet.mojom | 3 + 4 files changed, 108 insertions(+) diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index 6e326be046fa..ca0b732b42b9 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -676,6 +676,25 @@ void JsonRpcService::GetBlockNumber(GetBlockNumberCallback callback) { std::move(internal_callback)); } +void JsonRpcService::GetCode(const std::string& address, + mojom::CoinType coin, + const std::string& chain_id, + GetCodeCallback callback) { + auto network_url = GetNetworkURL(prefs_, chain_id, coin); + if (coin != mojom::CoinType::ETH || !network_url.is_valid()) { + std::move(callback).Run( + "", mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); + return; + } + + auto internal_callback = + base::BindOnce(&JsonRpcService::OnGetCode, weak_ptr_factory_.GetWeakPtr(), + std::move(callback)); + RequestInternal(eth::eth_getCode(address, "latest"), true, network_url, + std::move(internal_callback)); +} + void JsonRpcService::OnGetFilStateSearchMsgLimited( GetFilStateSearchMsgLimitedCallback callback, const std::string& cid, @@ -1095,6 +1114,27 @@ void JsonRpcService::OnGetERC20TokenBalance( std::move(callback).Run(args->at(0), mojom::ProviderError::kSuccess, ""); } +void JsonRpcService::OnGetCode(GetCodeCallback callback, + APIRequestResult api_request_result) { + if (!api_request_result.Is2XXResponseCode()) { + std::move(callback).Run( + "", mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + return; + } + + // Result is 0x when the address was an EOA + auto result = ParseSingleStringResult(api_request_result.value_body()); + if (!result) { + std::move(callback).Run( + "", mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + return; + } + + std::move(callback).Run(*result, mojom::ProviderError::kSuccess, ""); +} + void JsonRpcService::GetERC20TokenAllowance( const std::string& contract_address, const std::string& owner_address, diff --git a/components/brave_wallet/browser/json_rpc_service.h b/components/brave_wallet/browser/json_rpc_service.h index 0a9af1400442..5650cba4fed3 100644 --- a/components/brave_wallet/browser/json_rpc_service.h +++ b/components/brave_wallet/browser/json_rpc_service.h @@ -112,6 +112,10 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { mojom::CoinType coin, const std::string& chaind_id, GetBalanceCallback callback) override; + void GetCode(const std::string& address, + mojom::CoinType coin, + const std::string& chain_id, + GetCodeCallback callback) override; using GetFilBlockHeightCallback = base::OnceCallback result; + base::RunLoop run_loop; + json_rpc_service_->GetCode( + address, coin, chain_id, + base::BindLambdaForTesting([&](const std::string& bytecode, + mojom::ProviderError error, + const std::string& error_message) { + EXPECT_EQ(error, expected_error); + EXPECT_EQ(error_message, expected_error_message); + EXPECT_EQ(bytecode, expected_bytecode); + run_loop.Quit(); + })); + run_loop.Run(); + } + void TestGetERC1155TokenBalance(const std::string& contract, const std::string& token_id, const std::string& account_address, @@ -2302,6 +2323,45 @@ TEST_F(JsonRpcServiceUnitTest, Request_BadHeaderValues) { EXPECT_TRUE(callback_called); } +TEST_F(JsonRpcServiceUnitTest, GetCode) { + // Contract code response + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_getCode", "", + // Result has code that was intentionally truncated + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x6060604\"}"); + TestGetCode("0x0d8775f648430679a709e98d2b0cb6250d2887ef", + mojom::CoinType::ETH, mojom::kMainnetChainId, "0x6060604", + mojom::ProviderError::kSuccess, ""); + + // EOA response + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_getCode", "", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x\"}"); + TestGetCode("0x0d8775f648430679a709e98d2b0cb6250d2887ef", + mojom::CoinType::ETH, mojom::kMainnetChainId, "0x", + mojom::ProviderError::kSuccess, ""); + + // Processes error results OK + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_getCode", "", + MakeJsonRpcErrorResponse( + static_cast(mojom::ProviderError::kInternalError), + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + TestGetCode("0x0d8775f648430679a709e98d2b0cb6250d2887ef", + mojom::CoinType::ETH, mojom::kMainnetChainId, "", + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + + // Processes invalid chain IDs OK + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::SOL), + "eth_getCode", "", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x\"}"); + TestGetCode("0x0d8775f648430679a709e98d2b0cb6250d2887ef", + mojom::CoinType::SOL, mojom::kLocalhostChainId, "", + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); +} + TEST_F(JsonRpcServiceUnitTest, GetBalance) { bool callback_called = false; SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index ed70684a5345..3a18cb7ddd94 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -1030,6 +1030,9 @@ interface JsonRpcService { // Obtains the native balance (e.g. ETH for Ethereum) for the address GetBalance(string address, CoinType coin, string chain_id) => (string balance, ProviderError error, string error_message); + // Obtains the associated bytecode for the contract + GetCode(string address, CoinType coin, string chain_id) => (string bytecode, ProviderError error, string error_message); + // Obtains the contract's ERC20 compatible balance for an address // Supported by all EVM chains. GetERC20TokenBalance(string contract,