Skip to content
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

feat: multiplier oracle #9902

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading