## Proposal ### Justification #### Acala approach > Acala uses their own pallet, but unlike the previous two solutions, Acala actually performs a swap into their native currency. Similarly to Hydra, they have extrinsics to set the used currency (and swap path), but they also have a single-shot with_fee_path function. Their implementation is overkill for our purposes - they have this thing called a fee pool (in addition to the dex) and they allow fees to be paid by other accounts. They also reimplement a lot of code provided by substrate. For the swap to actually work a swap pool is needed, which would add a lot of uneccessary complexity and consumes a lot of time to maintain. The requirements don't imply to charge transaction fees necessarily in the native token ZTG. But obviously this would create demand for the ZTG token and foreign tokens would be sold for being able to pay transaction fees. #### HydraDX approach > Hydra uses a custom pallet. Like the substrate one, it does not do any swaps, and just pays the block author in a different currency. The documentation states otherwise, but it is incorrect - I checked on-chain behavior: see e.g. block 2363593. Unlike the substrate pallet, they configure the fee currency not on a per-extrinsic level, but instead they configure the used currency in a dedicated extrinsic (and as a consequence, it's persistent). Implementation detail: they use the same pallet_transaction_payment that we do, but with OnChargeTransaction that they implement in their transaction-multi-payment pallet. With this approach it's necessary to add a custom pallet by HydraDX. The fee currency is set by an extrinsic. So it's part of the [runtime storage](https://github.com/galacticcouncil/HydraDX-node/blob/e95231569765c163da9db844b03cc4089a835217/pallets/transaction-multi-payment/src/lib.rs#L177-L189). It's also worth mentioning whenever something changes inside the Substrate repository, this custom `transaction-multi-payment` pallet needs to be maintained by HydraDX too. For a native substrate pallet, it's likely to be maintained instantly. This approach could be easier for the front end to implement, because the fee tokens can just be queried on-chain and don't need to be stored by separately. https://github.com/galacticcouncil/HydraDX-node/tree/master/pallets/transaction-multi-payment ##### Substrate native: pallet-asset-tx-payment The path of the requirements leads to the usage of the `pallet-asset-tx-payment` pallet, because there is no other pre-written way inside of Substrate to handle transaction fees in different assets. The fees are charged in the foreign currency and would be hold by the Zeitgeist treasury in that currency. For example if the user pays with DOT, the fees wouldn't be paid in ZTG anymore, but directly in DOT. So, we wouldn't need any swap functionality here to swap from DOT to ZTG to pay the native fees. > This pallet is used by e.g. statemine and composable. The support in polkadot.js explorer is not good. Signing will work but it doesn't allow you to select different curriences. I think we could select the currency in our our front-end code, though. For front end integration regard this: https://substrate.stackexchange.com/questions/5695/informing-polkadot-api-about-additional-signed-extensions https://substrate.stackexchange.com/questions/8063/where-do-i-add-a-signedextension-in-a-subxt-call https://github.com/polkadot-js/api/blob/8ba4ed53776a7a71478e229547c7af3d4dc57d63/packages/types/src/extrinsic/signedExtensions/statemint.ts#L7-L14 https://github.com/paritytech/substrate/blob/5ea6d95309aaccfa399c5f72e5a14a4b7c6c4ca1/frame/transaction-payment/asset-tx-payment/src/lib.rs#L141-L152 #### Reasoning for pallet-asset-tx-payment As the Zeitgeist protocol does not have an internal swap mechanism for currencies and the overhead of implementing one just for the sake of providing the basis for one of the transaction fee solutions is an unacceptable overhead, the solution provided by Acala is automatically eliminated. Maintenance and long term support are important factors in the decision as they determine the future effort that is necessary to operate this solution. It is not guaranteed that this is given using the solution from HydraDX. Furthermore, the solution from HydraDX is unflexible in that it does only allow to pay is one currency at a time. To be precise, when an account is created for the first time, a currency is mapped to that account that was used to create that account. Now other currencies might be added to the account. Once the initial currency is depleted and the account is removed, the initial currency is removed as a fee payment currency. Now the system only allows to pay in the native currency until the account sets a new prefered currency, although other currencies are available in the account. While the Substrate pallet solution puts additional strain on the front-end developers, it also has the benefit that the front-end is always capable to determine a currency that fees can be paid with. The long-term support and the flexibility during currency selection make the Substrate pallet solution an ideal candidate for Zeitgeist. #### Integration of pallet-asset-tx-payment To understand how the pallet `pallet-asset-tx-payment` works, it's important to note that [it's a wrapper](https://github.com/paritytech/substrate/blob/ae1a608c91a5da441a0ee7c26a4d5d410713580d/frame/transaction-payment/asset-tx-payment/src/lib.rs#L32) around the `pallet-transaction-payment` pallet. This means that `pallet-asset-tx-payment` internally [calls the functionality](https://github.com/paritytech/substrate/blob/ae1a608c91a5da441a0ee7c26a4d5d410713580d/frame/transaction-payment/asset-tx-payment/src/lib.rs#L273-L281) of `pallet-transaction-payment` for the native currency and therefore depends on it. To use the `pallet-asset-tx-payment` pallet the following trait has to be implemented: ```rust /// Handle withdrawing, refunding and depositing of transaction fees. pub trait OnChargeAssetTransaction<T: Config> { /// The underlying integer type in which fees are calculated. type Balance: Balance; /// The type used to identify the assets used for transaction payment. type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; /// The type used to store the intermediate values between pre- and post-dispatch. type LiquidityInfo; /// Before the transaction is executed the payment of the transaction fees needs to be secured. /// /// Note: The `fee` already includes the `tip`. fn withdraw_fee( who: &T::AccountId, call: &T::RuntimeCall, dispatch_info: &DispatchInfoOf<T::RuntimeCall>, asset_id: Self::AssetId, fee: Self::Balance, tip: Self::Balance, ) -> Result<Self::LiquidityInfo, TransactionValidityError>; /// After the transaction was executed the actual fee can be calculated. /// This function should refund any overpaid fees and optionally deposit /// the corrected amount. /// /// Note: The `fee` already includes the `tip`. /// /// Returns the fee and tip in the asset used for payment as (fee, tip). fn correct_and_deposit_fee( who: &T::AccountId, dispatch_info: &DispatchInfoOf<T::RuntimeCall>, post_info: &PostDispatchInfoOf<T::RuntimeCall>, corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, ) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), TransactionValidityError>; } ``` The functions of this trait for `withdraw_fee` and `correct_and_deposit_fee` are meant to implement the charge of transaction fees in all foreign currencies. The types `Balance`, `AssetId` are pretty straigth-forward to assign, since the internal `Balance` type and `CurrencyId` type of the zeitgeist node can be used. Because `LiquidityInfo` is returned from `withdraw_fee` and passed as function parameter to `correct_and_deposit_fee` as `already_withdrawn` it's expected to represent a form of `Imbalance`. It actually can be seen [here](https://github.com/paritytech/substrate/blob/ae1a608c91a5da441a0ee7c26a4d5d410713580d/frame/transaction-payment/asset-tx-payment/src/payment.rs#L107), which turns out to be an imbalance from [here](https://paritytech.github.io/substrate/master/src/frame_support/traits/tokens/fungibles/imbalance.rs.html#172-178). To actually use the fee payment functionality of the newly configured pallet, it has to be integrated in the following way: > you should include both pallets in your `construct_runtime` macro, but only include this pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]) The integration information was found [here](https://github.com/paritytech/substrate/blob/ae1a608c91a5da441a0ee7c26a4d5d410713580d/frame/transaction-payment/asset-tx-payment/src/lib.rs#L32-L34). ### References - [Interlay's considered approaches](https://github.com/interlay/interbtc/pull/1054)