Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Conversation

@shaunxw
Copy link
Contributor

@shaunxw shaunxw commented Feb 18, 2019

This is a follow-up change related to fees module. (issue #1515 and PR #1648 )

ChargeFee is introduced into balances and contract modules, to handle fee charge logic:

  1. balances: transfer/creation fee.
  2. contract: gas fee buy/refund.

@parity-cla-bot
Copy link

It looks like @shaopengw hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement at the following URL: https://cla.parity.io

Once you've signed, please reply to this thread with [clabot:check] to prove it.

Many thanks,

Parity Technologies CLA Bot

1 similar comment
@parity-cla-bot
Copy link

It looks like @shaopengw hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement at the following URL: https://cla.parity.io

Once you've signed, please reply to this thread with [clabot:check] to prove it.

Many thanks,

Parity Technologies CLA Bot

@shaunxw
Copy link
Contributor Author

shaunxw commented Feb 18, 2019

[clabot:check]

@parity-cla-bot
Copy link

It looks like @shaopengw signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

1 similar comment
@parity-cla-bot
Copy link

It looks like @shaopengw signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

@gavofyork
Copy link
Member

Where's the code for the fees module?

if new_balance < Some(<balances::Module<T>>::existential_deposit()) {
return Err("not enough funds for transaction fee");
}
<T as Trait>::ChargeFee::charge_fee(transactor, cost)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

The previous version of code ensures that new_balance cannot be below existential deposit. In other words we can't kill an account buy purchasing gas. Looking at the current implementation this property won't hold anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pepyakin Maybe in general ChargeFee (and TransferAsset in the implementation) trait are not supposed to kill transactor's account. Account got killed is more like an unexpected side effect while paying fees or doing transfer in other srml modules except balances. I could update the implementation of TransferAsset, to make the functions return Errs instead of killing accounts.

let refund = <T::Gas as As<T::Balance>>::as_(gas_meter.gas_left) * gas_meter.gas_price;
<balances::Module<T>>::set_free_balance(transactor, b + refund);
<balances::Module<T>>::increase_total_stake_by(refund);
<T as Trait>::ChargeFee::refund_fee(transactor, refund)
Copy link
Contributor

Choose a reason for hiding this comment

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

Because we ensure that buy_gas doesn't destroy account, the previous version can't fail. This one apparently can and if it fails for some reason, the whole transaction will be deemed as failed.

This be very misleading, given that the changes that were performed in that extrinsic are actually commited.

Copy link
Contributor Author

@shaunxw shaunxw Feb 18, 2019

Choose a reason for hiding this comment

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

If refund_fee always called after charge_fee and the amount of refund the less that charged, then it can't fail. refund_fee is designed to haveResult return type to make it more robust, as we can't guarantee how it is called.

Is it better to document this kind of expected way of usage, and get rid of Result return type? Or we ignore the return here as it can't fail in this case?

// NOTE: this should go after the commit to the storage, since the storage changes
// can alter the balance of the caller.
gas::refund_unused_gas::<T>(&origin, gas_meter);
gas::refund_unused_gas::<T>(&origin, gas_meter)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that here (and above) when we can't refund funds for some reason, we abort the execution of the extrinsic with an error, even though all changes are commited.

Moreover, we lose all calls recorded so far.

type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;

/// Fee charge.
type ChargeFee: ChargeFee<Self::AccountId, Amount=Self::Balance>;
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are ok with changes here regarding contracts, we need to document the requirements of ChargeFee implementation. I.e. refund_fee should fail and etc.

@shaunxw
Copy link
Contributor Author

shaunxw commented Feb 18, 2019

Where's the code for the fees module?

The fees module code has been merged in PR #1648 .

@gavofyork
Copy link
Member

I see now; missed that one. We'll let #1821 go in before this one.

@shaunxw
Copy link
Contributor Author

shaunxw commented Feb 21, 2019

ChargeFee and TransferAsset traits are updated that they won't kill accounts. Killing accounts is more an unexpected side effect for charge_fee and remove_from, as they are typically used together with refund_fee and add_to. Also it's more reasonable to return Err if balance would be below existential deposit after paying fee.

@shaunxw
Copy link
Contributor Author

shaunxw commented Feb 21, 2019

For enforcing ExistentialDeposit law, another possible solution is that, instead of kill an account instantly when balance below existential_deposit, balances module tracks accounts whose balance got changed, and only do check and kill accounts if needed on finalize.

This could solve these situations:

  1. An account got killed but then created later in the same tx/block execution, with creation fee charged. For instance, in one tx, Alice transferred money to Bob and her account got killed, but in another tx in the same block, Charles transferred her some found.
  2. Alice has some particular amount of money that, she cannot buy gas fee as her account would got killed after payment(because of here), but actually she should be able to, as the refund amount will make her account still alive.
  3. ChargeFee and TransferAsset trait (and other possible traits in the future) implementation doesn't have to take care of accounts got killed or not after functions called.

@xlc
Copy link
Contributor

xlc commented Feb 26, 2019

@gavofyork what is the plan for this PR? I think this is going to be conflict with #1782. Do you intent to wait until #1782 is merged?

@gavofyork
Copy link
Member

Yes, but the good news is that it should be merged today or tomorrow so we can reevaluate how and if these changes make sense in the light of the api changes.

@gavofyork
Copy link
Member

Batching up deletions may make sense. But will need some thought as it introduces additional runtime costs that I’d rather not have.

@xlc
Copy link
Contributor

xlc commented Feb 28, 2019

What additional runtime cost are you concerning about?
It defers (and merge) the needs to check account balance less than existential deposit to end of block, instead of in the transaction processing. I don't think this overhead is significant?
I can see more storage access will be needed in order to implement this, but this should be possible to be optimized with some special in-memory block scope store? This will benefits to other modules as well (fees module been one, possibly system module).

@gavofyork
Copy link
Member

I can see more storage access will be needed in order to implement this,

Precisely my concern.

@gavofyork gavofyork added the A0-please_review Pull request needs code review. label Mar 5, 2019
@gavofyork gavofyork removed the A1-onice label Mar 5, 2019
Self::decrease_total_stake_by(fee);
T::ChargeFee::charge_fee(transactor, fee)?;

Self::set_free_balance(transactor, new_from_balance - fee);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the fee subtracted again? What protects us from overflows?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tomusdrw If not subtracted, fee will actually be charged twice, since charge_fee has already removed fee from balance. Because fee was added to new_from_balance (by calculating liability), the subtraction here cannot fail.

Copy link
Contributor

@tomusdrw tomusdrw Mar 8, 2019

Choose a reason for hiding this comment

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

If I'm not mistaken it's the other way around:

let liability = match value.checked_add(&fee)...;
let new_from_balance = match from_balance.checked_sub(&liability)...;
T::ChargeFee::charge_fee(transactor, fee)?;
Self::set_free_balance(transactor, new_from_balance - fee);

The fee is subtracted from from_balance and then again subtracted before setting the free_balance. So unless I'm missing something: new_from_balance = from_balance - value - fee - fee

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tomusdrw You're right, my mistake. Will update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. Also added two unit tests for balance transfer, to cover fee charge.


Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
assert_ok!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));
assert_ok!(Balances::ensure_account_can_withdraw(&1, 1, WithdrawReason::Transfer, 9));
Copy link
Member

