Skip to content

Commit

Permalink
feat: multiplier oracle (#9902)
Browse files Browse the repository at this point in the history
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
LHerskind authored Nov 13, 2024
1 parent 96428d1 commit 6456ec9
Show file tree
Hide file tree
Showing 7 changed files with 26,945 additions and 0 deletions.
1 change: 1 addition & 0 deletions l1-contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ fs_permissions = [
{access = "read", path = "./test/fixtures/mixed_block_2.json"},
{access = "read", path = "./test/fixtures/empty_block_1.json"},
{access = "read", path = "./test/fixtures/empty_block_2.json"},
{access = "read", path = "./test/fixtures/fee_data_points.json"}
]

[fmt]
Expand Down
4 changes: 4 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,8 @@ library Errors {
error ProofCommitmentEscrow__InsufficientBalance(uint256 balance, uint256 requested); // 0x09b8b789
error ProofCommitmentEscrow__NotOwner(address caller); // 0x2ac332c1
error ProofCommitmentEscrow__WithdrawRequestNotReady(uint256 current, Timestamp readyAt); // 0xb32ab8a7

// FeeMath
error FeeMath__InvalidProvingCostModifier(); // 0x8b9d62ac
error FeeMath__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad
}
80 changes: 80 additions & 0 deletions l1-contracts/src/core/libraries/FeeMath.sol
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;
}
}
65 changes: 65 additions & 0 deletions l1-contracts/test/fees/FeeModelTestPoints.t.sol
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]);
}
}
}
44 changes: 44 additions & 0 deletions l1-contracts/test/fees/MinimalFeeModel.sol
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);
}
}
69 changes: 69 additions & 0 deletions l1-contracts/test/fees/MinimalFeeModel.t.sol
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)})
);
}
}
Loading

0 comments on commit 6456ec9

Please sign in to comment.