0

For pallet-evm there's a config item, actually two config items that I would put under the same category and that is CallOrigin and WithdrawOrigin :

pallet_evm::Config {
        /// Allow the origin to call on behalf of given address.
        type CallOrigin: EnsureAddressOrigin<Self::RuntimeOrigin>;
        /// Allow the origin to withdraw on behalf of given address.
        type WithdrawOrigin: EnsureAddressOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
        /// Currency type for withdraw and balance storage.
        type Currency: Currency<Self::AccountId> + Inspect<Self::AccountId>;
        // --snip--
}

I understand that CallOrigin is a barrier that must be passed in order to interact with the evm via pallet-evm extrinsics. However I can not say the same about WithdrawOrigin for the following reasons. Going by the description, it seems to configure what origin can withdraw the associated type Currency but the question that follows it is withdraw where?

Secondly, let's take an example implementation, if I use EnsureAddressTruncated and my chain is using AccountId32 the EnsureAddressOrigin<O> checking code is something like this :

/// Ensure that the address is truncated hash of the origin. Only works if the account id is
/// `AccountId32`.
pub struct EnsureAddressTruncated;

impl<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAddressTruncated
where
    OuterOrigin: Into<Result<RawOrigin<AccountId32>, OuterOrigin>> + From<RawOrigin<AccountId32>>,
{
    type Success = AccountId32;

    fn try_address_origin(address: &H160, origin: OuterOrigin) -> Result<AccountId32, OuterOrigin> {
        origin.into().and_then(|o| match o {
            RawOrigin::Signed(who) if AsRef::<[u8; 32]>::as_ref(&who)[0..20] == address[0..20] => {
                Ok(who)
            }
            r => Err(OuterOrigin::from(r)),
        })
    }
}

I don't understand quite a few things here, firstly, how is the address parameter an H160 when my dispatching origin is a AccountId32? Clearly this function must be called after the truncation and hashing of the account [u8;32] has been performed. And even if that happens, this check should necessarily pass.

So my doubts can be boiled to two main questions :

  1. What's the point of having a WithdrawOrigin origin check here, i.e. why do we need it?
  2. What does pallet_evm::withdraw extrinsic do?

1 Answer 1

0

Let's address your questions one by one:

WithdrawOrigin Origin Check: The purpose of the WithdrawOrigin origin check in the pallet_evm::Config is to control which origin is allowed to withdraw funds from the EVM contract. In the context of the EVM pallet, "withdraw" refers to transferring funds from the EVM contract's balance (Ethereum-like account) to the native currency account in your runtime. This is needed because, in Substrate-based blockchains, there might be multiple pallets or modules that handle different types of accounts, and the WithdrawOrigin specifies the origin that can execute a withdrawal from the EVM contract.

For example, if your runtime uses multiple account systems like AccountId32 and AccountId, the WithdrawOrigin provides a way to specify which origin (e.g., AccountId32 or AccountId) is allowed to trigger a withdrawal from the EVM contract back to the native currency's account.

Address Truncation and Hashing: The confusion you have about H160 and AccountId32 is because of the nature of Ethereum addresses and how they are represented in Substrate runtimes. Ethereum addresses are 20 bytes long (160 bits), and they are typically represented using H160 in the context of the EVM pallet.

However, in Substrate runtimes, the native accounts can have varying lengths, depending on the runtime configuration. AccountId32 represents an account identifier that is 32 bytes long (256 bits).

The EnsureAddressOrigin implementation you provided (EnsureAddressTruncated) is used to ensure that the origin provided to the EVM pallet contains a truncated hash of the account identifier (AccountId32). It checks whether the first 20 bytes of the provided account ID match the first 20 bytes of the Ethereum address (H160).

The implementation is using AccountId32 as the origin because that's the type used in your runtime's configuration. When the origin is passed to the EVM pallet, it first checks if the origin is from RawOrigin::Signed (indicating a signed extrinsic), and if so, it extracts the account ID (AccountId32), truncates it to the first 20 bytes (H160 size), and then compares it with the provided Ethereum address (H160). If they match, the check is successful, and the provided AccountId32 is considered valid as the origin.

pallet_evm::withdraw Extrinsic: The pallet_evm::withdraw extrinsic is an extrinsic provided by the EVM pallet that allows users to withdraw funds from the EVM contract back to their native currency account. When you interact with the EVM contract, it will accumulate funds in the contract's balance. The pallet_evm::withdraw extrinsic provides a way for users to transfer these funds from the EVM contract back to their designated account (specified by the WithdrawOrigin).

In summary, the WithdrawOrigin allows you to control which origin can trigger withdrawals from the EVM contract, and the pallet_evm::withdraw extrinsic enables users to withdraw funds from the EVM contract to their designated account in the native currency of the runtime.

Yes, your understanding is correct. The EVM pallet in Substrate-based blockchains handles its own internal currency that is distinct from the native currency of the Substrate runtime.

To elaborate further:

Internal Currency of EVM Pallet: The EVM pallet introduces its own internal currency to represent the value and balances within the EVM itself. This internal currency is used to track the value held by Ethereum-like accounts and smart contracts within the EVM. When EVM contracts are executed and perform operations such as transfers, the changes in balances are reflected in this internal currency.

Reserving Tokens during Mint: When you execute an EVM contract and it performs operations that lead to the creation of new tokens (e.g., minting new tokens or increasing the balance of an existing account), the EVM pallet will internally update the balances of the relevant accounts in its internal currency. This update effectively "reserves" the tokens within the EVM's internal system.

Releasing Tokens during Withdraw: When users trigger a withdrawal from the EVM contract (via the pallet_evm::withdraw extrinsic), the EVM pallet will release the reserved tokens from its internal currency and transfer them back to the designated account in the native currency of the Substrate runtime. This operation effectively converts the internal EVM currency back into the native currency of the runtime.

From the perspective of the Substrate runtime, it doesn't directly interact with the EVM's internal currency. Instead, the EVM pallet abstracts the internal currency and exposes an interface (extrinsics and storage) that allows users to interact with the EVM, deposit funds, execute contracts, and withdraw funds. These interactions are carried out in the context of the EVM's internal currency.

2
  • Thanks for your answer. This makes perfect sense. As a follow up question, if you will, I believe pallet-evm mints and withdraws currency (for evm contracts and accounts) that's categorically different from the substrate currency, and from the perspective of substrate, all pallet-evm is doing is reserving tokens during a mint, and releasing the same currency during a withdraw. Is this understanding correct ? Commented Jul 21, 2023 at 11:21
  • Updated the answer above since there is a character limit in comments. accept my answer if it makes sense to you. thanks! Commented Jul 24, 2023 at 13:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.