centrifuge / liquidity-pools

Liquidity Pools enable seamless deployment of Centrifuge pools on any EVM-compatible blockchain
https://centrifuge.io
GNU Lesser General Public License v3.0
28 stars 8 forks source link

Connectors v1: Transfer message design and questions #37

Closed NunoAlexandre closed 1 year ago

NunoAlexandre commented 1 year ago

The message

The Transfer message aims to transfer a specific amount of tranche tokens to a destination address.

Transfer {
  pool_id: PoolId,
  tranche_id: TrancheId,
  domain: Domain,
  destination: Address, // [u8; 32] i.e, 32 bytes address 
  amount: Balance,
},

Flows

1. [Substrate] Cent Chain ---------> [EVM] Connector Contract

In this flow, amount will be transferred from the Cent Chain to the destination user in the EVM. In the EVM, address is a 20-byte, while in Substrate it is 32-bytes.

By keeping the Address type of the Transfer message to a 32-byte long address, when transferring tokens from the Cent-Chain to an EVM address, we must ensure that a 20-byte address is passed and that the required leading 12 zeros are added. When then decoding this message on the Solidity side, we must decode the first 20-bytes and ignore the leading 12.

⏭️ That means that in the cent-chain, when sending Transfer to an EVM-based domain, we should ensure the address to be 20-bytes and not 32. We could make Address an enum:

enum Address {
  EVM([u8; 20]),
  Substrate([u8; 32]),
}

We can't guarantee at the type-level that Address and the Domain types are used correctly, but we could define runtime restrictions to avoid human mistakes such as passing Domain::EVM + Address::Substrate pair.

2. [EVM] Connector Contract ---------> [Substrate] Cent Chain

In this flow, the destination of the transfer will be a 32-byte address.

⏭️ Therefore, we must again have a way of ensuring that if the transfer is targeting the Cent-Chain domain, that the address type passed is 32-bytes long, and that if it targets another EVM domain that the address is 20-bytes long.

⏭️ This requires the solidity formatTransfer function to expect bytes32 user instead of address user. By doing this, a caveat to keep in mind is that we will need specific testTransferEquivalence tests for each flow, since formatTransfer would encode 32 bytes but parseTransfer only reads 20.

Questions

a. Any thoughts on how to best implement the caveats or implementation suggestions flagged above (⏭️)?

b. how exactly is sendMessage supposed to be implemented? is it supposed to try to implement the transfer Evm -> Cent-Chain through Moonbeam as well, through Evm -> XCM bridging?

hieronx commented 1 year ago

Thanks for writing this down!

a. Any thoughts on how to best implement the caveats or implementation suggestions flagged above (⏭️)?

Why not change DomainAddress to something like

pub enum DomainAddress {
   EVM(EVMChainId, [u8; 20]),
   Parachain(ParachainId, [u8; 32]),
}

impl Into<Domain> for DomainAddress {
    /// convert to Domain here
}

Therefore, we must again have a way of ensuring that if the transfer is targeting the Cent-Chain domain, that the address type passed is 32-bytes long, and that if it targets another EVM domain that the address is 20-bytes long.

For now we anyways should only allow transfers to Cent Chain domains, so this simplifies it.

b. how exactly is sendMessage supposed to be implemented? is it supposed to try to implement the transfer Evm -> Cent-Chain through Moonbeam as well, through Evm -> XCM bridging?

It should format the message and then it's dependent on the router. For the XCM router for connectors deployed on Moonbeam, it would send a message through https://docs.moonbeam.network/builders/interoperability/xcm/xcm-transactor/#xcmtrasactor-solidity-interface I believe.

NunoAlexandre commented 1 year ago

Why not change DomainAddress to something like

Love it 💡

For now we anyways should only allow transfers to Cent Chain domains, so this simplifies it.

Sounds good!

It should format the message and then it's dependent on the router. For the XCM router for connectors deployed on Moonbeam, it would send a message through https://docs.moonbeam.network/builders/interoperability/xcm/xcm-transactor/#xcmtrasactor-solidity-interface I believe.

Cool that makes sense! @AStox is this something you are cool working on or do you want me to?

NunoAlexandre commented 1 year ago

Why not change DomainAddress to something like

pub enum DomainAddress {
  EVM(EVMChainId, [u8; 20]),
 Parachain(ParachainId, [u8; 32]),
}

@offerijns wait, I just realised this doesn't work. Say we want to transfer to our Connector contract on Moonbeam, the Domain is Xcm-based, Moonbeam, but the address should be [u8; 20], not [u8; 32].

NunoAlexandre commented 1 year ago

I am trying to model a better way to think about this conceptually. As far as I see it, at this at this stage, we have two flows to be concerned with:

  1. Messages from Cent Chain to EVM (wherever it is)
  2. Messages from EVM to Cent Chain

The first flow does not imply that the domain is necessarily an Evm(EvmChainId); in fact, sending a message to the Connectors contract on Moonbeam means sending a message to EVM which happens to need to go through XCM (and pallet_ethereum_xcm in this case) to get there.

So it seems to me that any message from the Cent-Chain will always be targeting an EVM instance, and that any incoming message in the near future will be coming from an EVM instance.

Connectors V1

The use cases we need to handle for Connectors v1 is to be able to:

  1. send all messages form the cent-chain to our Solidity smart contract deployed on Moonbeam
  2. receive Transfer messages in the cent-chain from said contract

For 1., Transfer.address should be an EVM address, [u8; 20]. For 2., Transfer.address should be an AccountId32 address, [u8; 32].

We can either choose to have an enum Message type for outgoing messages (to EVM, where address is [u8; 20]), and one for incoming Messages. This would facilitate restricting what messages can go out and in, as well as simplify the different types that different environments work with. Alternatively, we can choose to use types that work for all cases (e.g. [u8; 32] which works both for AccountId32 and EVM addresses) and restrict what's allowed in and out based as a runtime check.

Questions

NunoAlexandre commented 1 year ago

cc @mustermeiszer ☝️

AStox commented 1 year ago

Cool that makes sense! @AStox is this something you are cool working on or do you want me to?

Yah I definitely want to work on this aspect! But I imagine I'll be picking your brain on it too ;)