-
Notifications
You must be signed in to change notification settings - Fork 85
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
Refactor: Unify DomainAddress and account conversions #1967
Changes from all commits
9cac0c1
a76eba5
634d2ee
6db6ca9
ca9efa0
95d9148
81ee4a5
6bbf27d
5825728
84c03a8
764e4b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -10,113 +10,120 @@ | |||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
// GNU General Public License for more details. | ||||||
|
||||||
use cfg_utils::vec_to_fixed_array; | ||||||
use frame_support::pallet_prelude::RuntimeDebug; | ||||||
use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; | ||||||
use scale_info::TypeInfo; | ||||||
use sp_runtime::traits::AccountIdConversion; | ||||||
use sp_runtime::{traits::AccountIdConversion, TypeId}; | ||||||
|
||||||
use crate::EVMChainId; | ||||||
|
||||||
const MAX_ADDRESS_SIZE: usize = 32; | ||||||
pub type LocalAddress = [u8; 32]; | ||||||
pub type EthAddress = [u8; 20]; | ||||||
|
||||||
pub fn local_to_eth_address(address: LocalAddress) -> EthAddress { | ||||||
*address | ||||||
.split_first_chunk::<20>() | ||||||
.expect("always fit, qed") | ||||||
.0 | ||||||
} | ||||||
|
||||||
pub fn evm_to_local_address(chain_id: u64, address: EthAddress) -> LocalAddress { | ||||||
// We use a custom encoding here rather than relying on | ||||||
// `AccountIdConversion` for a couple of reasons: | ||||||
// 1. We have very few bytes to spare, so choosing our own fields is nice | ||||||
// 2. AccountIdConversion puts the tag first, which can unbalance the storage | ||||||
// trees if users create many H160-derived accounts. We put the tag last | ||||||
// here. | ||||||
let tag = b"EVM"; | ||||||
let mut bytes = [0; 32]; | ||||||
bytes[0..20].copy_from_slice(&address); | ||||||
bytes[20..28].copy_from_slice(&chain_id.to_be_bytes()); | ||||||
bytes[28..31].copy_from_slice(tag); | ||||||
bytes | ||||||
} | ||||||
|
||||||
/// A Domain is a chain or network we can send a message to. | ||||||
#[derive(Encode, Decode, Clone, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] | ||||||
#[derive(Encode, Decode, Clone, Copy, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] | ||||||
pub enum Domain { | ||||||
/// Referring to the Centrifuge Parachain. Will be used for handling | ||||||
/// incoming messages. | ||||||
/// | ||||||
/// NOTE: messages CAN NOT be sent directly from the Centrifuge chain to the | ||||||
/// Centrifuge chain itself. | ||||||
Centrifuge, | ||||||
/// Referring to the Local Parachain. | ||||||
Local, | ||||||
lemunozm marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// An EVM domain, identified by its EVM Chain Id | ||||||
EVM(EVMChainId), | ||||||
Evm(EVMChainId), | ||||||
} | ||||||
|
||||||
impl TypeId for Domain { | ||||||
const TYPE_ID: [u8; 4] = crate::ids::DOMAIN_ID; | ||||||
} | ||||||
|
||||||
impl Domain { | ||||||
pub fn into_account<AccountId: Encode + Decode>(&self) -> AccountId { | ||||||
DomainLocator { | ||||||
domain: self.clone(), | ||||||
} | ||||||
.into_account_truncating() | ||||||
self.into_account_truncating() | ||||||
} | ||||||
} | ||||||
|
||||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] | ||||||
pub struct DomainLocator<Domain> { | ||||||
pub domain: Domain, | ||||||
} | ||||||
|
||||||
#[derive(Encode, Decode, Clone, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] | ||||||
pub enum DomainAddress { | ||||||
/// A Centrifuge-Chain based account address, 32-bytes long | ||||||
Centrifuge([u8; 32]), | ||||||
/// An EVM chain address, 20-bytes long | ||||||
EVM(EVMChainId, [u8; 20]), | ||||||
/// A local based account address | ||||||
Local(LocalAddress), | ||||||
/// An EVM chain address | ||||||
Evm(EVMChainId, EthAddress), | ||||||
} | ||||||
|
||||||
impl Default for DomainAddress { | ||||||
fn default() -> Self { | ||||||
DomainAddress::Centrifuge([0; 32]) | ||||||
} | ||||||
impl TypeId for DomainAddress { | ||||||
const TYPE_ID: [u8; 4] = crate::ids::DOMAIN_ADDRESS_ID; | ||||||
} | ||||||
|
||||||
impl DomainAddress { | ||||||
pub fn evm(chain_id: EVMChainId, address: [u8; 20]) -> Self { | ||||||
Self::EVM(chain_id, address) | ||||||
} | ||||||
|
||||||
pub fn centrifuge(address: [u8; 32]) -> Self { | ||||||
Self::Centrifuge(address) | ||||||
} | ||||||
} | ||||||
|
||||||
impl From<(EVMChainId, [u8; 20])> for DomainAddress { | ||||||
fn from((chain_id, address): (EVMChainId, [u8; 20])) -> Self { | ||||||
Self::evm(chain_id, address) | ||||||
impl Default for DomainAddress { | ||||||
fn default() -> Self { | ||||||
DomainAddress::Local(LocalAddress::default()) | ||||||
} | ||||||
} | ||||||
|
||||||
impl From<DomainAddress> for Domain { | ||||||
fn from(x: DomainAddress) -> Self { | ||||||
match x { | ||||||
DomainAddress::Centrifuge(_) => Domain::Centrifuge, | ||||||
DomainAddress::EVM(chain_id, _) => Domain::EVM(chain_id), | ||||||
DomainAddress::Local(_) => Domain::Local, | ||||||
DomainAddress::Evm(chain_id, _) => Domain::Evm(chain_id), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
impl DomainAddress { | ||||||
/// Get the address in a 32-byte long representation. | ||||||
/// For EVM addresses, append 12 zeros. | ||||||
pub fn address(&self) -> [u8; 32] { | ||||||
match self.clone() { | ||||||
Self::Centrifuge(x) => x, | ||||||
Self::EVM(_, x) => vec_to_fixed_array(x), | ||||||
pub fn new(domain: Domain, address: [u8; MAX_ADDRESS_SIZE]) -> Self { | ||||||
match domain { | ||||||
Domain::Local => DomainAddress::Local(LocalAddress::from(address)), | ||||||
Domain::Evm(chain_id) => DomainAddress::Evm(chain_id, local_to_eth_address(address)), | ||||||
} | ||||||
} | ||||||
|
||||||
pub fn from_local(address: impl Into<LocalAddress>) -> DomainAddress { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I am not sure I am fully happy with the naming here because the returned domain address implicitly local even though it could also be EVM from a 32 byte array input. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user uses a 32-byte array (that represents an eth address expanded to 32 bytes) and sets this as local, then they are using the method wrongly. Or if not wrongly, they are creating an account from an array with X data, where X is a eth address in the array. Not sure if I got what you want to say. |
||||||
DomainAddress::Local(address.into()) | ||||||
} | ||||||
|
||||||
pub fn from_evm(chain_id: EVMChainId, address: impl Into<EthAddress>) -> DomainAddress { | ||||||
DomainAddress::Evm(chain_id, address.into()) | ||||||
} | ||||||
|
||||||
pub fn domain(&self) -> Domain { | ||||||
self.clone().into() | ||||||
} | ||||||
} | ||||||
|
||||||
#[cfg(test)] | ||||||
mod tests { | ||||||
use parity_scale_codec::{Decode, Encode}; | ||||||
|
||||||
use super::*; | ||||||
|
||||||
#[test] | ||||||
fn test_domain_encode_decode() { | ||||||
test_domain_identity(Domain::Centrifuge); | ||||||
test_domain_identity(Domain::EVM(1284)); | ||||||
test_domain_identity(Domain::EVM(1)); | ||||||
impl DomainAddress { | ||||||
pub fn as_local<Address: From<LocalAddress>>(&self) -> Address { | ||||||
match self.clone() { | ||||||
Self::Local(x) => x, | ||||||
Self::Evm(chain_id, x) => evm_to_local_address(chain_id, x), | ||||||
} | ||||||
.into() | ||||||
} | ||||||
|
||||||
/// Test that (decode . encode) results in the original value | ||||||
fn test_domain_identity(domain: Domain) { | ||||||
let encoded = domain.encode(); | ||||||
let decoded = Domain::decode(&mut encoded.as_slice()).unwrap(); | ||||||
|
||||||
assert_eq!(domain, decoded); | ||||||
pub fn as_eth<Address: From<EthAddress>>(&self) -> Address { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Namespace
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking now about just |
||||||
match self.clone() { | ||||||
Self::Local(x) => local_to_eth_address(x), | ||||||
Self::Evm(_, x) => x, | ||||||
} | ||||||
.into() | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Let's stick to one namespace for Eth, Evm, Solidity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to distinguish for the name:
I say this because not sure for example if
[u8; 20]
is an evm address, I think not because it does not have chain information 🤔But not sure if this association makes sense TBH. It seems not 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely get what you are saying but to me, having two namespaces just causes confusion.
However, since he address format is referred to as the Ethereum address format (as least from Metamask), it appears to me your distinction is correct. So fine with keeping as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add a comment regarding this then to explain the why. Thanks for the ref.
I think the key sentence is: in a EVM there are ETH address, so
evm
makes sense for domains, andeth
for addressesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frontier's approach is pretty straightforward when it comes to eth/evm addresses, they name these
AccountId20
- here.As someone who didn't have to deal much with
Domain
/DomainAddress
, I find that local/eth/evm are confusing as well tbh.Why not go for something simpler like:
CentrifugeAccount
-AccountId32
.EvmAccount
-AccountId20
/[u8;20]
.CentrifugeEvmAccount
-AccountId32
that's basicallyEvmAccount
+chain_id
+tag
.Then we could have implementations for:
CentrifugeAccount
->EvmAccount
- truncate;EvmAccount
->CentrifugeAccount
- pad with 0s;CentrifugeAccount
<->CentrifugeEvmAccount
- no need;EvmAccount
->CentrifugeEvmAccount
- appendchain_id
+tag
;CentrifugeEvmAccount
->EvmAccount
- truncate;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing this!
And these
CentrifugeAccount,
etc, should be types on their own, right? So you would directly remove theDomainAddress
type? Which type will you use to track all of them generically?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I would add them to the
DomainAddress
:This way we can keep the above mentioned conversions, and just have
From
implementations forDomainAddress
, and for each of these account types.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I like the way you're taking it.
Just a problem with this. The variant
Evm(EvmAccount)
will not have aDomain
information associated with it. A DomainAddress requires it to later extract this.For sure, I would not have time to do such a refactor for Monday 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add a struct for the evm account that has the chain and the actual account, or keep it as it is right now? I dunno, just thinking out loud at this point ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that would be what we have right now, right? 🤔
I'm going to sketch a new draft with some ideas from this, but reusing the current
DomainAddress
, let's see. Thanks for your thoughts!