-
Notifications
You must be signed in to change notification settings - Fork 272
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #9718. Implements the oracles relying directly on the `fake_exponential` e,g., the proving cost and fee asset cost. Using the data generated from AztecProtocol/engineering-designs#38 and checks that the solidity implementation matches the values from the python. The `MinimalFeeModel` showcase the storage needed and computations, the same logic logic will end up living closer to the rollup contract in what #9804
- Loading branch information
1 parent
916e52a
commit f1aca1b
Showing
7 changed files
with
26,945 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2024 Aztec Labs. | ||
pragma solidity >=0.8.27; | ||
|
||
import {Math} from "@oz/utils/math/Math.sol"; | ||
import {SafeCast} from "@oz/utils/math/SafeCast.sol"; | ||
import {SignedMath} from "@oz/utils/math/SignedMath.sol"; | ||
|
||
import {Errors} from "./Errors.sol"; | ||
|
||
struct OracleInput { | ||
int256 provingCostModifier; | ||
int256 feeAssetPriceModifier; | ||
} | ||
|
||
library FeeMath { | ||
using Math for uint256; | ||
using SafeCast for int256; | ||
using SafeCast for uint256; | ||
using SignedMath for int256; | ||
|
||
// These values are taken from the model, but mostly pulled out of the ass | ||
uint256 internal constant MINIMUM_PROVING_COST_PER_MANA = 5415357955; | ||
uint256 internal constant MAX_PROVING_COST_MODIFIER = 1000000000; | ||
uint256 internal constant PROVING_UPDATE_FRACTION = 100000000000; | ||
|
||
uint256 internal constant MINIMUM_FEE_ASSET_PRICE = 10000000000; | ||
uint256 internal constant MAX_FEE_ASSET_PRICE_MODIFIER = 1000000000; | ||
uint256 internal constant FEE_ASSET_PRICE_UPDATE_FRACTION = 100000000000; | ||
|
||
function assertValid(OracleInput memory _self) internal pure returns (bool) { | ||
require( | ||
SignedMath.abs(_self.provingCostModifier) <= MAX_PROVING_COST_MODIFIER, | ||
Errors.FeeMath__InvalidProvingCostModifier() | ||
); | ||
require( | ||
SignedMath.abs(_self.feeAssetPriceModifier) <= MAX_FEE_ASSET_PRICE_MODIFIER, | ||
Errors.FeeMath__InvalidFeeAssetPriceModifier() | ||
); | ||
return true; | ||
} | ||
|
||
function clampedAdd(uint256 _a, int256 _b) internal pure returns (uint256) { | ||
if (_b >= 0) { | ||
return _a + _b.toUint256(); | ||
} | ||
|
||
uint256 sub = SignedMath.abs(_b); | ||
|
||
if (_a > sub) { | ||
return _a - sub; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
function provingCostPerMana(uint256 _numerator) internal pure returns (uint256) { | ||
return fakeExponential(MINIMUM_PROVING_COST_PER_MANA, _numerator, PROVING_UPDATE_FRACTION); | ||
} | ||
|
||
function feeAssetPriceModifier(uint256 _numerator) internal pure returns (uint256) { | ||
return fakeExponential(MINIMUM_FEE_ASSET_PRICE, _numerator, FEE_ASSET_PRICE_UPDATE_FRACTION); | ||
} | ||
|
||
function fakeExponential(uint256 _factor, uint256 _numerator, uint256 _denominator) | ||
private | ||
pure | ||
returns (uint256) | ||
{ | ||
uint256 i = 1; | ||
uint256 output = 0; | ||
uint256 numeratorAccumulator = _factor * _denominator; | ||
while (numeratorAccumulator > 0) { | ||
output += numeratorAccumulator; | ||
numeratorAccumulator = (numeratorAccumulator * _numerator) / (_denominator * i); | ||
i += 1; | ||
} | ||
return output / _denominator; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2024 Aztec Labs. | ||
// solhint-disable var-name-mixedcase | ||
pragma solidity >=0.8.27; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
|
||
// Remember that foundry json parsing is alphabetically done, so you MUST | ||
// sort the struct fields alphabetically or prepare for a headache. | ||
|
||
struct L1Fees { | ||
uint256 base_fee; | ||
uint256 blob_fee; | ||
} | ||
|
||
struct Header { | ||
uint256 excess_mana; | ||
uint256 fee_asset_price_numerator; | ||
uint256 mana_used; | ||
uint256 proving_cast_per_mana_numerator; | ||
} | ||
|
||
struct OracleInput { | ||
int256 fee_asset_price_modifier; | ||
int256 proving_cost_modifier; | ||
} | ||
|
||
struct ManaBaseFeeComponents { | ||
uint256 congestion_cost; | ||
uint256 congestion_multiplier; | ||
uint256 data_cost; | ||
uint256 gas_cost; | ||
uint256 proving_cost; | ||
} | ||
|
||
struct TestPointOutputs { | ||
uint256 fee_asset_price_at_execution; | ||
ManaBaseFeeComponents mana_base_fee_components_in_fee_asset; | ||
ManaBaseFeeComponents mana_base_fee_components_in_wei; | ||
} | ||
|
||
struct TestPoint { | ||
uint256 l1_block_number; | ||
L1Fees l1_fees; | ||
Header header; | ||
OracleInput oracle_input; | ||
TestPointOutputs outputs; | ||
Header parent_header; | ||
} | ||
|
||
contract FeeModelTestPoints is Test { | ||
TestPoint[] public points; | ||
|
||
constructor() { | ||
string memory root = vm.projectRoot(); | ||
string memory path = string.concat(root, "/test/fixtures/fee_data_points.json"); | ||
string memory json = vm.readFile(path); | ||
bytes memory jsonBytes = vm.parseJson(json); | ||
TestPoint[] memory dataPoints = abi.decode(jsonBytes, (TestPoint[])); | ||
|
||
for (uint256 i = 0; i < dataPoints.length; i++) { | ||
points.push(dataPoints[i]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2024 Aztec Labs. | ||
pragma solidity >=0.8.27; | ||
|
||
import {FeeMath, OracleInput} from "@aztec/core/libraries/FeeMath.sol"; | ||
|
||
contract MinimalFeeModel { | ||
using FeeMath for OracleInput; | ||
using FeeMath for uint256; | ||
|
||
struct DataPoint { | ||
uint256 provingCostNumerator; | ||
uint256 feeAssetPriceNumerator; | ||
} | ||
|
||
uint256 public populatedThrough = 0; | ||
mapping(uint256 _slotNumber => DataPoint _dataPoint) public dataPoints; | ||
|
||
constructor() { | ||
dataPoints[0] = DataPoint({provingCostNumerator: 0, feeAssetPriceNumerator: 0}); | ||
} | ||
|
||
// See the `add_slot` function in the `fee-model.ipynb` notebook for more context. | ||
function addSlot(OracleInput memory _oracleInput) public { | ||
_oracleInput.assertValid(); | ||
|
||
DataPoint memory parent = dataPoints[populatedThrough]; | ||
|
||
dataPoints[++populatedThrough] = DataPoint({ | ||
provingCostNumerator: parent.provingCostNumerator.clampedAdd(_oracleInput.provingCostModifier), | ||
feeAssetPriceNumerator: parent.feeAssetPriceNumerator.clampedAdd( | ||
_oracleInput.feeAssetPriceModifier | ||
) | ||
}); | ||
} | ||
|
||
function getFeeAssetPrice(uint256 _slotNumber) public view returns (uint256) { | ||
return FeeMath.feeAssetPriceModifier(dataPoints[_slotNumber].feeAssetPriceNumerator); | ||
} | ||
|
||
function getProvingCost(uint256 _slotNumber) public view returns (uint256) { | ||
return FeeMath.provingCostPerMana(dataPoints[_slotNumber].provingCostNumerator); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.27; | ||
|
||
import {OracleInput, FeeMath} from "@aztec/core/libraries/FeeMath.sol"; | ||
import {FeeModelTestPoints} from "./FeeModelTestPoints.t.sol"; | ||
import {MinimalFeeModel} from "./MinimalFeeModel.sol"; | ||
import {Errors} from "@aztec/core/libraries/Errors.sol"; | ||
|
||
contract MinimalFeeModelTest is FeeModelTestPoints { | ||
MinimalFeeModel internal feeContract; | ||
|
||
function setUp() public { | ||
feeContract = new MinimalFeeModel(); | ||
|
||
for (uint256 i = 0; i < points.length; i++) { | ||
feeContract.addSlot( | ||
OracleInput({ | ||
provingCostModifier: points[i].oracle_input.proving_cost_modifier, | ||
feeAssetPriceModifier: points[i].oracle_input.fee_asset_price_modifier | ||
}) | ||
); | ||
} | ||
} | ||
|
||
function test_computeProvingCost() public { | ||
for (uint256 i = 0; i < points.length; i++) { | ||
assertEq( | ||
feeContract.getProvingCost(i), | ||
points[i].outputs.mana_base_fee_components_in_wei.proving_cost, | ||
"Computed proving cost does not match expected value" | ||
); | ||
} | ||
} | ||
|
||
function test_computeFeeAssetPrice() public { | ||
for (uint256 i = 0; i < points.length; i++) { | ||
assertEq( | ||
feeContract.getFeeAssetPrice(i), | ||
points[i].outputs.fee_asset_price_at_execution, | ||
"Computed fee asset price does not match expected value" | ||
); | ||
} | ||
} | ||
|
||
function test_invalidOracleInput() public { | ||
uint256 provingBoundary = FeeMath.MAX_PROVING_COST_MODIFIER + 1; | ||
uint256 feeAssetPriceBoundary = FeeMath.MAX_FEE_ASSET_PRICE_MODIFIER + 1; | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidProvingCostModifier.selector)); | ||
feeContract.addSlot( | ||
OracleInput({provingCostModifier: int256(provingBoundary), feeAssetPriceModifier: 0}) | ||
); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidProvingCostModifier.selector)); | ||
feeContract.addSlot( | ||
OracleInput({provingCostModifier: -int256(provingBoundary), feeAssetPriceModifier: 0}) | ||
); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidFeeAssetPriceModifier.selector)); | ||
feeContract.addSlot( | ||
OracleInput({provingCostModifier: 0, feeAssetPriceModifier: int256(feeAssetPriceBoundary)}) | ||
); | ||
|
||
vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidFeeAssetPriceModifier.selector)); | ||
feeContract.addSlot( | ||
OracleInput({provingCostModifier: 0, feeAssetPriceModifier: -int256(feeAssetPriceBoundary)}) | ||
); | ||
} | ||
} |
Oops, something went wrong.