Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: nested polkadot fetch #4006

Merged
merged 6 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 77 additions & 10 deletions state-chain/chains/src/dot/api/batch_fetch_and_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::{
},
FetchAssetParams, TransferAssetParams,
};
use cf_primitives::ChannelId;
use cf_utilities::SliceToArray;
use sp_std::{boxed::Box, vec::Vec};

pub fn extrinsic_builder(
Expand All @@ -24,16 +26,7 @@ pub fn extrinsic_builder(
fetch_params
.into_iter()
.map(|fetch_param| {
PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
// TODO: refer to issue #2354
index: fetch_param.deposit_fetch_id as u16,
call: Box::new(PolkadotRuntimeCall::Balances(
BalancesCall::transfer_all {
dest: PolkadotAccountIdLookup::from(vault_account),
keep_alive: false,
},
)),
})
utility_fetch(fetch_param.deposit_fetch_id, vault_account)
})
.collect::<Vec<PolkadotRuntimeCall>>(),
transfer_params
Expand All @@ -52,6 +45,25 @@ pub fn extrinsic_builder(
)
}

fn utility_fetch(channel_id: ChannelId, vault_account: PolkadotAccountId) -> PolkadotRuntimeCall {
let layers = channel_id
.to_be_bytes()
.chunks(2)
.map(|chunk| u16::from_be_bytes(chunk.as_array::<2>()))
.skip_while(|layer| *layer == 0u16)
Copy link
Contributor

@ramizhasan111 ramizhasan111 Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, we should check whether a derivative address can both be used as a valid deposit address AND be used to generate further derivative addresses in a new layer. This is because this implementation would lead to such a case for example the channel ids [0x00,0x00,0x02,0x05] and [0x00,0xAA,0x02,0x05] where AA could be any byte.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been thinking about this a bit more. I think what it boils down to is whether these are different:

[0x01] (resulting in a call with offset 0x01).
[0x01, 0x00] (resulting in an offset 0x01 wrapped in an offset 0x00).

I think the answer is yes, since when there are two bytes, there are two layers of hashing. So it's the difference between (simplified) hash(0x00, hash(X, root)) vs hash(X, root). I'll write short test.

.collect::<Vec<u16>>();

layers.into_iter().fold(
PolkadotRuntimeCall::Balances(BalancesCall::transfer_all {
dest: PolkadotAccountIdLookup::from(vault_account),
keep_alive: false,
}),
|call, index| {
PolkadotRuntimeCall::Utility(UtilityCall::as_derivative { index, call: Box::new(call) })
},
)
}

#[cfg(test)]
mod test_batch_fetch {

Expand Down Expand Up @@ -108,4 +120,59 @@ mod test_batch_fetch {
builder.insert_signature(keypair_proxy.public_key(), keypair_proxy.sign(&payload));
assert!(builder.is_signed());
}

#[test]
fn nested_fetch() {
let channel_id = 0x0004_0003_0002_0001;
let vault_account = PolkadotAccountId::from_aliased([1u8; 32]);
let call = utility_fetch(channel_id, vault_account);

assert_eq!(
call,
PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
index: 0x0001,
call: Box::new(PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
index: 0x0002,
call: Box::new(PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
index: 0x0003,
call: Box::new(PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
index: 0x0004,
call: Box::new(PolkadotRuntimeCall::Balances(
BalancesCall::transfer_all {
dest: PolkadotAccountIdLookup::from(vault_account),
keep_alive: false,
}
)),
})),
})),
})),
})
);

let channel_id = 1;
let vault_account = PolkadotAccountId::from_aliased([1u8; 32]);
let call = utility_fetch(channel_id, vault_account);

assert_eq!(
call,
PolkadotRuntimeCall::Utility(UtilityCall::as_derivative {
index: 1,
call: Box::new(PolkadotRuntimeCall::Balances(BalancesCall::transfer_all {
dest: PolkadotAccountIdLookup::from(vault_account),
keep_alive: false,
})),
})
);
}

#[test]
fn fetch_equivalence() {
let channel_id_1 = 0x0000_0000_0000_0001;
let channel_id_2 = 0x0000_0000_0001_0000;
let vault_account = PolkadotAccountId::from_aliased([1u8; 32]);
let call_1 = utility_fetch(channel_id_1, vault_account);
let call_2 = utility_fetch(channel_id_2, vault_account);

assert_ne!(call_1, call_2);
}
}
127 changes: 89 additions & 38 deletions state-chain/runtime/src/chainflip/address_derivation/dot.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::Vec;
use cf_chains::{address::AddressDerivationApi, dot::PolkadotAccountId, Chain, Polkadot};
use cf_primitives::ChannelId;
use cf_utilities::SliceToArray;
use frame_support::sp_runtime::{
traits::{BlakeTwo256, Hash},
DispatchError,
Expand All @@ -23,25 +24,30 @@ impl AddressDerivationApi<Polkadot> for AddressDerivation {
let master_account = Environment::polkadot_vault_account()
.ok_or(DispatchError::Other("Vault Account does not exist."))?;

// Because we re-use addresses, we don't expect to hit this case in the wild.
if channel_id > u16::MAX.into() {
return Err(DispatchError::Other(
"Channel ID too large. Polkadot can only support up to u16::MAX addresses",
))
}
let mut layers = channel_id
.to_be_bytes()
.chunks(2)
.map(|chunk| u16::from_be_bytes(chunk.as_array::<2>()))
.skip_while(|layer| *layer == 0u16)
.collect::<Vec<u16>>();

let mut payload = Vec::with_capacity(PAYLOAD_LENGTH);
// Fill the first slots with the derivation prefix.
payload.extend(PREFIX);
// Then add the 32-byte public key.
payload.extend(master_account.aliased_ref());
// Finally, add the index to the end of the payload.
payload.extend(&(<u16>::try_from(channel_id).unwrap()).to_le_bytes());
layers.reverse();

// Hash the whole thing
let payload_hash = BlakeTwo256::hash(&payload);
let payload_hash =
layers.into_iter().fold(*master_account.aliased_ref(), |sub_account, salt| {
let mut payload = Vec::with_capacity(PAYLOAD_LENGTH);
// Fill the first slots with the derivation prefix.
payload.extend(PREFIX);
// Then add the 32-byte public key.
payload.extend(sub_account);
// Finally, add the index to the end of the payload.
payload.extend(&salt.to_le_bytes());

Ok(PolkadotAccountId::from_aliased(*payload_hash.as_fixed_bytes()))
// Hash the whole thing
BlakeTwo256::hash(&payload).to_fixed_bytes()
});

Ok(PolkadotAccountId::from_aliased(payload_hash))
}

fn generate_address_and_state(
Expand All @@ -58,34 +64,79 @@ impl AddressDerivationApi<Polkadot> for AddressDerivation {
}
}

#[test]
fn test_dot_derive() {
#[cfg(test)]
mod test {
use super::*;
use crate::Runtime;
use cf_primitives::chains::assets::dot;
use frame_support::sp_runtime::app_crypto::Ss58Codec;
use pallet_cf_environment::PolkadotVaultAccountId;

frame_support::sp_io::TestExternalities::new_empty().execute_with(|| {
let (account_id, address_format) = sp_runtime::AccountId32::from_ss58check_with_version(
"15uPkKV7SsNXxw5VCu3LgnuaR5uSZ4QMyzxnLfDFE9J5nni9",
)
.unwrap();
PolkadotVaultAccountId::<Runtime>::put(PolkadotAccountId::from_aliased(
*account_id.as_ref(),
));
#[test]
fn single_layer() {
frame_support::sp_io::TestExternalities::new_empty().execute_with(|| {
let (account_id, address_format) =
sp_runtime::AccountId32::from_ss58check_with_version(
"15uPkKV7SsNXxw5VCu3LgnuaR5uSZ4QMyzxnLfDFE9J5nni9",
)
.unwrap();
PolkadotVaultAccountId::<Runtime>::put(PolkadotAccountId::from_aliased(
*account_id.as_ref(),
));

assert_eq!(
"12AeXofJkQErqQuiJmJapqwS4KiAZXBhAYoj9HZ2sYo36mRg",
sp_runtime::AccountId32::new(
*<AddressDerivation as AddressDerivationApi<Polkadot>>::generate_address(
dot::Asset::Dot,
6259
assert_eq!(
"12AeXofJkQErqQuiJmJapqwS4KiAZXBhAYoj9HZ2sYo36mRg",
sp_runtime::AccountId32::new(
*<AddressDerivation as AddressDerivationApi<Polkadot>>::generate_address(
dot::Asset::Dot,
6259
)
.unwrap()
.aliased_ref()
)
.unwrap()
.aliased_ref()
.to_ss58check_with_version(address_format),
);
});
}

#[test]
fn four_layers() {
frame_support::sp_io::TestExternalities::new_empty().execute_with(|| {
let (alice, address_format) = sp_runtime::AccountId32::from_ss58check_with_version(
"15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5",
)
.to_ss58check_with_version(address_format),
);
println!("Derivation worked for DOT! 🚀");
});
.unwrap();
PolkadotVaultAccountId::<Runtime>::put(PolkadotAccountId::from_aliased(
*alice.as_ref(),
));

assert_eq!(
// The following account was generated using nested utility.asDerivative calls in
// PolkaJS. The wrapped call was system.remarkWithEvent, which emits the generated
// address in its event.
//
// The call was dispatched from the Alice account.
//
// To see the call go to
// extrinsics/decode/0x1a0101001a0102001a0103001a01040000071448414c4c4f
// on any polkaJS instance connected to a polkadot node.
//
// Call details:
// 1a01 is the utility.asDerivative call index.
// 0007 is the system.remarkWithEvent call index.
// Encoded call: 0x1a01 0100 1a01 0200 1a01 0300 1a01 0400 0007 1448414c4c4f
// ---- -^-- ---- -^-- ---- -^-- ---- -^-- b"HALLO"
"1422Jc2BYRh5ENjxWJchoHPSC2Rd4jFs8PDWHqBJue4yskEt",
sp_runtime::AccountId32::new(
*<AddressDerivation as AddressDerivationApi<Polkadot>>::generate_address(
dot::Asset::Dot,
0x0004_0003_0002_0001
)
.unwrap()
.aliased_ref()
)
.to_ss58check_with_version(address_format),
);
});
}
}