taikoxyz / taiko-mono

A based rollup. 🥁
https://taiko.xyz
MIT License
4.43k stars 2.08k forks source link

feat(protocol): Calculate fee/reward based on proof time w/ 1559 math #13489

Closed adaki2004 closed 1 year ago

adaki2004 commented 1 year ago

Proposer fee /prover reward balanced algorithm

This PR introduces an implementation of the proposer fee / prover reward equilibrium calculation, aiming to balance deposits and withdrawals. The implemented (new baseFeeProof) calculation depends on the proving time and utilizes the following parameters:

proofTimeTarget: A preset of the average proving time (shall be overestimated-> cause no harm)
gasConsumed: The consumed L2 gas per block (currently set to the gasLimit).
adjustmentQuotient: The quotient used for adjusting base fees (16).

The base fee proof serves as a dynamic fee that adapts to the network conditions, aiming to keep the inflow and outflow of tokens in balance.

See issue discussion: https://github.com/taikoxyz/internal/issues/121 (Especially see Brecht's comment here, with fixed proof time target (Target 1) and 'Mint new tokens when needed' approach - in reality the deposits and withdrawals will be balanced.)

This is an experiment where the base idea is to have a simplified fee / reward calculation structure with TKO for provers and proposers and depends on proving time.

Notes:

What remains to be done in this PR:

adaki2004 commented 1 year ago

@Brechtpd @dantaik So here is a draft version from what we just talked about here.

This is the version of race 2 (moving average is the target) however I'd have some questions after doing some benchmark tests (python code vs. solidity/foundry tests). Tendencies (python code vs this) seems to be the same(slower time of proof -> higher fee and reward) but for example the reward - ratio is very weird in both.

'proof reward' vs. the 'proposer_fee' are not on the same 'level'.

Here is an example logs i extracted from my test: (you can run if you pull this branch and forge build &&forge test -vv)

Solidity

Proving time increases:

1.
Proposer fee:         0
FeeBase :              0
Prover reward: 3320,31250000 (TKO)

2.
Proposer fee:           425000,00000000 (TKO)
FeeBase:              42500000
Prover reward:             7080,07812500 (TKO)

3.
Proposer fee:           453125,00000000 (TKO)
FeeBase:              45312500
Prover reward:             9624,38648001 (TKO)

4.
Proposer fee:           451704,54000000
FeeBase:              45170454
Prover reward:             10780,03874648 (TKO)

Proving time decreases:

1.
Proposer fee:         0
FeeBase :              0
Prover reward:      21240,23437500 (TKO)

2.
Proposer fee:           543750,00000000 (TKO)
FeeBase:              54375000
Prover reward:            523,43750000 (TKO)

3.
Proposer fee:           83750,00000000 (TKO)
FeeBase:              8375000
Prover reward:             402,66423264 (TKO)

4.
Proposer fee:           83333,33000000 (TKO)
FeeBase:              8333333
Prover reward:             288,94990704 (TKO)

So as you see the TKO as fee is much higher, than the reward. I do not know if this is within the math, but i somewhat conclude both (so in the math somewhere, and also in my code. The latter also comes from the fact that solidity has no floating point operations, so for example 1.001 - 1.99 exp is always 1. I tried to create a workaround for it, and use higher precision, this is also within this branch - please see EXPONENTIAL_REWARD_FACTOR).

So the python script from here has same kind of an issue when i run them it shows 6-8 x between fee and reward):

Python

print("Quciker proving proving")
propose_block(50000000)
time = 400
prove_block(50000000)
propose_block(50000000)
time = 600
prove_block(50000000)
propose_block(50000000)
time = 650
prove_block(50000000)
propose_block(50000000)
time = 675
prove_block(50000000)

The results are:

