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: make surcharges settable #461

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 3 additions & 6 deletions src/contracts/atlas/AtlETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,7 @@ abstract contract AtlETH is Permit69 {
/// @dev This function can only be called by the current surcharge recipient.
/// It transfers the accumulated surcharge amount to the surcharge recipient's address.
function withdrawSurcharge() external {
if (msg.sender != S_surchargeRecipient) {
revert InvalidAccess();
}
_onlySurchargeRecipient();

uint256 _paymentAmount = S_cumulativeSurcharge;
S_cumulativeSurcharge = 0; // Clear before transfer to prevent reentrancy
Expand All @@ -268,10 +266,9 @@ abstract contract AtlETH is Permit69 {
/// If the caller is not the current surcharge recipient, it reverts with an `InvalidAccess` error.
/// @param newRecipient The address of the new surcharge recipient.
function transferSurchargeRecipient(address newRecipient) external {
_onlySurchargeRecipient();

address _surchargeRecipient = S_surchargeRecipient;
if (msg.sender != _surchargeRecipient) {
revert InvalidAccess();
}

S_pendingSurchargeRecipient = newRecipient;
emit SurchargeRecipientTransferStarted(_surchargeRecipient, newRecipient);
Expand Down
35 changes: 18 additions & 17 deletions src/contracts/atlas/GasAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ abstract contract GasAccounting is SafetyLocks {
/// @param gasMarker The gas marker used to calculate the initial accounting values.
function _initializeAccountingValues(uint256 gasMarker) internal {
uint256 _rawClaims = (FIXED_GAS_OFFSET + gasMarker) * tx.gasprice;
(uint256 _atlasSurchargeRate, uint256 _bundlerSurchargeRate) = _surchargeRates();

// Set any withdraws or deposits
t_claims = _rawClaims.withSurcharge(BUNDLER_SURCHARGE_RATE);
t_claims = _rawClaims.withSurcharge(_bundlerSurchargeRate);

// Atlas surcharge is based on the raw claims value.
t_fees = _rawClaims.getSurcharge(ATLAS_SURCHARGE_RATE);
t_fees = _rawClaims.getSurcharge(_atlasSurchargeRate);
t_deposits = msg.value;

// Explicitly set other transient vars to 0 in case multiple metacalls in single tx.
Expand Down Expand Up @@ -278,6 +279,7 @@ abstract contract GasAccounting is SafetyLocks {
internal
{
uint256 _gasUsed = (gasWaterMark + _SOLVER_BASE_GAS_USED - gasleft()) * tx.gasprice;
(uint256 _atlasSurchargeRate, uint256 _bundlerSurchargeRate) = _surchargeRates();

if (includeCalldata) {
_gasUsed += _getCalldataCost(solverOp.data.length);
Expand All @@ -288,10 +290,10 @@ abstract contract GasAccounting is SafetyLocks {
if (result.bundlersFault()) {
// CASE: Solver is not responsible for the failure of their operation, so we blame the bundler
// and reduce the total amount refunded to the bundler
t_writeoffs += _gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE);
t_writeoffs += _gasUsed.withSurcharges(_atlasSurchargeRate, _bundlerSurchargeRate);
} else {
// CASE: Solver failed, so we calculate what they owe.
uint256 _gasUsedWithSurcharges = _gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE);
uint256 _gasUsedWithSurcharges = _gasUsed.withSurcharges(_atlasSurchargeRate, _bundlerSurchargeRate);
uint256 _surchargesOnly = _gasUsedWithSurcharges - _gasUsed;

// In `_assign()`, the failing solver's bonded AtlETH balance is reduced by `_gasUsedWithSurcharges`. Any
Expand All @@ -309,13 +311,13 @@ abstract contract GasAccounting is SafetyLocks {
}

function _writeOffBidFindGasCost(uint256 gasUsed) internal {
t_writeoffs += gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE);
(uint256 _atlasSurchargeRate, uint256 _bundlerSurchargeRate) = _surchargeRates();
t_writeoffs += gasUsed.withSurcharges(_atlasSurchargeRate, _bundlerSurchargeRate);
}

/// @param ctx Context struct containing relevant context information for the Atlas auction.
/// @param solverGasLimit The maximum gas limit for a solver, as set in the DAppConfig
/// @return adjustedWithdrawals Withdrawals of the current metacall, adjusted by adding the Atlas gas surcharge.
/// @return adjustedDeposits Deposits of the current metacall, no adjustments applied.
/// @return adjustedClaims Claims of the current metacall, adjusted by subtracting the unused gas scaled to include
/// bundler surcharge.
/// @return adjustedWriteoffs Writeoffs of the current metacall, adjusted by adding the bundler gas overage penalty
Expand All @@ -331,7 +333,6 @@ abstract contract GasAccounting is SafetyLocks {
internal
returns (
uint256 adjustedWithdrawals,
uint256 adjustedDeposits,
uint256 adjustedClaims,
uint256 adjustedWriteoffs,
uint256 netAtlasGasSurcharge
Expand All @@ -340,55 +341,55 @@ abstract contract GasAccounting is SafetyLocks {
uint256 _surcharge = S_cumulativeSurcharge;

adjustedWithdrawals = t_withdrawals;
adjustedDeposits = t_deposits;
adjustedClaims = t_claims;
adjustedWriteoffs = t_writeoffs;
uint256 _fees = t_fees;
(uint256 _atlasSurchargeRate, uint256 _bundlerSurchargeRate) = _surchargeRates();

uint256 _gasLeft = gasleft(); // Hold this constant for the calculations

// Estimate the unspent, remaining gas that the Solver will not be liable for.
uint256 _gasRemainder = _gasLeft * tx.gasprice;

adjustedClaims -= _gasRemainder.withSurcharge(BUNDLER_SURCHARGE_RATE);
adjustedClaims -= _gasRemainder.withSurcharge(_bundlerSurchargeRate);

if (ctx.solverSuccessful) {
// If a solver was successful, calc the full Atlas gas surcharge on the gas cost of the entire metacall, and
// add it to withdrawals so that the cost is assigned to winning solver by the end of _settle(). This will
// be offset by any gas surcharge paid by failed solvers, which would have been added to deposits or
// writeoffs in _handleSolverAccounting(). As such, the winning solver does not pay for surcharge on the gas
// used by other solvers.
netAtlasGasSurcharge = _fees - _gasRemainder.getSurcharge(ATLAS_SURCHARGE_RATE);
netAtlasGasSurcharge = _fees - _gasRemainder.getSurcharge(_atlasSurchargeRate);
adjustedWithdrawals += netAtlasGasSurcharge;
S_cumulativeSurcharge = _surcharge + netAtlasGasSurcharge;
} else {
// If no successful solvers, only collect partial surcharges from solver's fault failures (if any)
uint256 _solverSurcharge = t_solverSurcharge;
if (_solverSurcharge > 0) {
netAtlasGasSurcharge = _solverSurcharge.getPortionFromTotalSurcharge({
targetSurchargeRate: ATLAS_SURCHARGE_RATE,
totalSurchargeRate: ATLAS_SURCHARGE_RATE + BUNDLER_SURCHARGE_RATE
targetSurchargeRate: _atlasSurchargeRate,
totalSurchargeRate: _atlasSurchargeRate + _bundlerSurchargeRate
});

// When no winning solvers, bundler max refund is 80% of metacall gas cost. The remaining 20% can be
// collected through storage refunds. Any excess bundler surcharge is instead taken as Atlas surcharge.
uint256 _bundlerSurcharge = _solverSurcharge - netAtlasGasSurcharge;
uint256 _maxBundlerRefund = adjustedClaims.withoutSurcharge(BUNDLER_SURCHARGE_RATE).maxBundlerRefund();
uint256 _maxBundlerRefund = adjustedClaims.withoutSurcharge(_bundlerSurchargeRate).maxBundlerRefund();
if (_bundlerSurcharge > _maxBundlerRefund) {
netAtlasGasSurcharge += _bundlerSurcharge - _maxBundlerRefund;
}

adjustedWithdrawals += netAtlasGasSurcharge;
S_cumulativeSurcharge = _surcharge + netAtlasGasSurcharge;
}
return (adjustedWithdrawals, adjustedDeposits, adjustedClaims, adjustedWriteoffs, netAtlasGasSurcharge);
return (adjustedWithdrawals, adjustedClaims, adjustedWriteoffs, netAtlasGasSurcharge);
}

// Calculate whether or not the bundler used an excessive amount of gas and, if so, reduce their
// gas rebate. By reducing the claims, solvers end up paying less in total.
if (ctx.solverCount > 0) {
// Calculate the unadjusted bundler gas surcharge
uint256 _grossBundlerGasSurcharge = adjustedClaims.withoutSurcharge(BUNDLER_SURCHARGE_RATE);
uint256 _grossBundlerGasSurcharge = adjustedClaims.withoutSurcharge(_bundlerSurchargeRate);

// Calculate an estimate for how much gas should be remaining
// NOTE: There is a free buffer of one SolverOperation because solverIndex starts at 0.
Expand Down Expand Up @@ -435,9 +436,9 @@ abstract contract GasAccounting is SafetyLocks {
uint256 _claims;
uint256 _writeoffs;
uint256 _withdrawals;
uint256 _deposits;
uint256 _deposits = t_deposits; // load here, not used in adjustment function below

(_withdrawals, _deposits, _claims, _writeoffs, netAtlasGasSurcharge) =
(_withdrawals, _claims, _writeoffs, netAtlasGasSurcharge) =
_adjustAccountingForFees(ctx, solverGasLimit);

uint256 _amountSolverPays;
Expand Down
64 changes: 53 additions & 11 deletions src/contracts/atlas/Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants {
address public immutable SIMULATOR;
address public immutable L2_GAS_CALCULATOR;
uint256 public immutable ESCROW_DURATION;
uint256 public immutable ATLAS_SURCHARGE_RATE;
uint256 public immutable BUNDLER_SURCHARGE_RATE;

// AtlETH public constants
// These constants double as interface functions for the ERC20 standard, hence the lowercase naming convention.
Expand Down Expand Up @@ -48,15 +46,16 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants {
uint256 internal S_totalSupply;
uint256 internal S_bondedTotalSupply;

mapping(address => EscrowAccountBalance) internal s_balanceOf; // public balanceOf will return a uint256
mapping(address => EscrowAccountAccessData) internal S_accessData;
mapping(bytes32 => bool) internal S_solverOpHashes; // NOTE: Only used for when allowTrustedOpHash is enabled

// atlETH GasAccounting storage
// Surcharge-related storage
uint256 internal S_surchargeRates; // left 128 bits: Atlas rate, right 128 bits: Bundler rate
uint256 internal S_cumulativeSurcharge; // Cumulative gas surcharges collected
address internal S_surchargeRecipient; // Fastlane surcharge recipient
address internal S_pendingSurchargeRecipient; // For 2-step transfer process

mapping(address => EscrowAccountBalance) internal s_balanceOf; // public balanceOf will return a uint256
mapping(address => EscrowAccountAccessData) internal S_accessData;
mapping(bytes32 => bool) internal S_solverOpHashes; // NOTE: Only used for when allowTrustedOpHash is enabled

constructor(
uint256 escrowDuration,
uint256 atlasSurchargeRate,
Expand All @@ -72,17 +71,40 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants {
SIMULATOR = simulator;
L2_GAS_CALCULATOR = l2GasCalculator;
ESCROW_DURATION = escrowDuration;
ATLAS_SURCHARGE_RATE = atlasSurchargeRate;
BUNDLER_SURCHARGE_RATE = bundlerSurchargeRate;

// Gas Accounting
// Initialized with msg.value to seed flash loan liquidity
// Check Atlas and Bundler gas surcharges fit in 128 bits each
if(atlasSurchargeRate > type(uint128).max || bundlerSurchargeRate > type(uint128).max) {
revert SurchargeRateTooHigh();
}

S_surchargeRates = atlasSurchargeRate << 128 | bundlerSurchargeRate;
S_cumulativeSurcharge = msg.value;
S_surchargeRecipient = initialSurchargeRecipient;

emit SurchargeRecipientTransferred(initialSurchargeRecipient);
}

// ---------------------------------------------------- //
// Storage Setters //
// ---------------------------------------------------- //

function setSurchargeRates(uint256 newAtlasRate, uint256 newBundlerRate) external {
_onlySurchargeRecipient();

// Check Atlas and Bundler gas surcharges fit in 128 bits each
if(newAtlasRate > type(uint128).max || newBundlerRate > type(uint128).max) {
revert SurchargeRateTooHigh();
}

S_surchargeRates = newAtlasRate << 128 | newBundlerRate;
}

function _onlySurchargeRecipient() internal view {
if (msg.sender != S_surchargeRecipient) {
revert InvalidAccess();
}
}

// ---------------------------------------------------- //
// Storage Getters //
// ---------------------------------------------------- //
Expand Down Expand Up @@ -130,6 +152,26 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants {
return S_pendingSurchargeRecipient;
}

function atlasSurchargeRate() external view returns (uint256) {
// Includes only the left 128 bits to isolate the Atlas rate
return S_surchargeRates >> 128;
}

function bundlerSurchargeRate() external view returns (uint256) {
// Includes only the right 128 bits to isolate the bundler rate
return S_surchargeRates & ((1 << 128) - 1);
}

// ---------------------------------------------------- //
// Storage Internal Getters //
// ---------------------------------------------------- //

function _surchargeRates() internal view returns (uint256 atlasRate, uint256 bundlerRate) {
uint256 _bothRates = S_surchargeRates;
atlasRate = _bothRates >> 128;
bundlerRate = _bothRates & ((1 << 128) - 1);
}

// ---------------------------------------------------- //
// Transient External Getters //
// ---------------------------------------------------- //
Expand Down
4 changes: 4 additions & 0 deletions src/contracts/interfaces/IAtlas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ interface IAtlas {
function depositAndBond(uint256 amountToBond) external payable;
function unbond(uint256 amount) external;
function redeem(uint256 amount) external;

function withdrawSurcharge() external;
function transferSurchargeRecipient(address newRecipient) external;
function becomeSurchargeRecipient() external;
function setSurchargeRates(uint128 newAtlasRate, uint128 newBundlerRate) external;

// Permit69.sol
function transferUserERC20(
Expand Down Expand Up @@ -101,4 +103,6 @@ interface IAtlas {
function cumulativeSurcharge() external view returns (uint256);
function surchargeRecipient() external view returns (address);
function pendingSurchargeRecipient() external view returns (address);
function atlasSurchargeRate() external view returns (uint256);
function bundlerSurchargeRate() external view returns (uint256);
}
3 changes: 3 additions & 0 deletions src/contracts/types/AtlasErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ contract AtlasErrors {
error NotInitialized();
error AlreadyInitialized();

// Storage
error SurchargeRateTooHigh();

// AtlasVerification
error NoUnusedNonceInBitmap();

Expand Down
2 changes: 1 addition & 1 deletion test/FLOnline.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,7 @@ contract FastLaneOnlineTest is BaseTest {

// Calculate estimated Atlas gas surcharge taken from call above
txGasUsed = estAtlasGasSurcharge - gasleft();
estAtlasGasSurcharge = txGasUsed * defaultGasPrice * atlas.ATLAS_SURCHARGE_RATE() / atlas.SCALE();
estAtlasGasSurcharge = txGasUsed * defaultGasPrice * atlas.atlasSurchargeRate() / atlas.SCALE();

assertTrue(
result == swapCallShouldSucceed,
Expand Down
5 changes: 2 additions & 3 deletions test/GasAccounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ contract MockGasAccounting is TestAtlas, BaseTest {
external
returns (
uint256 adjustedWithdrawals,
uint256 adjustedDeposits,
uint256 adjustedClaims,
uint256 adjustedWriteoffs,
uint256 netAtlasGasSurcharge
Expand Down Expand Up @@ -281,8 +280,8 @@ contract GasAccountingTest is AtlasConstants, BaseTest {
uint256 rawClaims = (_gasMarker + mockGasAccounting.FIXED_GAS_OFFSET()) * tx.gasprice;
claims = rawClaims
* (
mockGasAccounting.SCALE() + mockGasAccounting.ATLAS_SURCHARGE_RATE()
+ mockGasAccounting.BUNDLER_SURCHARGE_RATE()
mockGasAccounting.SCALE() + mockGasAccounting.atlasSurchargeRate()
+ mockGasAccounting.bundlerSurchargeRate()
) / mockGasAccounting.SCALE();
}

Expand Down
42 changes: 38 additions & 4 deletions test/Storage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "forge-std/Test.sol";

import { Storage } from "../src/contracts/atlas/Storage.sol";
import "../src/contracts/types/LockTypes.sol";
import { AtlasErrors } from "../src/contracts/types/AtlasErrors.sol";

import { BaseTest } from "./base/BaseTest.t.sol";

Expand All @@ -29,10 +30,6 @@ contract StorageTest is BaseTest {
assertEq(atlas.symbol(), "atlETH", "symbol set incorrectly");
assertEq(atlas.decimals(), 18, "decimals set incorrectly");

assertEq(atlas.ATLAS_SURCHARGE_RATE(), DEFAULT_ATLAS_SURCHARGE_RATE, "ATLAS_SURCHARGE_RATE set incorrectly");
assertEq(
atlas.BUNDLER_SURCHARGE_RATE(), DEFAULT_BUNDLER_SURCHARGE_RATE, "BUNDLER_SURCHARGE_RATE set incorrectly"
);
assertEq(atlas.SCALE(), DEFAULT_SCALE, "SCALE set incorrectly");
assertEq(atlas.FIXED_GAS_OFFSET(), DEFAULT_FIXED_GAS_OFFSET, "FIXED_GAS_OFFSET set incorrectly");
}
Expand Down Expand Up @@ -143,6 +140,43 @@ contract StorageTest is BaseTest {
assertEq(atlas.pendingSurchargeRecipient(), userEOA, "pendingSurchargeRecipient should be userEOA");
}

function test_storage_view_atlasSurchargeRate() public {
assertEq(atlas.atlasSurchargeRate(), DEFAULT_ATLAS_SURCHARGE_RATE, "atlasSurchargeRate set incorrectly");
vm.prank(deployer);
atlas.setSurchargeRates(100, 200);
assertEq(atlas.atlasSurchargeRate(), 100, "atlasSurchargeRate set incorrectly");
}

function test_storage_view_bundlerSurchargeRate() public {
assertEq(atlas.bundlerSurchargeRate(), DEFAULT_BUNDLER_SURCHARGE_RATE, "bundlerSurchargeRate set incorrectly");
vm.prank(deployer);
atlas.setSurchargeRates(100, 200);
assertEq(atlas.bundlerSurchargeRate(), 200, "bundlerSurchargeRate set incorrectly");
}

// Storage Setters

function test_storage_setSurchargeRates() public {
uint256 tooHigh = uint256(type(uint128).max) + 1;

vm.prank(deployer);
vm.expectRevert(AtlasErrors.SurchargeRateTooHigh.selector);
atlas.setSurchargeRates(tooHigh, 456);

vm.prank(deployer);
vm.expectRevert(AtlasErrors.SurchargeRateTooHigh.selector);
atlas.setSurchargeRates(123, tooHigh);

vm.prank(userEOA);
vm.expectRevert(AtlasErrors.InvalidAccess.selector);
atlas.setSurchargeRates(123, 456);

vm.prank(deployer);
atlas.setSurchargeRates(123, 456);
assertEq(atlas.atlasSurchargeRate(), 123, "atlasSurchargeRate set incorrectly");
assertEq(atlas.bundlerSurchargeRate(), 456, "bundlerSurchargeRate set incorrectly");
}

// Transient Storage Getters and Setters

function test_storage_transient_lock() public {
Expand Down
2 changes: 1 addition & 1 deletion test/TrebleSwap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ contract TrebleSwapTest is BaseTest {

// Estimate gas surcharge Atlas should have taken
txGasUsed = estAtlasGasSurcharge - gasleft();
estAtlasGasSurcharge = txGasUsed * tx.gasprice * atlas.ATLAS_SURCHARGE_RATE() / atlas.SCALE();
estAtlasGasSurcharge = txGasUsed * tx.gasprice * atlas.atlasSurchargeRate() / atlas.SCALE();

// For benchmarking
console.log("Metacall gas cost: ", txGasUsed);
Expand Down
Loading