-
Notifications
You must be signed in to change notification settings - Fork 807
Implement ACP-77 fee calculations #3367
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
Changes from all commits
1857cb9
4d11a29
90a38ff
f60e4ea
1ca0caf
1cd2428
3587c32
ec16c11
f52cbbe
076b1f0
df5ca64
dcc056b
3279764
911a167
ddf3392
25802a8
01b854a
f79e0c7
1eda29c
3fa1306
16ffe7b
3035064
62ece94
333a41e
520ac91
db5f197
574a120
501beb1
ab5c23b
e0d0a00
12a1969
cda1001
2ff46f4
e05a514
7ab0094
79e84b1
264a73f
1340a51
62f6d78
6e54ba7
68e621d
8e9f406
dd08695
3042de2
9b8aa4a
1d72e1b
5126df9
909a24c
c1299c8
a2d0968
aa6b6f6
be79d4c
367cf42
a14f140
eed7d1a
30240fc
9fe5414
413a7a9
c988391
3f49097
447e326
b23b654
952fd7f
db18027
e019360
43b2b53
fe4f3d6
ddf94a4
4a5fecb
643ddbd
215821f
bdc015d
d1652c0
4fb9e17
2555a6a
751bdea
1c91b02
82ce5d0
4c2205e
0ba141d
e537d23
f18163b
35e0580
a61fb06
b42de99
4a1ca5c
0b15984
99378b4
1684fdb
22ac121
a8524ba
b5df28b
bed839d
4adc17e
ffeb3d3
37cbc52
4edc421
67e2bef
63d5ac7
ece7c2b
92a7187
144dad2
6cc89e1
1503bd4
7dd74b5
6a0db3f
4afac28
5545958
41127c9
8b54f2f
13238f1
fdc1069
57dc1be
85ce497
33e7126
15473d5
58eebf4
f7a5558
f33f9a7
499f19c
dfaff2e
6b26b49
aeddd28
3dcdcda
64a6c53
ad664a0
186cf8d
de97114
40a2cae
b0ce060
0ecfc50
06534fc
a44b68a
68a6e24
61640da
1619e79
f536353
f6339b5
91338cb
f602280
c3b076b
4328bac
805ae43
518cf19
26b3702
e1f55a5
8895f27
aa7f222
91c8619
b5638e0
4831205
df3fb4c
8f8ebc9
d87a472
ff95e5b
7aee249
1ca22eb
bd52982
a0af420
2bc3f86
1a8e5f2
29af8da
77a9820
471871f
a490641
62a2ae1
c780c34
5af7744
471b03e
8e468ab
7940741
d642d1d
605aa61
e5ef65d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package fee | ||
|
||
import ( | ||
"math" | ||
|
||
"github.com/ava-labs/avalanchego/vms/components/gas" | ||
|
||
safemath "github.com/ava-labs/avalanchego/utils/math" | ||
) | ||
|
||
// Config contains all the static parameters of the dynamic fee mechanism. | ||
type Config struct { | ||
Target gas.Gas `json:"target"` | ||
MinPrice gas.Price `json:"minPrice"` | ||
ExcessConversionConstant gas.Gas `json:"excessConversionConstant"` | ||
} | ||
|
||
// State represents the current on-chain values used in the dynamic fee | ||
// mechanism. | ||
type State struct { | ||
Current gas.Gas `json:"current"` | ||
Excess gas.Gas `json:"excess"` | ||
} | ||
|
||
// AdvanceTime adds (s.Current - target) * seconds to Excess. | ||
// | ||
// If Excess would underflow, it is set to 0. | ||
// If Excess would overflow, it is set to MaxUint64. | ||
func (s State) AdvanceTime(target gas.Gas, seconds uint64) State { | ||
excess := s.Excess | ||
if s.Current < target { | ||
excess = excess.SubPerSecond(target-s.Current, seconds) | ||
} else if s.Current > target { | ||
excess = excess.AddPerSecond(s.Current-target, seconds) | ||
} | ||
return State{ | ||
Current: s.Current, | ||
Excess: excess, | ||
} | ||
} | ||
|
||
// CostOf calculates how much to charge based on the dynamic fee mechanism for | ||
// [seconds]. | ||
// | ||
// This implements the ACP-77 cost over time formula: | ||
func (s State) CostOf(c Config, seconds uint64) uint64 { | ||
// If the current and target are the same, the price is constant. | ||
if s.Current == c.Target { | ||
price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) | ||
cost, err := safemath.Mul(seconds, uint64(price)) | ||
if err != nil { | ||
return math.MaxUint64 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll see your error and raise you an exorbitant transaction cost! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually a discount! (because this is just capping the cost at |
||
} | ||
return cost | ||
} | ||
|
||
var ( | ||
cost uint64 | ||
err error | ||
) | ||
for i := uint64(0); i < seconds; i++ { | ||
s = s.AdvanceTime(c.Target, 1) | ||
|
||
// Advancing the time is going to either hold excess constant, | ||
// monotonically increase it, or monotonically decrease it. If it is | ||
// equal to 0 after performing one of these operations, it is guaranteed | ||
// to always remain 0. | ||
if s.Excess == 0 { | ||
secondsWithZeroExcess := seconds - i | ||
zeroExcessCost, err := safemath.Mul(uint64(c.MinPrice), secondsWithZeroExcess) | ||
if err != nil { | ||
return math.MaxUint64 | ||
} | ||
|
||
cost, err = safemath.Add(cost, zeroExcessCost) | ||
if err != nil { | ||
return math.MaxUint64 | ||
} | ||
return cost | ||
} | ||
|
||
price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) | ||
cost, err = safemath.Add(cost, uint64(price)) | ||
if err != nil { | ||
return math.MaxUint64 | ||
} | ||
} | ||
return cost | ||
} | ||
|
||
// SecondsUntil calculates the number of seconds that it would take to charge at | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice naming! I love code that reads like prose. |
||
// least [targetCost] based on the dynamic fee mechanism. The result is capped | ||
// at [maxSeconds]. | ||
func (s State) SecondsUntil(c Config, maxSeconds uint64, targetCost uint64) uint64 { | ||
// Because this function can divide by prices, we need to sanity check the | ||
// parameters to avoid division by 0. | ||
if c.MinPrice == 0 { | ||
if targetCost == 0 { | ||
return 0 | ||
} | ||
return maxSeconds | ||
} | ||
|
||
// If the current and target are the same, the price is constant. | ||
if s.Current == c.Target { | ||
price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) | ||
return secondsUntil( | ||
uint64(price), | ||
maxSeconds, | ||
targetCost, | ||
) | ||
} | ||
|
||
var ( | ||
cost uint64 | ||
seconds uint64 | ||
err error | ||
) | ||
for cost < targetCost && seconds < maxSeconds { | ||
s = s.AdvanceTime(c.Target, 1) | ||
|
||
// Advancing the time is going to either hold excess constant, | ||
// monotonically increase it, or monotonically decrease it. If it is | ||
// equal to 0 after performing one of these operations, it is guaranteed | ||
// to always remain 0. | ||
if s.Excess == 0 { | ||
zeroExcessCost := targetCost - cost | ||
secondsWithZeroExcess := secondsUntil( | ||
uint64(c.MinPrice), | ||
maxSeconds, | ||
zeroExcessCost, | ||
) | ||
|
||
totalSeconds, err := safemath.Add(seconds, secondsWithZeroExcess) | ||
if err != nil || totalSeconds >= maxSeconds { | ||
return maxSeconds | ||
} | ||
return totalSeconds | ||
} | ||
|
||
seconds++ | ||
price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) | ||
cost, err = safemath.Add(cost, uint64(price)) | ||
if err != nil { | ||
return seconds | ||
} | ||
} | ||
return seconds | ||
} | ||
|
||
// Calculate the number of seconds that it would take to charge at least [cost] | ||
// at [price] every second. The result is capped at [maxSeconds]. | ||
func secondsUntil(price uint64, maxSeconds uint64, cost uint64) uint64 { | ||
// Directly rounding up could cause an overflow. Instead we round down and | ||
// then check if we should have rounded up. | ||
secondsRoundedDown := cost / price | ||
if secondsRoundedDown >= maxSeconds { | ||
return maxSeconds | ||
} | ||
if cost%price == 0 { | ||
return secondsRoundedDown | ||
} | ||
return secondsRoundedDown + 1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this because we were talking about functional languages? 🤣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually because we might be passing these through different block states... And we definitely don't want them to be mutable there