Quciker proving proving
******* propose ********
network fee: 0.3668379411737963
prover fee: 500.00000000000006
basefee_network: 8.542737132336227e-09
******* prove ********
proof_time: 400
prover reward: 526700.2765236852
basefee_proof: 0.0015801008295710556
proof_time_average: 77.00000000000001
******* propose ********
network fee: 0.3668379411737963
prover fee: 79005.04147855278
basefee_network: 8.542737132336227e-09
******* prove ********
proof_time: 200
prover reward: 161161.83912022863
basefee_proof: 0.0012409461612257608
proof_time_average: 83.15000000000002
******* propose ********
network fee: 0.3668379411737963
prover fee: 62047.30806128804
basefee_network: 8.542737132336227e-09
******* prove ********
proof_time: 50
prover reward: 32658.909283771933
basefee_proof: 0.0010862353227782547
proof_time_average: 81.49250000000002
******* propose ********
network fee: 0.3668379411737963
prover fee: 54311.76613891274
basefee_network: 8.542737132336227e-09
******* prove ********
proof_time: 25
prover reward: 16402.00134360839
basefee_proof: 0.0010693120755952057
proof_time_average: 78.66787500000001

Quickest way to trace back calculation in the solidity code is searching for 2 debug print keywords:

PS.: HH failing tests are known issues, since testing the functions first with foundry + debugging - will modify tests and add more foundry ones when calculations are aligned.

Brechtpd commented 1 year ago

The large difference between the prover fee paid by the proposer and the reward is because the base fee is calculated based on proof_time_issued. Because the initial value for that is set to 0, the basefee will be wildly different from the expected value because the base fee needs some time to adjust to a correct value that will result in a reasonable fee. To get around that you can set proof_time_issued to an initial value that will result in a fee that makes sense from the start (I also updated my other python code):

basefee_proof = 10**18/L2_BLOCK_GAS_LIMIT
proof_time_issued = 
  (PROOF_TIME_TARGET * PROOF_ADJUSTMENT_QUOTIENT) * 
  math.log(basefee_proof * (PROOF_TIME_TARGET * PROOF_ADJUSTMENT_QUOTIENT))

This will make sure that the initial proving fee for a block of L2_BLOCK_GAS_LIMIT gas will be 1 token (10**18 decimals).

Do make sure that the part in the ln() is >= 1 because otherwise the initial value for proof_time_issued < 0 which doesn't make sense.

adaki2004 commented 1 year ago

@Brechtpd @dantaik Feel free to review. There are some outstanding items (see PR desc) otherwise functional wise this is the one Brecht put together in a python script.

Some observations from today's testing. I might not like that much the 'targetTime' anymore - as much as i did - because it has the implications we need to set it very precisely.

2 scenarios.:

  1. If proof time fluctuates then stabilizes AT proof time we get the equilibrium (long term : deposits = withdrawals) See foundry test: test_reward_and_fee_if_proof_time_increasing_then_stabilizes_at_the_proof_time_target

    kép
  2. If proof time increases / decreases then stabilizes below AT proof time we will not get to equilibrium (long term : deposits will be more, than withdrawals). The deposits and withdrawals will be constant, but different from each other obviously. (Withdrawal < Deposits) Test case name: test_reward_and_fee_if_proof_time_increasing_then_stabilizes_below_the_proof_time_target

    kép

The last commit (where we implement a mechanism, (or option) if we not allow minting, just distributing what we have in treasury is the one i like the most, tho I need to write some test cases.

codecov[bot] commented 1 year ago

Codecov Report

:exclamation: No coverage uploaded for pull request base (major_protocol_upgrade_rebase@4f15527). Click here to learn what that means. The diff coverage is n/a.

@@                       Coverage Diff                        @@
##             major_protocol_upgrade_rebase   #13489   +/-   ##
================================================================
  Coverage                                 ?   39.36%           
================================================================
  Files                                    ?      113           
  Lines                                    ?     3432           
  Branches                                 ?      402           
================================================================
  Hits                                     ?     1351           
  Misses                                   ?     1992           
  Partials                                 ?       89           
Flag Coverage Δ *Carryforward flag
bridge-ui 94.20% <0.00%> (?) Carriedforward from 519dca7
eventindexer ∅ <0.00%> (?) Carriedforward from 519dca7
protocol 0.00% <0.00%> (?)
relayer 62.55% <0.00%> (?)
ui 100.00% <0.00%> (?) Carriedforward from 519dca7

*This pull request uses carry forward flags. Click here to find out more.

:mega: We’re building smart automated test selection to slash your CI/CD build times. Learn more

dantaik commented 1 year ago

@Brechtpd @adaki2004 so if allowMinting is true, is it possible that the supply will go down? When can that happen?

I'm not sure if we should support this config, maybe we should simply allow dynamic minting and burning of tokens.

For useTimeWeightedReward, I'd also prefer that we remove this parameter and use useTimeWeightedReward=true in our design.

Brechtpd commented 1 year ago

@Brechtpd @adaki2004 so if allowMinting is true, is it possible that the supply will go down? When can that happen? I'm not sure if we should support this config, maybe we should simply allow dynamic minting and burning of tokens.

allowMinting == true is basically the same behavior as we had previously so, burning tokens at the proposing side, minting tokens at the reward side. So pretty sure it should be roughly no change in supply.

For useTimeWeightedReward, I'd also prefer that we remove this parameter and use useTimeWeightedReward=true in our design.

I do also think the non time weighted one is very unlikely to work for us, so makes sense to me to remove it already.

adaki2004 commented 1 year ago

@Brechtpd @adaki2004 so if allowMinting is true, is it possible that the supply will go down? When can that happen? I'm not sure if we should support this config, maybe we should simply allow dynamic minting and burning of tokens.

allowMinting == true is basically the same behavior as we had previously so, burning tokens at the proposing side, minting tokens at the reward side. So pretty sure it should be roughly no change in supply.

For useTimeWeightedReward, I'd also prefer that we remove this parameter and use useTimeWeightedReward=true in our design.

I do also think the non time weighted one is very unlikely to work for us, so makes sense to me to remove it already.

@Brechtpd @dantaik Daniel and I had a meeting this morning - and discussed some things, but i'd like to double check with both of you my curent blockers (point 1).

  1. Daniel created with proposed changes this branch + PR: https://github.com/taikoxyz/taiko-mono/pull/13540 Problem with this solution is that in verifyBlock() he uses fc.gasUsed, while in proposeBlock() the input.gasLimit (gasUsed not available at prroposal time). I dont know how to resolve this ATM,

    • If we want to adjust basefee with the gas used (tho it depends highly on the proof time, not the demand for block space (?)) we need to have this information at proposeBlock() ?
    • If we don't want to adjust it according to the demand for blockspace we can use input.gasLimit (need to be re-added to struct Block{}) or we use a constant ?
  2. We agreed to pick 1 algorithm - since we anyhow try one / testnet and i'd go for the time weighted treasury pool one and remove the rest. Reason i'd choose that because the allowMinting version requires us to be punctual with the proofTimeTarget - otherwise we end up in an unbalanced state and maybe kind of frequent updates. Since the test results showed:

    • stable above proofTimeTarget: ever-increasing baseFee (hance fee and reward)
    • stable but below proofTimeTarget: there is a surplus long term because rewards are smaller than deposits. (So we would need to do something with that surplus. Burn or lower the prooTimeTarget, etc.)
    • Only ideal if proofTimeTarget is punctual. Is it fine with you ?
Brechtpd commented 1 year ago

Daniel created with proposed changes this branch + PR: refactor(protocol): recommend changes to Dani's PR (part1) #13540 Problem with this solution is that in verifyBlock() he uses fc.gasUsed, while in proposeBlock() the input.gasLimit (gasUsed not available at prroposal time). I dont know how to resolve this ATM,

If we want to adjust basefee with the gas used (tho it depends highly on the proof time, not the demand for block space (?)) we need to have this information at proposeBlock() ?

If we don't want to adjust it according to the demand for blockspace we can use input.gasLimit (need to be re-added to struct Block{}) or we use a constant ?

Using gasUsed for the reward calculation is a bit problematic I think because it could be 0 (empty block), while the prover obviously still has significant costs (some proof generation, but also L1 gas costs that are independent of how much gas is used in a block). For the same reason the proposer set gas limit also isn't really usable. Best we can do is probably to just use the fixed L2 block gas limit on the proposer side and the reward side.

  1. We agreed to pick 1 algorithm - since we anyhow try one / testnet and i'd go for the time weighted treasury pool one and remove the rest.

That's also the one I like best, if it works of course. :)

Reason i'd choose that because the allowMinting version requires us to be punctual with the proofTimeTarget - otherwise we end up in an unbalanced state and maybe kind of frequent updates. Since the test results showed:

stable above proofTimeTarget: ever-increasing baseFee (hance fee and reward)

Yeah we'd definitely have to make sure that the target is more than enough for a proof to be generated, otherwise it cannot work.

stable but below proofTimeTarget: there is a surplus long term because rewards are smaller than deposits. (So we would need to do something with that surplus. Burn or lower the prooTimeTarget, etc.)

In this case the basefee would going down so people would stop submitting proofs before the target no? Which would then in turn increase the basefee again because proofs are submitted too late.

Only ideal if proofTimeTarget is punctual.

I agree that finding a good target would be a bit tricky, though I think with limited impact even if the value isn't ideal (as long as it's not too short).

dantaik commented 1 year ago

@Brechtpd @adaki2004 so if allowMinting is true, is it possible that the supply will go down? When can that happen? I'm not sure if we should support this config, maybe we should simply allow dynamic minting and burning of tokens.

allowMinting == true is basically the same behavior as we had previously so, burning tokens at the proposing side, minting tokens at the reward side. So pretty sure it should be roughly no change in supply.

For useTimeWeightedReward, I'd also prefer that we remove this parameter and use useTimeWeightedReward=true in our design.

I do also think the non time weighted one is very unlikely to work for us, so makes sense to me to remove it already.

@Brechtpd @dantaik Daniel and I had a meeting this morning - and discussed some things, but i'd like to double check with both of you my curent blockers (point 1).

  1. Daniel created with proposed changes this branch + PR: refactor(protocol): recommend changes to Dani's PR (part1) #13540 Problem with this solution is that in verifyBlock() he uses fc.gasUsed, while in proposeBlock() the input.gasLimit (gasUsed not available at prroposal time). I dont know how to resolve this ATM,
  • If we want to adjust basefee with the gas used (tho it depends highly on the proof time, not the demand for block space (?)) we need to have this information at proposeBlock() ?
  • If we don't want to adjust it according to the demand for blockspace we can use input.gasLimit (need to be re-added to struct Block{}) or we use a constant ?
  1. We agreed to pick 1 algorithm - since we anyhow try one / testnet and i'd go for the time weighted treasury pool one and remove the rest. Reason i'd choose that because the allowMinting version requires us to be punctual with the proofTimeTarget - otherwise we end up in an unbalanced state and maybe kind of frequent updates. Since the test results showed:
  • stable above proofTimeTarget: ever-increasing baseFee (hance fee and reward)
  • stable but below proofTimeTarget: there is a surplus long term because rewards are smaller than deposits. (So we would need to do something with that surplus. Burn or lower the prooTimeTarget, etc.)
  • Only ideal if proofTimeTarget is punctual. Is it fine with you ?

Lets talk about it tomorrow.

adaki2004 commented 1 year ago

@dantaik @Brechtpd

We definitely need to be reasonable with the proofTimeTarget.

How to test possible main net scenario ? 
Remove the ‘x’ prefix from the function names of the TaikoL1LibTokenomicsMainnet.t.sol and use gas setting: 3000M in foundry.toml ->

dantaik commented 1 year ago

@adaki2004 please also fix all the warnings except those related to block.prevrandao.