[Polkadot] Parse assets pallet chain metadata. (#36419)

* [Polkadot] Parse assets pallet chain metadata.
Check whether assets pallet exists and parse transfer calls.
Resolves https://github.com/brave/brave-browser/issues/55503
This commit is contained in:
cypt4
2026-06-03 15:37:02 +07:00
committed by GitHub
parent f9ea04c1a5
commit aebe762e97
12 changed files with 365 additions and 94 deletions
+1
View File
@@ -771,6 +771,7 @@ source_set("unit_tests") {
"polkadot/polkadot_keyring_unittest.cc",
"polkadot/polkadot_metadata_provider_unittest.cc",
"polkadot/polkadot_substrate_rpc_unittest.cc",
"polkadot/polkadot_test_utils_unittest.cc",
"polkadot/polkadot_transaction_status_task_unittest.cc",
"polkadot/polkadot_transaction_unittest.cc",
"polkadot/polkadot_tx_manager_unittest.cc",
@@ -196,10 +196,9 @@ fn parse_pallet(input: &mut &[u8], has_pallet_docs: bool) -> Result<PalletInfo,
let name: String = decode_scale(input)?;
// storage: Option<PalletStorageMetadata>
let _storage = decode_option(input, |input| {
decode_option(input, |input| {
let _: String = decode_scale(input)?; // prefix
let _ = decode_vec(input, parse_storage_entry)?;
Ok(())
decode_vec(input, parse_storage_entry)
})?;
// calls: Option<PalletCallMetadata { ty: u32 }>
@@ -356,6 +355,8 @@ fn parse_extrinsic_metadata(input: &mut &[u8], version: u8) -> Result<bool, Erro
/// - `transfer_allow_death` call index
/// - `System.SS58Prefix`
/// - `System.Version.spec_version`
/// - `Assets` pallet index and transfer call indexes when the pallet is
/// present
/// - whether `ChargeAssetTxPayment` is a signed extension
///
/// References:
@@ -396,6 +397,19 @@ fn parse_chain_metadata_fields(bytes: &[u8]) -> Result<CxxPolkadotChainMetadata,
let transfer_all_call_index =
get_call_index(&portable_registry, balances_pallet, "transferall")?;
let mut has_assets_pallet = false;
let mut assets_pallet_index = 0;
let mut assets_transfer_all_call_index = 0;
let mut assets_transfer_keep_alive_call_index = 0;
if let Some(assets_pallet) = pallets.iter().find(|p| normalize_ident(&p.name) == "assets") {
has_assets_pallet = true;
assets_pallet_index = assets_pallet.index;
assets_transfer_all_call_index =
get_call_index(&portable_registry, assets_pallet, "transferall")?;
assets_transfer_keep_alive_call_index =
get_call_index(&portable_registry, assets_pallet, "transferkeepalive")?;
}
let system_pallet = pallets
.iter()
.find(|p| normalize_ident(&p.name) == "system")
@@ -430,6 +444,10 @@ fn parse_chain_metadata_fields(bytes: &[u8]) -> Result<CxxPolkadotChainMetadata,
transfer_allow_death_call_index,
transfer_keep_alive_call_index,
transfer_all_call_index,
assets_pallet_index,
assets_transfer_all_call_index,
assets_transfer_keep_alive_call_index,
has_assets_pallet,
ss58_prefix,
spec_version,
asset_tx_payment,
@@ -55,6 +55,10 @@ mod ffi {
pub transfer_allow_death_call_index: u8,
pub transfer_keep_alive_call_index: u8,
pub transfer_all_call_index: u8,
pub assets_pallet_index: u8,
pub assets_transfer_all_call_index: u8,
pub assets_transfer_keep_alive_call_index: u8,
pub has_assets_pallet: bool,
pub ss58_prefix: u16,
pub spec_version: u32,
pub asset_tx_payment: bool,
@@ -62,6 +66,7 @@ mod ffi {
extern "Rust" {
fn compact_scale_encode_u32(x: u32) -> Vec<u8>;
fn scale_encode_string(value: &[u8]) -> Vec<u8>;
type CxxPolkadotDecodeUnsignedTransferResult;
type CxxPolkadotChainMetadataResult;
@@ -521,6 +526,10 @@ fn compact_scale_encode_u32(x: u32) -> Vec<u8> {
Compact(x).encode()
}
fn scale_encode_string(value: &[u8]) -> Vec<u8> {
value.encode()
}
fn was_extrinsic_successful(
events: &[u8],
extrinsic_idx: u32,
@@ -48,15 +48,23 @@ PolkadotChainMetadata PolkadotChainMetadata::FromFields(
uint8_t transfer_all_call_index,
uint16_t ss58_prefix,
uint32_t spec_version,
bool asset_tx_payment) {
bool asset_tx_payment,
bool has_assets_pallet,
uint8_t assets_pallet_index,
uint8_t assets_transfer_all_call_index,
uint8_t assets_transfer_keep_alive_call_index) {
PolkadotChainMetadata metadata;
metadata->system_pallet_index = system_pallet_index;
metadata->balances_pallet_index = balances_pallet_index;
metadata->transaction_payment_pallet_index = transaction_payment_pallet_index;
metadata->transfer_allow_death_call_index = transfer_allow_death_call_index;
metadata->transfer_keep_alive_call_index = transfer_keep_alive_call_index;
metadata->transfer_all_call_index = transfer_all_call_index;
metadata->assets_pallet_index = assets_pallet_index;
metadata->assets_transfer_all_call_index = assets_transfer_all_call_index;
metadata->assets_transfer_keep_alive_call_index =
assets_transfer_keep_alive_call_index;
metadata->has_assets_pallet = has_assets_pallet;
metadata->ss58_prefix = ss58_prefix;
metadata->spec_version = spec_version;
metadata->asset_tx_payment = asset_tx_payment;
@@ -47,7 +47,11 @@ class PolkadotChainMetadata {
uint8_t transfer_all_call_index,
uint16_t ss58_prefix,
uint32_t spec_version,
bool asset_tx_payment);
bool asset_tx_payment,
bool has_assets_pallet,
uint8_t assets_pallet_index,
uint8_t assets_transfer_all_call_index,
uint8_t assets_transfer_keep_alive_call_index);
// Obtain a reference to the underlying opaque type so that it can be passed
// to Rust routines.
@@ -24,6 +24,11 @@ constexpr char kTransferAllowDeathCallIndex[] =
"transfer_allow_death_call_index";
constexpr char kTransferKeepAliveCallIndex[] = "transfer_keep_alive_call_index";
constexpr char kTransferAllCallIndex[] = "transfer_all_call_index";
constexpr char kHasAssetsPallet[] = "has_assets_pallet";
constexpr char kAssetsPalletIndex[] = "assets_pallet_index";
constexpr char kAssetsTransferAllCallIndex[] = "assets_transfer_all_call_index";
constexpr char kAssetsTransferKeepAliveCallIndex[] =
"assets_transfer_keep_alive_call_index";
constexpr char kAssetTxPayment[] = "asset_tx_payment";
constexpr char kSs58Prefix[] = "ss58_prefix";
constexpr char kSpecVersion[] = "spec_version";
@@ -39,6 +44,10 @@ constexpr char kVersionField[] = "version";
// "transfer_allow_death_call_index": int, // u8
// "transfer_keep_alive_call_index": int, // u8
// "transfer_all_call_index": int, // u8
// "has_assets_pallet": bool, // bool
// "assets_pallet_index": int, // u8
// "assets_transfer_all_call_index": int, // u8
// "assets_transfer_keep_alive_call_index": int, // u8
// "asset_tx_payment": bool, // bool
// "ss58_prefix": int, // u16
// "spec_version": int // u32
@@ -128,6 +137,26 @@ PolkadotChainMetadataPrefs::GetChainMetadata(std::string_view chain_id) const {
return std::nullopt;
}
if (!ReadBool(*chain_metadata, kHasAssetsPallet,
&metadata->has_assets_pallet)) {
return std::nullopt;
}
if (!ReadUint(*chain_metadata, kAssetsPalletIndex,
&metadata->assets_pallet_index)) {
return std::nullopt;
}
if (!ReadUint(*chain_metadata, kAssetsTransferAllCallIndex,
&metadata->assets_transfer_all_call_index)) {
return std::nullopt;
}
if (!ReadUint(*chain_metadata, kAssetsTransferKeepAliveCallIndex,
&metadata->assets_transfer_keep_alive_call_index)) {
return std::nullopt;
}
if (!ReadBool(*chain_metadata, kAssetTxPayment,
&metadata->asset_tx_payment)) {
return std::nullopt;
@@ -154,6 +183,9 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata(
int transfer_allow_death_call_index = 0;
int transfer_keep_alive_call_index = 0;
int transfer_all_call_index = 0;
int assets_pallet_index = 0;
int assets_transfer_all_call_index = 0;
int assets_transfer_keep_alive_call_index = 0;
int ss58_prefix = 0;
int spec_version = 0;
if (!base::CheckedNumeric<int>(metadata->system_pallet_index)
@@ -168,6 +200,13 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata(
.AssignIfValid(&transfer_keep_alive_call_index) ||
!base::CheckedNumeric<int>(metadata->transfer_all_call_index)
.AssignIfValid(&transfer_all_call_index) ||
!base::CheckedNumeric<int>(metadata->assets_pallet_index)
.AssignIfValid(&assets_pallet_index) ||
!base::CheckedNumeric<int>(metadata->assets_transfer_all_call_index)
.AssignIfValid(&assets_transfer_all_call_index) ||
!base::CheckedNumeric<int>(
metadata->assets_transfer_keep_alive_call_index)
.AssignIfValid(&assets_transfer_keep_alive_call_index) ||
!base::CheckedNumeric<int>(metadata->ss58_prefix)
.AssignIfValid(&ss58_prefix) ||
!base::CheckedNumeric<int>(metadata->spec_version)
@@ -181,6 +220,11 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata(
value.Set(kTransferAllowDeathCallIndex, transfer_allow_death_call_index);
value.Set(kTransferKeepAliveCallIndex, transfer_keep_alive_call_index);
value.Set(kTransferAllCallIndex, transfer_all_call_index);
value.Set(kHasAssetsPallet, metadata->has_assets_pallet);
value.Set(kAssetsPalletIndex, assets_pallet_index);
value.Set(kAssetsTransferAllCallIndex, assets_transfer_all_call_index);
value.Set(kAssetsTransferKeepAliveCallIndex,
assets_transfer_keep_alive_call_index);
value.Set(kAssetTxPayment, metadata->asset_tx_payment);
value.Set(kSs58Prefix, ss58_prefix);
value.Set(kSpecVersion, spec_version);
@@ -18,7 +18,7 @@ namespace brave_wallet {
class PolkadotChainMetadataPrefs {
public:
static constexpr int kVersion = 1;
static constexpr int kVersion = 2;
explicit PolkadotChainMetadataPrefs(PrefService& profile_prefs);
virtual ~PolkadotChainMetadataPrefs();
@@ -24,6 +24,11 @@ constexpr char kTransferAllowDeathCallIndex[] =
"transfer_allow_death_call_index";
constexpr char kTransferKeepAliveCallIndex[] = "transfer_keep_alive_call_index";
constexpr char kTransferAllCallIndex[] = "transfer_all_call_index";
constexpr char kHasAssetsPallet[] = "has_assets_pallet";
constexpr char kAssetsPalletIndex[] = "assets_pallet_index";
constexpr char kAssetsTransferAllCallIndex[] = "assets_transfer_all_call_index";
constexpr char kAssetsTransferKeepAliveCallIndex[] =
"assets_transfer_keep_alive_call_index";
constexpr char kAssetTxPayment[] = "asset_tx_payment";
constexpr char kSs58Prefix[] = "ss58_prefix";
constexpr char kVersionField[] = "version";
@@ -48,6 +53,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) {
uint8_t transfer_allow_death_call_index;
uint8_t transfer_keep_alive_call_index;
uint8_t transfer_all_call_index;
bool has_assets_pallet;
uint8_t assets_pallet_index;
uint8_t assets_transfer_all_call_index;
uint8_t assets_transfer_keep_alive_call_index;
uint16_t ss58_prefix;
uint32_t spec_version;
bool asset_tx_payment;
@@ -60,6 +69,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) {
/*transfer_allow_death_call_index=*/2,
/*transfer_keep_alive_call_index=*/4,
/*transfer_all_call_index=*/5,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0,
/*ss58_prefix=*/42,
/*spec_version=*/1234, /*asset_tx_payment=*/false},
{"AssetHub Polkadot Metadata",
@@ -68,6 +81,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) {
/*transfer_allow_death_call_index=*/2,
/*transfer_keep_alive_call_index=*/4,
/*transfer_all_call_index=*/255,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/10,
/*assets_transfer_keep_alive_call_index=*/9,
/*ss58_prefix=*/std::numeric_limits<uint16_t>::max(),
/*spec_version=*/12344321, /*asset_tx_payment=*/true}};
@@ -78,7 +95,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) {
tc.system_pallet_index, tc.balances_pallet_index,
tc.transaction_payment_pallet_index, tc.transfer_allow_death_call_index,
tc.transfer_keep_alive_call_index, tc.transfer_all_call_index,
tc.ss58_prefix, tc.spec_version, tc.asset_tx_payment);
tc.ss58_prefix, tc.spec_version, tc.asset_tx_payment,
tc.has_assets_pallet, tc.assets_pallet_index,
tc.assets_transfer_all_call_index,
tc.assets_transfer_keep_alive_call_index);
PolkadotChainMetadataPrefs prefs = MakePrefs();
EXPECT_TRUE(prefs.SetChainMetadata(mojom::kPolkadotMainnet, metadata));
@@ -111,6 +131,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, InvalidRangeRejected) {
value.Set(kTransferAllowDeathCallIndex, 1);
value.Set(kTransferKeepAliveCallIndex, 1);
value.Set(kTransferAllCallIndex, 1);
value.Set(kHasAssetsPallet, false);
value.Set(kAssetsPalletIndex, 0);
value.Set(kAssetsTransferAllCallIndex, 0);
value.Set(kAssetsTransferKeepAliveCallIndex, 0);
value.Set(kAssetTxPayment, false);
value.Set(kSs58Prefix, 0);
value.Set(kSpecVersion, 100);
@@ -133,6 +157,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, NegativeValueRejected) {
-1); // Negative should be rejected.
value.Set(kTransferKeepAliveCallIndex, 1);
value.Set(kTransferAllCallIndex, 1);
value.Set(kHasAssetsPallet, false);
value.Set(kAssetsPalletIndex, 0);
value.Set(kAssetsTransferAllCallIndex, 0);
value.Set(kAssetsTransferKeepAliveCallIndex, 0);
value.Set(kAssetTxPayment, false);
value.Set(kSs58Prefix, 0);
value.Set(kSpecVersion, 100);
@@ -154,6 +182,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, MissingRequiredFieldRejected) {
value.Set(kTransferAllowDeathCallIndex, 1);
value.Set(kTransferKeepAliveCallIndex, 4);
value.Set(kTransferAllCallIndex, 1);
value.Set(kHasAssetsPallet, false);
value.Set(kAssetsPalletIndex, 0);
value.Set(kAssetsTransferAllCallIndex, 0);
value.Set(kAssetsTransferKeepAliveCallIndex, 0);
value.Set(kAssetTxPayment, false);
value.Set(kSs58Prefix, 42);
// Missing spec version should reject persisted value.
@@ -175,6 +207,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, MissingRequiredFieldRejected) {
value.Set(kTransferAllowDeathCallIndex, 1);
value.Set(kTransferKeepAliveCallIndex, 4);
value.Set(kTransferAllCallIndex, 1);
value.Set(kHasAssetsPallet, false);
value.Set(kAssetsPalletIndex, 0);
value.Set(kAssetsTransferAllCallIndex, 0);
value.Set(kAssetsTransferKeepAliveCallIndex, 0);
value.Set(kSs58Prefix, 42);
value.Set(kSpecVersion, 1234);
update->Set(mojom::kPolkadotMainnet, std::move(value));
@@ -5,11 +5,11 @@
#include "brave/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h"
#include <string>
#include <string_view>
#include <vector>
#include "base/base_paths.h"
#include "base/path_service.h"
#include "base/test/values_test_util.h"
#include "base/containers/span.h"
#include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h"
#include "brave/components/brave_wallet/common/hex_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,7 +18,11 @@ namespace brave_wallet {
namespace {
constexpr char kResult[] = "result";
std::string ScaleEncodeString(std::string_view value) {
auto bytes = base::as_byte_span(value);
auto encoded = scale_encode_string(::rust::Slice(bytes));
return std::string(encoded.begin(), encoded.end());
}
} // namespace
@@ -30,7 +34,11 @@ TEST(PolkadotChainMetadataUnitTest, FromFields) {
/*transfer_keep_alive_call_index=*/4,
/*transfer_all_call_index=*/5,
/*ss58_prefix=*/42, /*spec_version=*/1'234'567,
/*asset_tx_payment=*/true);
/*asset_tx_payment=*/true,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/10,
/*assets_transfer_keep_alive_call_index=*/9);
EXPECT_EQ(metadata->system_pallet_index, 0u);
EXPECT_EQ(metadata->balances_pallet_index, 7u);
@@ -38,6 +46,10 @@ TEST(PolkadotChainMetadataUnitTest, FromFields) {
EXPECT_EQ(metadata->transfer_allow_death_call_index, 2u);
EXPECT_EQ(metadata->transfer_keep_alive_call_index, 4u);
EXPECT_EQ(metadata->transfer_all_call_index, 5u);
EXPECT_TRUE(metadata->has_assets_pallet);
EXPECT_EQ(metadata->assets_pallet_index, 50u);
EXPECT_EQ(metadata->assets_transfer_all_call_index, 10u);
EXPECT_EQ(metadata->assets_transfer_keep_alive_call_index, 9u);
EXPECT_EQ(metadata->ss58_prefix, 42u);
EXPECT_EQ(metadata->spec_version, 1'234'567u);
EXPECT_TRUE(metadata->asset_tx_payment);
@@ -51,7 +63,11 @@ TEST(PolkadotChainMetadataUnitTest, EqualityOperator) {
/*transfer_keep_alive_call_index=*/4,
/*transfer_all_call_index=*/5,
/*ss58_prefix=*/42, /*spec_version=*/1'234'567,
/*asset_tx_payment=*/false);
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0);
PolkadotChainMetadata metadata_b = metadata_a;
@@ -61,9 +77,23 @@ TEST(PolkadotChainMetadataUnitTest, EqualityOperator) {
PolkadotChainMetadata metadata_d = metadata_a;
metadata_d->asset_tx_payment = true;
auto metadata_e = PolkadotChainMetadata::FromFields(
/*system_pallet_index=*/0, /*balances_pallet_index=*/7,
/*transaction_payment_pallet_index=*/0x20,
/*transfer_allow_death_call_index=*/2,
/*transfer_keep_alive_call_index=*/4,
/*transfer_all_call_index=*/5,
/*ss58_prefix=*/42, /*spec_version=*/1'234'567,
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/10,
/*assets_transfer_keep_alive_call_index=*/9);
EXPECT_EQ(metadata_a, metadata_b);
EXPECT_NE(metadata_a, metadata_c);
EXPECT_NE(metadata_a, metadata_d);
EXPECT_NE(metadata_a, metadata_e);
}
TEST(PolkadotChainMetadataUnitTest, FromBytesInvalid) {
@@ -77,29 +107,46 @@ TEST(PolkadotChainMetadataUnitTest, FromBytesInvalid) {
EXPECT_FALSE(PolkadotChainMetadata::FromBytes(invalid_short));
}
TEST(PolkadotChainMetadataUnitTest,
ParseMetadataWithoutAssetsPalletLeavesAssetsFieldsUnset) {
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_polkadot.json");
ASSERT_FALSE(metadata_bytes.empty());
auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes);
ASSERT_TRUE(metadata);
EXPECT_FALSE((*metadata)->has_assets_pallet);
EXPECT_EQ((*metadata)->assets_pallet_index, 0u);
EXPECT_EQ((*metadata)->assets_transfer_all_call_index, 0u);
EXPECT_EQ((*metadata)->assets_transfer_keep_alive_call_index, 0u);
}
TEST(PolkadotChainMetadataUnitTest,
ParseMetadataWithAssetsPalletMissingMethodFails) {
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_assethub_polkadot.json");
ASSERT_FALSE(metadata_bytes.empty());
// The second transfer_keep_alive occurrence is the Assets pallet call
// variant; the first belongs to Balances.
const std::string transfer_keep_alive =
ScaleEncodeString("transfer_keep_alive");
const std::string transfer_dead_alive =
ScaleEncodeString("transfer_dead_alive");
ASSERT_TRUE(ReplaceNthOccurrence(metadata_bytes, transfer_keep_alive,
transfer_dead_alive, /*occurrence=*/1));
EXPECT_FALSE(PolkadotChainMetadata::FromBytes(metadata_bytes));
}
TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponsePolkadot) {
// Refreshed with:
// curl -sS -H 'Content-Type: application/json' \
// -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \
// https://rpc.polkadot.io/
const auto fixture_path =
base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT)
.AppendASCII("brave")
.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("brave_wallet")
.AppendASCII("polkadot")
.AppendASCII("chain_metadata")
.AppendASCII("state_getMetadata_polkadot.json");
const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path);
const std::string* metadata_hex = json.FindString(kResult);
ASSERT_TRUE(metadata_hex);
ASSERT_FALSE(metadata_hex->empty());
std::vector<uint8_t> metadata_bytes;
ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes));
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_polkadot.json");
auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes);
ASSERT_TRUE(metadata);
@@ -111,7 +158,11 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponsePolkadot) {
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/0, /*spec_version=*/2'000'007,
/*asset_tx_payment=*/false);
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0);
EXPECT_EQ(*metadata, expected);
auto metadata2 = PolkadotMetadataFromChainName("Polkadot");
@@ -127,24 +178,8 @@ TEST(PolkadotChainMetadataUnitTest,
// curl -sS -H 'Content-Type: application/json' \
// -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \
// https://polkadot-asset-hub-rpc.polkadot.io
const auto fixture_path =
base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT)
.AppendASCII("brave")
.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("brave_wallet")
.AppendASCII("polkadot")
.AppendASCII("chain_metadata")
.AppendASCII("state_getMetadata_assethub_polkadot.json");
const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path);
const std::string* metadata_hex = json.FindString(kResult);
ASSERT_TRUE(metadata_hex);
ASSERT_FALSE(metadata_hex->empty());
std::vector<uint8_t> metadata_bytes;
ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes));
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_assethub_polkadot.json");
auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes);
ASSERT_TRUE(metadata);
@@ -156,7 +191,12 @@ TEST(PolkadotChainMetadataUnitTest,
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/0, /*spec_version=*/2'002'001,
/*asset_tx_payment=*/true);
/*asset_tx_payment=*/true,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/32,
/*assets_transfer_keep_alive_call_index=*/9);
EXPECT_EQ(*metadata, expected);
auto metadata2 = PolkadotMetadataFromChainName("Polkadot Asset Hub");
ASSERT_TRUE(metadata2);
@@ -170,24 +210,8 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponseWestend) {
// curl -sS -H 'Content-Type: application/json' \
// -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \
// https://westend-rpc.polkadot.io/
const auto fixture_path =
base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT)
.AppendASCII("brave")
.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("brave_wallet")
.AppendASCII("polkadot")
.AppendASCII("chain_metadata")
.AppendASCII("state_getMetadata_westend.json");
const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path);
const std::string* metadata_hex = json.FindString(kResult);
ASSERT_TRUE(metadata_hex);
ASSERT_FALSE(metadata_hex->empty());
std::vector<uint8_t> metadata_bytes;
ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes));
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_westend.json");
auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes);
ASSERT_TRUE(metadata);
@@ -199,7 +223,11 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponseWestend) {
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/42, /*spec_version=*/1'022'000,
/*asset_tx_payment=*/false);
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0);
EXPECT_EQ(*metadata, expected);
auto metadata2 = PolkadotMetadataFromChainName("Westend");
@@ -215,24 +243,8 @@ TEST(PolkadotChainMetadataUnitTest,
// curl -sS -H 'Content-Type: application/json' \
// -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \
// https://westend-asset-hub-rpc.polkadot.io/
const auto fixture_path =
base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT)
.AppendASCII("brave")
.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("brave_wallet")
.AppendASCII("polkadot")
.AppendASCII("chain_metadata")
.AppendASCII("state_getMetadata_assethub_westend.json");
const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path);
const std::string* metadata_hex = json.FindString(kResult);
ASSERT_TRUE(metadata_hex);
ASSERT_FALSE(metadata_hex->empty());
std::vector<uint8_t> metadata_bytes;
ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes));
std::vector<uint8_t> metadata_bytes =
ReadMetadataFixture("state_getMetadata_assethub_westend.json");
auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes);
ASSERT_TRUE(metadata);
@@ -244,7 +256,12 @@ TEST(PolkadotChainMetadataUnitTest,
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/42, /*spec_version=*/1'022'005,
/*asset_tx_payment=*/true);
/*asset_tx_payment=*/true,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/32,
/*assets_transfer_keep_alive_call_index=*/9);
EXPECT_EQ(*metadata, expected);
auto metadata2 = PolkadotMetadataFromChainName("Westend Asset Hub");
ASSERT_TRUE(metadata2);
@@ -5,8 +5,12 @@
#include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h"
#include <algorithm>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/containers/map_util.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
@@ -85,6 +89,40 @@ std::vector<uint8_t> ReadMetadataFixture(std::string_view file_name) {
return metadata_bytes;
}
bool ReplaceNthOccurrence(std::vector<uint8_t>& bytes,
std::string_view needle,
std::string_view replacement,
size_t occurrence) {
DCHECK(!needle.empty());
if (needle.empty()) {
return false;
}
const auto needle_bytes = base::as_byte_span(needle);
const auto replacement_bytes = base::as_byte_span(replacement);
auto it = bytes.begin();
size_t num_found = 0;
while (it != bytes.end()) {
auto match = std::ranges::search(it, bytes.end(), needle_bytes.begin(),
needle_bytes.end());
if (match.begin() == bytes.end()) {
return false;
}
if (num_found == occurrence) {
auto pos = bytes.erase(match.begin(), match.end());
bytes.insert(pos, replacement_bytes.begin(), replacement_bytes.end());
return true;
}
++num_found;
it = match.end();
}
return false;
}
std::optional<PolkadotChainMetadata> PolkadotMetadataFromChainName(
std::string_view chain_name) {
// spec_version is unknown when constructing from chain name alone; callers
@@ -103,7 +141,11 @@ std::optional<PolkadotChainMetadata> PolkadotMetadataFromChainName(
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/42, kUnknownSpecVersion,
/*asset_tx_payment=*/false);
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0);
}
// https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/asset-hub-kusama-json.json#L969
@@ -116,7 +158,11 @@ std::optional<PolkadotChainMetadata> PolkadotMetadataFromChainName(
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/42, kUnknownSpecVersion,
/*asset_tx_payment=*/true);
/*asset_tx_payment=*/true,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/32,
/*assets_transfer_keep_alive_call_index=*/9);
}
// https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/polkadot-json.json#L1096
@@ -129,7 +175,11 @@ std::optional<PolkadotChainMetadata> PolkadotMetadataFromChainName(
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/0, kUnknownSpecVersion,
/*asset_tx_payment=*/false);
/*asset_tx_payment=*/false,
/*has_assets_pallet=*/false,
/*assets_pallet_index=*/0,
/*assets_transfer_all_call_index=*/0,
/*assets_transfer_keep_alive_call_index=*/0);
}
// https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/asset-hub-polkadot-json.json#L969
@@ -142,7 +192,11 @@ std::optional<PolkadotChainMetadata> PolkadotMetadataFromChainName(
/*transfer_keep_alive_call_index=*/3,
/*transfer_all_call_index=*/4,
/*ss58_prefix=*/0, kUnknownSpecVersion,
/*asset_tx_payment=*/true);
/*asset_tx_payment=*/true,
/*has_assets_pallet=*/true,
/*assets_pallet_index=*/50,
/*assets_transfer_all_call_index=*/32,
/*assets_transfer_keep_alive_call_index=*/9);
}
return std::nullopt;
@@ -7,8 +7,10 @@
#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_POLKADOT_POLKADOT_TEST_UTILS_H_
#include <array>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/values.h"
@@ -25,6 +27,19 @@ base::DictValue RequestBodyToJsonDict(const network::ResourceRequest& req);
std::string ReadMetadataFixtureJson(std::string_view file_name);
std::vector<uint8_t> ReadMetadataFixture(std::string_view file_name);
// Replaces the zero-based nth occurrence of `needle` in `bytes` with
// `replacement`. Returns false if `needle` is empty or if the requested
// occurrence is not found.
//
// Examples:
// ReplaceNthOccurrence("a b a b", "a", "x", 0) -> "x b a b"
// ReplaceNthOccurrence("a b a b", "a", "x", 1) -> "a b x b"
// ReplaceNthOccurrence("a b", "z", "x", 0) -> false
bool ReplaceNthOccurrence(std::vector<uint8_t>& bytes,
std::string_view needle,
std::string_view replacement,
size_t occurrence);
struct PolkadotMockRpc {
public:
PolkadotMockRpc(network::TestURLLoaderFactory* url_loader_factory,
@@ -0,0 +1,65 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h"
#include <string_view>
#include <vector>
#include "base/containers/span.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace brave_wallet {
namespace {
std::vector<uint8_t> BytesForTest(std::string_view value) {
auto bytes = base::as_byte_span(value);
return std::vector(bytes.begin(), bytes.end());
}
} // namespace
TEST(PolkadotTestUtilsUnitTest,
ReplaceNthOccurrenceReplacesRequestedOccurrence) {
auto bytes = BytesForTest("transfer transfer transfer");
EXPECT_TRUE(ReplaceNthOccurrence(bytes, "transfer", "replaced",
/*occurrence=*/1));
EXPECT_EQ(bytes, BytesForTest("transfer replaced transfer"));
}
TEST(PolkadotTestUtilsUnitTest,
ReplaceNthOccurrenceSupportsDifferentLengthReplacement) {
auto bytes = BytesForTest("before transfer after");
EXPECT_TRUE(ReplaceNthOccurrence(bytes, "transfer", "x",
/*occurrence=*/0));
EXPECT_EQ(bytes, BytesForTest("before x after"));
}
TEST(PolkadotTestUtilsUnitTest, ReplaceNthOccurrenceMissingNeedleFails) {
auto bytes = BytesForTest("before transfer after");
const auto original = bytes;
EXPECT_FALSE(ReplaceNthOccurrence(bytes, "missing", "x",
/*occurrence=*/0));
EXPECT_EQ(bytes, original);
}
TEST(PolkadotTestUtilsUnitTest, ReplaceNthOccurrenceOutOfRangeFails) {
auto bytes = BytesForTest("transfer transfer");
const auto original = bytes;
EXPECT_FALSE(ReplaceNthOccurrence(bytes, "transfer", "replaced",
/*occurrence=*/2));
EXPECT_EQ(bytes, original);
}
} // namespace brave_wallet