Choose a reason for hiding this comment

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

These are not logically equivalent. Any such changes should be well-documented in the PR with why unit tests are being altered.

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason of the change is old behavior is not correct.
balances transfer method not only withdraw for transfer reason, but also withdraw for tx fee reason. Therefore it should fail, which indeed failed with the updated behavior.
Related: #1962, #1978

Copy link
Contributor

Choose a reason for hiding this comment

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

@shaopengw maybe should have assert_err!(<Balances as TransferAsset<_>>::transfer(&1, &2, 1));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xlc The assert_noop! below has covered the TransactionPayment withdraw should fail case, so it should be fine without adding assert err for transfer.

If these tests are mean to be unit tests, maybe other transfer calls in this test should also be changed to ensure_account_can_withdraw like this line, as transfer implies both WithdrawReason::TransactionPayment and WithdrawReason::Transfer.

Copy link
Member

Choose a reason for hiding this comment

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

The reason of the change is old behavior is not correct.
balances transfer method not only withdraw for transfer reason, but also withdraw for tx fee reason

The old behaviour is correct. The only time transaction payment happens is prior to extrinsic execution in executive. Any further withdrawals are not "transaction payment" but high-level actions that are logically unrelated. The two should not be confused.

@gavofyork gavofyork removed the A0-please_review Pull request needs code review. label Mar 13, 2019
@xlc xlc mentioned this pull request Mar 14, 2019
@gavofyork
Copy link
Member

superceded in #2048

@gavofyork gavofyork closed this Mar 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants