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

Refactor: Unify DomainAddress and account conversions #1967

Closed
wants to merge 11 commits into from

Conversation

lemunozm
Copy link
Contributor

@lemunozm lemunozm commented Aug 14, 2024

Description

  • Removed account conversions though Convert traits. Not DomainAddress know about to convert accounts.
  • Unify DomainAddress to [u8;32]. See this slack thread
    • Before this PR you can transform a DomainAddress into [u8;32] in two ways:

      • Using T::DomainAddressIntoAccount::convert() which put 20 bytes from Eth + chain + "EVM" number.
      • Using DomainAddress.address() which put 20 bytes following by 0s.

      This is pretty confusing because is easy to use one or the other in different places, ending with different arrays values. From this PR, the second option is removed. We always add the extra information each time we extract the array from a DomainAddress

      • We need to ensure this is not a problem with out current chain state. From Solidity it seems not because Solidity always reads the first 20 bytes.
  • Added sugar method to DomainAddress to easy work with them safely
  • Renamed Centrifuge to Local (or maybe I revert this)
  • Renamed EVM to Evm
  • (only in IT). Removed From/Into traits from Keyring to H160 and [u8;20] and use instead an explicit method called in_eth() (open to name proposals). This method represents the account in the Ethereum network. Note that this differs from Keyring::Alice.into()[0..20], which is used in other places where an ethereum account is stored as an AccountId and needs to be recovered. Because this is highly confusing, I've removed the into/from methods to be more explicit about when the first case happens.

@lemunozm lemunozm self-assigned this Aug 14, 2024
@lemunozm lemunozm force-pushed the ref/domain-address branch from af9bc6c to f2f9db5 Compare August 14, 2024 15:30
@lemunozm
Copy link
Contributor Author

lemunozm commented Aug 14, 2024

Current implementation is done as:

pub enum DomainAddress {
    Local([u8; 32]),		
    Evm(EVMChainId, [u8; 20]),
}

But I'm thinking of changing this into:

pub enum DomainAddress {
    Centrifuge(AccountId32),		
    Evm(EVMChainId, H160),
}

I do not like that we expose the AccountId32 type (which, theoretically, is set at runtime), but we would gain much more type safety. It's a pain to deal with arrays. Maybe it makes sense to expose an AccountId32 because speaking about a "Centrifuge domain" inherently carries some "domain information"

EDIT: after playing with the second approach, I feel like it's harder to deal with in a lot of scenarios, so I will keep the first model.

@lemunozm lemunozm marked this pull request as ready for review August 14, 2024 18:10
Copy link

codecov bot commented Aug 14, 2024

Codecov Report

Attention: Patch coverage is 60.34483% with 46 lines in your changes missing coverage. Please review.

Project coverage is 47.76%. Comparing base (e8f1efd) to head (764e4b2).

Files Patch % Lines
pallets/liquidity-pools/src/hooks.rs 0.00% 10 Missing ⚠️
libs/types/src/domain_address.rs 76.66% 7 Missing ⚠️
node/src/chain_spec.rs 0.00% 6 Missing ⚠️
pallets/liquidity-pools-gateway/src/lib.rs 0.00% 6 Missing ⚠️
pallets/liquidity-pools/src/lib.rs 85.18% 4 Missing ⚠️
runtime/common/src/gateway.rs 0.00% 4 Missing ⚠️
runtime/common/src/routing.rs 0.00% 3 Missing ⚠️
runtime/common/src/account_conversion.rs 0.00% 2 Missing ⚠️
pallets/axelar-router/src/lib.rs 66.66% 1 Missing ⚠️
runtime/altair/src/lib.rs 0.00% 1 Missing ⚠️
... and 2 more
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1967   +/-   ##
=======================================
  Coverage   47.76%   47.76%           
=======================================
  Files         183      183           
  Lines       12929    12887   -42     
=======================================
- Hits         6176     6156   -20     
+ Misses       6753     6731   -22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lemunozm lemunozm requested review from wischli and cdamian and removed request for wischli August 14, 2024 22:33
@lemunozm lemunozm changed the title Refactor: Unify DomainAddress and account conversions (WIP) Refactor: Unify DomainAddress and account conversions Aug 15, 2024
@lemunozm lemunozm force-pushed the ref/domain-address branch from 279d48e to 84c03a8 Compare August 15, 2024 11:02
Copy link
Contributor

@wischli wischli left a comment

Choose a reason for hiding this comment

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

I wish we did this earlier because this would have saved me quite some time! Overall, looks really good to me. I just have a few nits and couple of questions.

One change request on which I cannot comment: Please revert the submodule change because you are going back one commit there: This PR wants to change 6e8f1a29dff0d7cf5ff74285cfffadae8a8b303f which is origin/main to 4301885b9a3b8ec36f3bda4b789daa5b115c006a

}
}

