From a8f0298517a2c3eca04627af79b954fdd5b395ed Mon Sep 17 00:00:00 2001 From: nvonpentz <12549658+nvonpentz@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:26:08 -0400 Subject: [PATCH 1/4] Add GetEthTokenSymbol to JsonRpcService --- .../brave_wallet/browser/json_rpc_service.cc | 42 +++++++++++ .../brave_wallet/browser/json_rpc_service.h | 7 ++ .../browser/json_rpc_service_unittest.cc | 69 +++++++++++++++++++ .../brave_wallet/common/brave_wallet.mojom | 6 ++ 4 files changed, 124 insertions(+) diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index b2b8804a1a4e..61982ed18f66 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -37,6 +37,7 @@ #include "brave/components/brave_wallet/common/eth_abi_utils.h" #include "brave/components/brave_wallet/common/eth_address.h" #include "brave/components/brave_wallet/common/features.h" +#include "brave/components/brave_wallet/common/hash_utils.h" #include "brave/components/brave_wallet/common/hex_utils.h" #include "brave/components/decentralized_dns/core/constants.h" #include "brave/components/decentralized_dns/core/utils.h" @@ -2652,6 +2653,47 @@ bool JsonRpcService::AddSwitchEthereumChainRequest(const std::string& chain_id, return true; } +void JsonRpcService::GetEthTokenSymbol(const std::string& contract_address, + const std::string& chain_id, + GetEthTokenSymbolCallback callback) { + auto network_url = GetNetworkURL(prefs_, chain_id, mojom::CoinType::ETH); + if (!network_url.is_valid()) { + std::move(callback).Run( + "", mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); + return; + } + std::string data; + const std::string function_hash = GetFunctionHash("symbol()"); + auto internal_callback = + base::BindOnce(&JsonRpcService::OnGetEthTokenSymbol, + weak_ptr_factory_.GetWeakPtr(), std::move(callback)); + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); +} + +void JsonRpcService::OnGetEthTokenSymbol(GetEthTokenSymbolCallback 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; + } + + std::string symbol; + if (!eth::ParseStringResult(api_request_result.value_body(), &symbol)) { + mojom::ProviderError error; + std::string error_message; + ParseErrorResult(api_request_result.value_body(), + &error, &error_message); + std::move(callback).Run("", error, error_message); + return; + } + std::move(callback).Run(symbol, mojom::ProviderError::kSuccess, ""); +} + void JsonRpcService::Reset() { ClearJsonRpcServiceProfilePrefs(prefs_); diff --git a/components/brave_wallet/browser/json_rpc_service.h b/components/brave_wallet/browser/json_rpc_service.h index 26c94e04d3d4..efebc19e9456 100644 --- a/components/brave_wallet/browser/json_rpc_service.h +++ b/components/brave_wallet/browser/json_rpc_service.h @@ -409,6 +409,10 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { mojom::ProviderError error, const std::string& error_message); + void GetEthTokenSymbol(const std::string& contract_address, + const std::string& chain_id, + GetEthTokenSymbolCallback callback) override; + using SwitchEthereumChainRequestCallback = base::OnceCallback; @@ -631,6 +635,9 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { void OnGetSupportsInterface(GetSupportsInterfaceCallback callback, APIRequestResult api_request_result); + void OnGetEthTokenSymbol(GetEthTokenSymbolCallback callback, + APIRequestResult api_request_result); + // Solana void OnGetSolanaBalance(GetSolanaBalanceCallback callback, APIRequestResult api_request_result); diff --git a/components/brave_wallet/browser/json_rpc_service_unittest.cc b/components/brave_wallet/browser/json_rpc_service_unittest.cc index cea2ce1d9052..17ba01d078b4 100644 --- a/components/brave_wallet/browser/json_rpc_service_unittest.cc +++ b/components/brave_wallet/browser/json_rpc_service_unittest.cc @@ -659,6 +659,17 @@ class JsonRpcEnpointHandler { std::vector sol_rpc_call_handlers_; }; +constexpr char kJsonRpcResponseTemplate[] = R"({ + "jsonrpc":"2.0", + "id":1, + "result":"$1" + })"; + +std::string formatJsonRpcResponse(const std::string& value) { + return base::ReplaceStringPlaceholders(kJsonRpcResponseTemplate, {value}, + nullptr); +} + } // namespace class JsonRpcServiceUnitTest : public testing::Test { @@ -1270,6 +1281,25 @@ class JsonRpcServiceUnitTest : public testing::Test { run_loop.Run(); } + void TestGetEthTokenSymbol(const std::string& contract_address, + const std::string& chain_id, + const std::string& expected_symbol, + mojom::ProviderError expected_error, + const std::string& expected_error_message) { + base::RunLoop run_loop; + json_rpc_service_->GetEthTokenSymbol( + contract_address, chain_id, + base::BindLambdaForTesting([&](const std::string& symbol, + mojom::ProviderError error, + const std::string& error_message) { + EXPECT_EQ(symbol, expected_symbol); + EXPECT_EQ(error, expected_error); + EXPECT_EQ(error_message, expected_error_message); + run_loop.Quit(); + })); + run_loop.Run(); + } + void TestGetSolanaBalance(uint64_t expected_balance, mojom::SolanaProviderError expected_error, const std::string& expected_error_message) { @@ -6584,4 +6614,43 @@ TEST_F(JsonRpcServiceUnitTest, GetEthNftStandard) { mojom::ProviderError::kSuccess, ""); } +TEST_F(JsonRpcServiceUnitTest, GetEthTokenSymbol) { + // Invalid chain ID yields invalid params + TestGetEthTokenSymbol( + "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", "", "", + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); + + // Valid inputs but request times out yields internal error + SetHTTPRequestTimeoutInterceptor(); + TestGetEthTokenSymbol("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "", + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + + // Valid + const std::string bat_symbol_result = + "0x" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000003" + "4241540000000000000000000000000000000000000000000000000000000000"; + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_call", "", formatJsonRpcResponse(bat_symbol_result)); + TestGetEthTokenSymbol("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "BAT", + mojom::ProviderError::kSuccess, ""); + + // Response parsing error yields parsing error + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_call", "", R"({ + "jsonrpc": "2.0", + "id": 1, + "result": "0xinvalid" + })"); + TestGetEthTokenSymbol("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "", + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); +} + } // namespace brave_wallet diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index dbcc21449b76..1c6ed41f3ace 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -1176,6 +1176,12 @@ interface JsonRpcService { // Obtains the quantity of ERC1155 tokens a user has GetERC1155TokenBalance(string contract_address, string token_id, string account_address, string chain_id) => (string balance, ProviderError error, string error_message); + // Fetches symbol of an ERC20 or ERC721 token. + GetEthTokenSymbol(string chain_id, + string contract_address) => (string symbol, + ProviderError error, + string error_message); + // Solana JSON RPCs // https://docs.solana.com/developing/clients/jsonrpc-api From 679442b6237aba70a35b2a79988918a43719026b Mon Sep 17 00:00:00 2001 From: nvonpentz <12549658+nvonpentz@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:00:16 -0400 Subject: [PATCH 2/4] Fix: supply the correct data in the eth_call --- components/brave_wallet/browser/json_rpc_service.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index 61982ed18f66..f7173c3b1916 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -2663,8 +2663,7 @@ void JsonRpcService::GetEthTokenSymbol(const std::string& contract_address, l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); return; } - std::string data; - const std::string function_hash = GetFunctionHash("symbol()"); + const std::string data = GetFunctionHash("symbol()"); auto internal_callback = base::BindOnce(&JsonRpcService::OnGetEthTokenSymbol, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); From 8c682c7cc3627f2f9495f2a3f87e98a474b2a6ed Mon Sep 17 00:00:00 2001 From: nvonpentz <12549658+nvonpentz@users.noreply.github.com> Date: Thu, 27 Apr 2023 10:48:24 -0400 Subject: [PATCH 3/4] Add GetEthTokenDecimals to JsonRpcService --- .../brave_wallet/browser/json_rpc_service.cc | 49 ++++++++++++++++ .../brave_wallet/browser/json_rpc_service.h | 6 ++ .../browser/json_rpc_service_unittest.cc | 56 +++++++++++++++++++ .../brave_wallet/common/brave_wallet.mojom | 5 ++ 4 files changed, 116 insertions(+) diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index f7173c3b1916..67a0e4675981 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -2693,6 +2693,55 @@ void JsonRpcService::OnGetEthTokenSymbol(GetEthTokenSymbolCallback callback, std::move(callback).Run(symbol, mojom::ProviderError::kSuccess, ""); } +void JsonRpcService::GetEthTokenDecimals(const std::string& contract_address, + const std::string& chain_id, + GetEthTokenDecimalsCallback callback) { + auto network_url = GetNetworkURL(prefs_, chain_id, mojom::CoinType::ETH); + if (!network_url.is_valid()) { + std::move(callback).Run( + "", mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); + return; + } + const std::string data = GetFunctionHash("decimals()"); + auto internal_callback = + base::BindOnce(&JsonRpcService::OnGetEthTokenDecimals, + weak_ptr_factory_.GetWeakPtr(), std::move(callback)); + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); +} + +void JsonRpcService::OnGetEthTokenDecimals( + GetEthTokenDecimalsCallback 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; + } + + auto result = eth::ParseEthCall(api_request_result.value_body()); + if (!result) { + mojom::ProviderError error; + std::string error_message; + ParseErrorResult(api_request_result.value_body(), + &error, &error_message); + std::move(callback).Run("", error, error_message); + return; + } + + const auto& args = eth::DecodeEthCallResponse(*result, {"uint8"}); + if (args == absl::nullopt) { + std::move(callback).Run("", mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); + return; + } + + std::move(callback).Run(args->at(0), mojom::ProviderError::kSuccess, ""); +} + void JsonRpcService::Reset() { ClearJsonRpcServiceProfilePrefs(prefs_); diff --git a/components/brave_wallet/browser/json_rpc_service.h b/components/brave_wallet/browser/json_rpc_service.h index efebc19e9456..cb8b26b08ded 100644 --- a/components/brave_wallet/browser/json_rpc_service.h +++ b/components/brave_wallet/browser/json_rpc_service.h @@ -413,6 +413,10 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { const std::string& chain_id, GetEthTokenSymbolCallback callback) override; + void GetEthTokenDecimals(const std::string& contract_address, + const std::string& chain_id, + GetEthTokenDecimalsCallback callback) override; + using SwitchEthereumChainRequestCallback = base::OnceCallback; @@ -638,6 +642,8 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { void OnGetEthTokenSymbol(GetEthTokenSymbolCallback callback, APIRequestResult api_request_result); + void OnGetEthTokenDecimals(GetEthTokenDecimalsCallback callback, + APIRequestResult api_request_result); // Solana void OnGetSolanaBalance(GetSolanaBalanceCallback callback, APIRequestResult api_request_result); diff --git a/components/brave_wallet/browser/json_rpc_service_unittest.cc b/components/brave_wallet/browser/json_rpc_service_unittest.cc index 17ba01d078b4..3b9f1f6e745a 100644 --- a/components/brave_wallet/browser/json_rpc_service_unittest.cc +++ b/components/brave_wallet/browser/json_rpc_service_unittest.cc @@ -1300,6 +1300,25 @@ class JsonRpcServiceUnitTest : public testing::Test { run_loop.Run(); } + void TestGetEthTokenDecimals(const std::string& contract_address, + const std::string& chain_id, + const std::string& expected_decimals, + mojom::ProviderError expected_error, + const std::string& expected_error_message) { + base::RunLoop run_loop; + json_rpc_service_->GetEthTokenDecimals( + contract_address, chain_id, + base::BindLambdaForTesting([&](const std::string& decimals, + mojom::ProviderError error, + const std::string& error_message) { + EXPECT_EQ(decimals, expected_decimals); + EXPECT_EQ(error, expected_error); + EXPECT_EQ(error_message, expected_error_message); + run_loop.Quit(); + })); + run_loop.Run(); + } + void TestGetSolanaBalance(uint64_t expected_balance, mojom::SolanaProviderError expected_error, const std::string& expected_error_message) { @@ -6653,4 +6672,41 @@ TEST_F(JsonRpcServiceUnitTest, GetEthTokenSymbol) { l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); } +TEST_F(JsonRpcServiceUnitTest, GetEthTokenDecimals) { + // Invalid chain ID yields invalid params + TestGetEthTokenDecimals( + "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", "", "", + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); + + // Valid inputs but request times out yields internal error + SetHTTPRequestTimeoutInterceptor(); + TestGetEthTokenDecimals("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "", + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + + // Valid + const std::string bat_decimals_result = + "0x" + "0000000000000000000000000000000000000000000000000000000000000012"; + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_call", "", formatJsonRpcResponse(bat_decimals_result)); + TestGetEthTokenDecimals("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "0x12", + mojom::ProviderError::kSuccess, ""); + + // Response parsing error yields parsing error + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_call", "", R"({ + "jsonrpc": "2.0", + "id": 1, + "result": "0xinvalid" + })"); + TestGetEthTokenDecimals("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + mojom::kMainnetChainId, "", + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); +} + } // namespace brave_wallet diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index 1c6ed41f3ace..fe8b0c9010c6 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -1182,6 +1182,11 @@ interface JsonRpcService { ProviderError error, string error_message); + // Fetches decimals of an ERC20 or ERC721 token. + GetEthTokenDecimals(string chain_id, + string contract_address) => (string symbol, + ProviderError error, + string error_message); // Solana JSON RPCs // https://docs.solana.com/developing/clients/jsonrpc-api From 0106405475d262a576fb506e8268ce0ac5127a23 Mon Sep 17 00:00:00 2001 From: nvonpentz <12549658+nvonpentz@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:14:24 -0400 Subject: [PATCH 4/4] Address feedback * Rename symbol -> decimals * Rename formatJsonRpcResponse -> FormatJsonRpcResponse * Use short form of eth::eth_call --- components/brave_wallet/browser/json_rpc_service.cc | 10 ++++------ .../brave_wallet/browser/json_rpc_service_unittest.cc | 6 +++--- components/brave_wallet/common/brave_wallet.mojom | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index 67a0e4675981..f50c88395043 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -2667,9 +2667,8 @@ void JsonRpcService::GetEthTokenSymbol(const std::string& contract_address, auto internal_callback = base::BindOnce(&JsonRpcService::OnGetEthTokenSymbol, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_call("", contract_address, "", "", "", data, - kEthereumBlockTagLatest), - true, network_url, std::move(internal_callback)); + RequestInternal(eth::eth_call(contract_address, data), true, network_url, + std::move(internal_callback)); } void JsonRpcService::OnGetEthTokenSymbol(GetEthTokenSymbolCallback callback, @@ -2707,9 +2706,8 @@ void JsonRpcService::GetEthTokenDecimals(const std::string& contract_address, auto internal_callback = base::BindOnce(&JsonRpcService::OnGetEthTokenDecimals, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_call("", contract_address, "", "", "", data, - kEthereumBlockTagLatest), - true, network_url, std::move(internal_callback)); + RequestInternal(eth::eth_call(contract_address, data), true, network_url, + std::move(internal_callback)); } void JsonRpcService::OnGetEthTokenDecimals( diff --git a/components/brave_wallet/browser/json_rpc_service_unittest.cc b/components/brave_wallet/browser/json_rpc_service_unittest.cc index 3b9f1f6e745a..9a0c5ffcf88b 100644 --- a/components/brave_wallet/browser/json_rpc_service_unittest.cc +++ b/components/brave_wallet/browser/json_rpc_service_unittest.cc @@ -665,7 +665,7 @@ constexpr char kJsonRpcResponseTemplate[] = R"({ "result":"$1" })"; -std::string formatJsonRpcResponse(const std::string& value) { +std::string FormatJsonRpcResponse(const std::string& value) { return base::ReplaceStringPlaceholders(kJsonRpcResponseTemplate, {value}, nullptr); } @@ -6654,7 +6654,7 @@ TEST_F(JsonRpcServiceUnitTest, GetEthTokenSymbol) { "0000000000000000000000000000000000000000000000000000000000000003" "4241540000000000000000000000000000000000000000000000000000000000"; SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), - "eth_call", "", formatJsonRpcResponse(bat_symbol_result)); + "eth_call", "", FormatJsonRpcResponse(bat_symbol_result)); TestGetEthTokenSymbol("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", mojom::kMainnetChainId, "BAT", mojom::ProviderError::kSuccess, ""); @@ -6691,7 +6691,7 @@ TEST_F(JsonRpcServiceUnitTest, GetEthTokenDecimals) { "0x" "0000000000000000000000000000000000000000000000000000000000000012"; SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), - "eth_call", "", formatJsonRpcResponse(bat_decimals_result)); + "eth_call", "", FormatJsonRpcResponse(bat_decimals_result)); TestGetEthTokenDecimals("0x0D8775F648430679A709E98d2b0Cb6250d2887EF", mojom::kMainnetChainId, "0x12", mojom::ProviderError::kSuccess, ""); diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index fe8b0c9010c6..321e9f72b39b 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -1184,7 +1184,7 @@ interface JsonRpcService { // Fetches decimals of an ERC20 or ERC721 token. GetEthTokenDecimals(string chain_id, - string contract_address) => (string symbol, + string contract_address) => (string decimals, ProviderError error, string error_message); // Solana JSON RPCs