Skip to content

Commit

Permalink
Add eth_getCode support for getting address bytecode (#17215)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bbondy authored Feb 16, 2023
1 parent 4a256f7 commit cb5521e
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
40 changes: 40 additions & 0 deletions components/brave_wallet/browser/json_rpc_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions components/brave_wallet/browser/json_rpc_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(uint64_t height,
mojom::FilecoinProviderError error,
Expand Down Expand Up @@ -446,6 +450,7 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService {
APIRequestResult api_request_result);
void OnFilGetBalance(GetBalanceCallback callback,
APIRequestResult api_request_result);
void OnGetCode(GetCodeCallback callback, APIRequestResult api_request_result);
void OnEthGetTransactionCount(GetTxCountCallback callback,
APIRequestResult api_request_result);
void OnFilGetTransactionCount(GetFilTxCountCallback callback,
Expand Down
60 changes: 60 additions & 0 deletions components/brave_wallet/browser/json_rpc_service_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,27 @@ class JsonRpcServiceUnitTest : public testing::Test {
return result;
}

void TestGetCode(const std::string& address,
mojom::CoinType coin,
const std::string& chain_id,
const std::string& expected_bytecode,
mojom::ProviderError expected_error,
const std::string& expected_error_message) {
absl::optional<std::string> 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,
Expand Down Expand Up @@ -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<int>(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),
Expand Down
3 changes: 3 additions & 0 deletions components/brave_wallet/common/brave_wallet.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit cb5521e

Please sign in to comment.