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

Making sense of total fees #55

Open
9oelM opened this issue Oct 3, 2024 · 5 comments
Open

Making sense of total fees #55

9oelM opened this issue Oct 3, 2024 · 5 comments
Labels
bug Something isn't working

Comments

@9oelM
Copy link

9oelM commented Oct 3, 2024

I have a question about tx.totalFees.coins. I genuinely cannot understand what this value is supposed to represent. In my case, I want to write a test on how much gas is spent for a series of transactions.

I've provided an example at https://github.com/9oelM/ton-sandbox-totalfees-bug.git. The test setup is simple:

  1. User wallet sends tx to ContractA (msg mode is 0)
  2. ContractA sends tx to ContractB (msg mode is 64)

In code, it is as simple as:

it('fees test', async () => {
        const increaser = await blockchain.treasury('increaser');
        const contractBTONBalanceBefore = await contractB.getBalance();

        const increaseResult = await contractA.sendIncrease(increaser.getSender(), {
            increaseBy: 1,
            value: toNano('0.05'),
        });

        expect(increaseResult.transactions).toHaveTransaction({
            from: increaser.address,
            to: contractA.address,
            success: true,
        });
        expect(increaseResult.transactions).toHaveTransaction({
            from: contractA.address,
            to: contractB.address,
            success: true,
        });

        printTransactionFees(increaseResult.transactions);

        const allFees = increaseResult.transactions.reduce((acc, tx) => {
            return acc + tx.totalFees.coins;
        }, 0n)
        
        const contractBTONBalanceAfter = await contractB.getBalance();
        console.log('contractB TON inflow', contractBTONBalanceAfter - contractBTONBalanceBefore);

        const lastTx = flattenTransaction(increaseResult.transactions[increaseResult.transactions.length - 1]);
        expect(lastTx.value! - lastTx.totalFees!).toBe(
            toNano('0.05') - allFees
        );
    });

printTransactionFees(increaseResult.transactions); prints:
Screenshot 2024-10-03 at 10 08 58

But I couldn't wrap my head around these numbers. I thought totalFees is supposed to be the sum of all total fees for a tx. In other words, $valueIn - valueOut = totalFees$. But this simply isn't the case:

  1. For index = 0, valueIn = 0.05 TON (we started with 0.05 TON) and valueOut = 0.0496 TON. valueIn - valueOut = 0.0004. But totalFees = 0.001613???
  2. Same for index = 1. valueIn = 0.0496 TON and valueOut = 0.046978 TON. valueIn - valueOut = 0.002622. But totalFees = 0.002296???

If totalFees were correctly calculated as total gas for the tx, toNano('0.05') - allFees would simply match the TON balance transferred to a contract, which is contractBTONBalanceAfter - contractBTONBalanceBefore. But this is not the case. In fact, lastTx.value - lastTx.totalFees == contractBTONBalanceAfter - contractBTONBalanceBefore != toNano('0.05') - allFees:

  • lastTx.value! - lastTx.totalFees! = 45904800n
  • contractBTONBalanceAfter - contractBTONBalanceBefore = 45904800n
  • toNano('0.05') - allFees = 45020139n

cc @krigga

@9oelM 9oelM added the bug Something isn't working label Oct 3, 2024
@9oelM 9oelM changed the title Making sense of total fees and tx.value Making sense of total fees Oct 3, 2024
@Trinketer22
Copy link
Contributor

Transaction has multiple phases (storage, compute, action).
Gas is compute phase only.
totalFees is sum of fees of all phases in the specified transaction. (not series of transactions).

@9oelM
Copy link
Author

9oelM commented Oct 3, 2024

@Trinketer22 thanks for the reply. Then could you break the fees in the first transaction down for me?:
image
How are they supposed to add up or make sense mathematically?

@Trinketer22
Copy link
Contributor

Well, the totalFees should be compute+action_fees+storage+external import(in case of transaction triggered by external message).
If we're talking tx with index 0, then the import fee is there, because it's external triggered.
As you can see, storage+import are not part of the table and action fees is in fact lower.

In order to get to the bottom of what is happening, you can print the blockchainLogs of this transaction.
Because for whatever reason, the import fee is not part of transaction description.

In case of transactions, triggered by external message, the fees are deducted from contract balance, so (valueIn - valueOut) is not valid logic here.

There is also a gotcha in the fact that totalFees represent the amount of fees collected at this point.
But the tricky part is that the forward fee is collected in two parts in/out. One part for the sending shard and the other for the receiving shard.
the fwdOut goes to the action fees and accounted in totalFees on the sender part, while inFwd part is accounted for totalFees on receiver.
I think it would be better to display action fees separately.

What printTransaction displays as outForwardFee is in fact the totalForwardFee, and that's likely a bug.

@9oelM
Copy link
Author

9oelM commented Oct 3, 2024

@Trinketer22 wow thank you so much for the explanation. Now I kinda get the hang of it. Appreciate the help.

@Trinketer22
Copy link
Contributor

@9oelM, you can also go through this attempt to explain fees in detail, if you haven't already.

Bare in mind that there is slight naming confusion when reading about fwd_fees. fwd_in/fwd_out vs fwd_mine/fwd_remain.
What printTransaction atempts to show as fwd_in/out is mine/remain and fwd_in supposed to be used for the external import fee.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants