Skip to content

Commit 64a88f0

Browse files
committed
feat: multiplier oracle
1 parent d513099 commit 64a88f0

File tree

7 files changed

+26945
-0
lines changed

7 files changed

+26945
-0
lines changed

l1-contracts/foundry.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fs_permissions = [
1717
{access = "read", path = "./test/fixtures/mixed_block_2.json"},
1818
{access = "read", path = "./test/fixtures/empty_block_1.json"},
1919
{access = "read", path = "./test/fixtures/empty_block_2.json"},
20+
{access = "read", path = "./test/fixtures/fee_data_points.json"}
2021
]
2122

2223
[fmt]

l1-contracts/src/core/libraries/Errors.sol

+4
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,8 @@ library Errors {
109109
error ProofCommitmentEscrow__InsufficientBalance(uint256 balance, uint256 requested); // 0x09b8b789
110110
error ProofCommitmentEscrow__NotOwner(address caller); // 0x2ac332c1
111111
error ProofCommitmentEscrow__WithdrawRequestNotReady(uint256 current, Timestamp readyAt); // 0xb32ab8a7
112+
113+
// FeeMath
114+
error FeeMath__InvalidProvingCostModifier(); // 0x8b9d62ac
115+
error FeeMath__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad
112116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2024 Aztec Labs.
3+
pragma solidity >=0.8.27;
4+
5+
import {Math} from "@oz/utils/math/Math.sol";
6+
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
7+
import {SignedMath} from "@oz/utils/math/SignedMath.sol";
8+
9+
import {Errors} from "./Errors.sol";
10+
11+
struct OracleInput {
12+
int256 provingCostModifier;
13+
int256 feeAssetPriceModifier;
14+
}
15+
16+
library FeeMath {
17+
using Math for uint256;
18+
using SafeCast for int256;
19+
using SafeCast for uint256;
20+
using SignedMath for int256;
21+
22+
// These values are taken from the model, but mostly pulled out of the ass
23+
uint256 internal constant MINIMUM_PROVING_COST_PER_MANA = 5415357955;
24+
uint256 internal constant MAX_PROVING_COST_MODIFIER = 1000000000;
25+
uint256 internal constant PROVING_UPDATE_FRACTION = 100000000000;
26+
27+
uint256 internal constant MINIMUM_FEE_ASSET_PRICE = 10000000000;
28+
uint256 internal constant MAX_FEE_ASSET_PRICE_MODIFIER = 1000000000;
29+
uint256 internal constant FEE_ASSET_PRICE_UPDATE_FRACTION = 100000000000;
30+
31+
function assertValid(OracleInput memory _self) internal pure returns (bool) {
32+
require(
33+
SignedMath.abs(_self.provingCostModifier) <= MAX_PROVING_COST_MODIFIER,
34+
Errors.FeeMath__InvalidProvingCostModifier()
35+
);
36+
require(
37+
SignedMath.abs(_self.feeAssetPriceModifier) <= MAX_FEE_ASSET_PRICE_MODIFIER,
38+
Errors.FeeMath__InvalidFeeAssetPriceModifier()
39+
);
40+
return true;
41+
}
42+
43+
function clampedAdd(uint256 _a, int256 _b) internal pure returns (uint256) {
44+
if (_b >= 0) {
45+
return _a + _b.toUint256();
46+
}
47+
48+
uint256 sub = SignedMath.abs(_b);
49+
50+
if (_a > sub) {
51+
return _a - sub;
52+
}
53+
54+
return 0;
55+
}
56+
57+
function provingCostPerMana(uint256 _numerator) internal pure returns (uint256) {
58+
return fakeExponential(MINIMUM_PROVING_COST_PER_MANA, _numerator, PROVING_UPDATE_FRACTION);
59+
}
60+
61+
function feeAssetPriceModifier(uint256 _numerator) internal pure returns (uint256) {
62+
return fakeExponential(MINIMUM_FEE_ASSET_PRICE, _numerator, FEE_ASSET_PRICE_UPDATE_FRACTION);
63+
}
64+
65+
function fakeExponential(uint256 _factor, uint256 _numerator, uint256 _denominator)
66+
private
67+
pure
68+
returns (uint256)
69+
{
70+
uint256 i = 1;
71+
uint256 output = 0;
72+
uint256 numeratorAccumulator = _factor * _denominator;
73+
while (numeratorAccumulator > 0) {
74+
output += numeratorAccumulator;
75+
numeratorAccumulator = (numeratorAccumulator * _numerator) / (_denominator * i);
76+
i += 1;
77+
}
78+
return output / _denominator;
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2024 Aztec Labs.
3+
// solhint-disable var-name-mixedcase
4+
pragma solidity >=0.8.27;
5+
6+
import {Test} from "forge-std/Test.sol";
7+
8+
// Remember that foundry json parsing is alphabetically done, so you MUST
9+
// sort the struct fields alphabetically or prepare for a headache.
10+
11+
struct L1Fees {
12+
uint256 base_fee;
13+
uint256 blob_fee;
14+
}
15+
16+
struct Header {
17+
uint256 excess_mana;
18+
uint256 fee_asset_price_numerator;
19+
uint256 mana_used;
20+
uint256 proving_cast_per_mana_numerator;
21+
}
22+
23+
struct OracleInput {
24+
int256 fee_asset_price_modifier;
25+
int256 proving_cost_modifier;
26+
}
27+
28+
struct ManaBaseFeeComponents {
29+
uint256 congestion_cost;
30+
uint256 congestion_multiplier;
31+
uint256 data_cost;
32+
uint256 gas_cost;
33+
uint256 proving_cost;
34+
}
35+
36+
struct TestPointOutputs {
37+
uint256 fee_asset_price_at_execution;
38+
ManaBaseFeeComponents mana_base_fee_components_in_fee_asset;
39+
ManaBaseFeeComponents mana_base_fee_components_in_wei;
40+
}
41+
42+
struct TestPoint {
43+
uint256 l1_block_number;
44+
L1Fees l1_fees;
45+
Header header;
46+
OracleInput oracle_input;
47+
TestPointOutputs outputs;
48+
Header parent_header;
49+
}
50+
51+
contract FeeModelTestPoints is Test {
52+
TestPoint[] public points;
53+
54+
constructor() {
55+
string memory root = vm.projectRoot();
56+
string memory path = string.concat(root, "/test/fixtures/fee_data_points.json");
57+
string memory json = vm.readFile(path);
58+
bytes memory jsonBytes = vm.parseJson(json);
59+
TestPoint[] memory dataPoints = abi.decode(jsonBytes, (TestPoint[]));
60+
61+
for (uint256 i = 0; i < dataPoints.length; i++) {
62+
points.push(dataPoints[i]);
63+
}
64+
}
65+
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2024 Aztec Labs.
3+
pragma solidity >=0.8.27;
4+
5+
import {FeeMath, OracleInput} from "@aztec/core/libraries/FeeMath.sol";
6+
7+
contract MinimalFeeModel {
8+
using FeeMath for OracleInput;
9+
using FeeMath for uint256;
10+
11+
struct DataPoint {
12+
uint256 provingCostNumerator;
13+
uint256 feeAssetPriceNumerator;
14+
}
15+
16+
uint256 public populatedThrough = 0;
17+
mapping(uint256 _slotNumber => DataPoint _dataPoint) public dataPoints;
18+
19+
constructor() {
20+
dataPoints[0] = DataPoint({provingCostNumerator: 0, feeAssetPriceNumerator: 0});
21+
}
22+
23+
// See the `add_slot` function in the `fee-model.ipynb` notebook for more context.
24+
function addSlot(OracleInput memory _oracleInput) public {
25+
_oracleInput.assertValid();
26+
27+
DataPoint memory parent = dataPoints[populatedThrough];
28+
29+
dataPoints[++populatedThrough] = DataPoint({
30+
provingCostNumerator: parent.provingCostNumerator.clampedAdd(_oracleInput.provingCostModifier),
31+
feeAssetPriceNumerator: parent.feeAssetPriceNumerator.clampedAdd(
32+
_oracleInput.feeAssetPriceModifier
33+
)
34+
});
35+
}
36+
37+
function getFeeAssetPrice(uint256 _slotNumber) public view returns (uint256) {
38+
return FeeMath.feeAssetPriceModifier(dataPoints[_slotNumber].feeAssetPriceNumerator);
39+
}
40+
41+
function getProvingCost(uint256 _slotNumber) public view returns (uint256) {
42+
return FeeMath.provingCostPerMana(dataPoints[_slotNumber].provingCostNumerator);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.27;
3+
4+
import {OracleInput, FeeMath} from "@aztec/core/libraries/FeeMath.sol";
5+
import {FeeModelTestPoints} from "./FeeModelTestPoints.t.sol";
6+
import {MinimalFeeModel} from "./MinimalFeeModel.sol";
7+
import {Errors} from "@aztec/core/libraries/Errors.sol";
8+
9+
contract MinimalFeeModelTest is FeeModelTestPoints {
10+
MinimalFeeModel internal feeContract;
11+
12+
function setUp() public {
13+
feeContract = new MinimalFeeModel();
14+
15+
for (uint256 i = 0; i < points.length; i++) {
16+
feeContract.addSlot(
17+
OracleInput({
18+
provingCostModifier: points[i].oracle_input.proving_cost_modifier,
19+
feeAssetPriceModifier: points[i].oracle_input.fee_asset_price_modifier
20+
})
21+
);
22+
}
23+
}
24+
25+
function test_computeProvingCost() public {
26+
for (uint256 i = 0; i < points.length; i++) {
27+
assertEq(
28+
feeContract.getProvingCost(i),
29+
points[i].outputs.mana_base_fee_components_in_wei.proving_cost,
30+
"Computed proving cost does not match expected value"
31+
);
32+
}
33+
}
34+
35+
function test_computeFeeAssetPrice() public {
36+
for (uint256 i = 0; i < points.length; i++) {
37+
assertEq(
38+
feeContract.getFeeAssetPrice(i),
39+
points[i].outputs.fee_asset_price_at_execution,
40+
"Computed fee asset price does not match expected value"
41+
);
42+
}
43+
}
44+
45+
function test_invalidOracleInput() public {
46+
uint256 provingBoundary = FeeMath.MAX_PROVING_COST_MODIFIER + 1;
47+
uint256 feeAssetPriceBoundary = FeeMath.MAX_FEE_ASSET_PRICE_MODIFIER + 1;
48+
49+
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidProvingCostModifier.selector));
50+
feeContract.addSlot(
51+
OracleInput({provingCostModifier: int256(provingBoundary), feeAssetPriceModifier: 0})
52+
);
53+
54+
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidProvingCostModifier.selector));
55+
feeContract.addSlot(
56+
OracleInput({provingCostModifier: -int256(provingBoundary), feeAssetPriceModifier: 0})
57+
);
58+
59+
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidFeeAssetPriceModifier.selector));
60+
feeContract.addSlot(
61+
OracleInput({provingCostModifier: 0, feeAssetPriceModifier: int256(feeAssetPriceBoundary)})
62+
);
63+
64+
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidFeeAssetPriceModifier.selector));
65+
feeContract.addSlot(
66+
OracleInput({provingCostModifier: 0, feeAssetPriceModifier: -int256(feeAssetPriceBoundary)})
67+
);
68+
}
69+
}

0 commit comments

Comments
 (0)