pub fn from_local(address: impl Into<LocalAddress>) -> DomainAddress {
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

pub type LocalAddress = [u8; 32];
pub type EthAddress = [u8; 20];

pub fn local_to_eth_address(address: LocalAddress) -> EthAddress {
Copy link
Contributor

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

Suggested change
pub fn local_to_eth_address(address: LocalAddress) -> EthAddress {
pub fn local_to_evm_address(address: LocalAddress) -> EthAddress {

Copy link
Contributor Author

@lemunozm lemunozm Aug 15, 2024

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:

  • eth: just the address
  • evm: and ethereum address + chain information

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 😅

Copy link
Contributor

@wischli wischli Aug 15, 2024

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.

Copy link
Contributor Author

@lemunozm lemunozm Aug 15, 2024

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, and eth for addresses

Copy link
Contributor

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 basically EvmAccount + chain_id + tag.

Then we could have implementations for:

  • CentrifugeAccount -> EvmAccount - truncate;
  • EvmAccount -> CentrifugeAccount - pad with 0s;
  • CentrifugeAccount <-> CentrifugeEvmAccount - no need;
  • EvmAccount -> CentrifugeEvmAccount - append chain_id + tag;
  • CentrifugeEvmAccount -> EvmAccount - truncate;

Copy link
Contributor Author

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 the DomainAddress type? Which type will you use to track all of them generically?

Copy link
Contributor

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:

pub enum DomainAddress {
	/// A standard Centrifuge account.
	Centrifuge(CentrifugeAccount),
	
	/// A Centrifuge account derived from an `EvmAccount`.
	CentrifugeEvm(CentrifugeEvmAccount),
	
	/// An EVM-compatible account.
	Evm(EvmAccount),
}

This way we can keep the above mentioned conversions, and just have From implementations for DomainAddress, and for each of these account types.

Copy link
Contributor Author

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 a Domain 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 😅

Copy link
Contributor

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 ^^

Copy link
Contributor Author

@lemunozm lemunozm Aug 15, 2024

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!

let decoded = Domain::decode(&mut encoded.as_slice()).unwrap();

assert_eq!(domain, decoded);
pub fn as_eth<Address: From<EthAddress>>(&self) -> Address {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Namespace

Suggested change
pub fn as_eth<Address: From<EthAddress>>(&self) -> Address {
pub fn as_evm<Address: From<EthAddress>>(&self) -> Address {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking now about just eth/evm, WDYT?

pallets/liquidity-pools/src/hooks.rs Show resolved Hide resolved

test_encode_decode_identity(
Message::TransferTrancheTokens {
pool_id: 1,
tranche_id: default_tranche_id(),
domain: domain_address.domain().into(),
receiver: domain_address.address(),
receiver: domain_address.as_local(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I am skeptical about the changed serialization here. Probably should be

Suggested change
receiver: domain_address.as_local(),
receiver: domain_address.as_eth(),

Copy link
Contributor Author

@lemunozm lemunozm Aug 15, 2024

Choose a reason for hiding this comment

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

address() returned a [u8; 32], the receiver has type [u8; 32] but as_eth() returns an [u8;20].

Basically we use address as 32-bytes in the message for compatibility reasons, but later the 12 remaining bytes are discarded by solidity, see this thread: https://kflabs.slack.com/archives/C06FRHCRQL9/p1723627567780339

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for breaking this down. Seems like the message serialization is now correct and was poorly mocked beforehand, i.e. by using the 20 byte account address which in reality, should never occur.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, previously it was a chaos of different versions of [u8; 20] -> [u8; 32] conversions

pallets/liquidity-pools/src/mock.rs Show resolved Hide resolved
AccountId::new(arr)
};
pub const CENTRIFUGE_DOMAIN_ADDRESS: DomainAddress = DomainAddress::Centrifuge(ALICE_32);
pub const ALICE_EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::Evm(42, ALICE_ETH);
Copy link
Contributor

Choose a reason for hiding this comment

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

You re-inserted the incorrect chain ID which was fixed in #1964

Suggested change
pub const ALICE_EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::Evm(42, ALICE_ETH);
pub const ALICE_EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::Evm(CHAIN_ID, ALICE_ETH);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should do something wrong in the rebase, although I do not know how it passes then, I will inspect

Copy link
Contributor Author

@lemunozm lemunozm Aug 15, 2024

Choose a reason for hiding this comment

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

Ok, this has changed because ALICE_EVM_LOCAL_ACCOUNT no longer exists, and now we're using ALICE_EVM_DOMAIN_ADDRESS, which has chain 42, not sure why TBH...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ok, never mind... fixing it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for being aware of this!

pallets/liquidity-pools/src/tests.rs Show resolved Hide resolved
runtime/common/src/gateway.rs Show resolved Hide resolved
/// Returns the Ethereum address.
/// The H160 retrived is NOT the local representation of that account in our
/// chain, it's the real address in ethereum.
pub fn in_eth(self) -> H160 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Namespace

Suggested change
pub fn in_eth(self) -> H160 {
pub fn in_evm(self) -> H160 {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will change if you confirm the above makes no sense, that probably not 😆

@lemunozm
Copy link
Contributor Author

This PR wants to change 6e8f1a29dff0d7cf5ff74285cfffadae8a8b303f which is origin/main to 4301885b9a3b8ec36f3bda4b789daa5b115c006a

Good point! It was during the rebase...

@lemunozm
Copy link
Contributor Author

Given the change from Local to Centrifuge, I should also change the as_local() and from_local(), I think both could be: as_account() (or just account()) and from_account().

Do you agree or do you have some preference? @wischli @cdamian

@wischli
Copy link
Contributor

wischli commented Aug 15, 2024

Given the change from Local to Centrifuge, I should also change the as_local() and from_local(), I think both could be: as_account() (or just account()) and from_account().

Do you agree or do you have some preference? @wischli @cdamian

Sounds good to me! {as, from}_centrifuge certainly sounds wrong. If we want to be more explicit, we could also use ..account32.. or ..account_id...

@lemunozm
Copy link
Contributor Author

Well, I need to think a bit more because what's returned is the 32-bit array, so the account name may not make sense in some places where the array is expected (as when we construct a message with an eth address expanded to 32 bytes). Such a dilemma!

@lemunozm
Copy link
Contributor Author

Closed in favor of: #1969

@lemunozm lemunozm closed this Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants