Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
83 changes: 83 additions & 0 deletions src/chain/ResourceConstraintManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ contract ResourceConstraintManager is AccessControlEnumerable {
ArbOwner internal constant ARB_OWNER = ArbOwner(address(0x70));
ArbGasInfo internal constant ARB_GAS_INFO = ArbGasInfo(address(0x6c));
Comment thread
gzeoneth marked this conversation as resolved.

// Constraint parameters boundaries
uint256 public constant MAX_CONSTRAINTS = 10;
Comment thread
TucksonDev marked this conversation as resolved.
Outdated
uint64 public constant MIN_GAS_TARGET_PER_SEC = 7_000_000;
uint64 public constant MAX_GAS_TARGET_PER_SEC = 100_000_000;
uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5;
uint32 public constant MAX_ADJUSTMENT_WINDOW_SECS = 86400;
uint64 public constant MAX_PRICING_EXPONENT = 8000; // scaled by 1000 to allow for fractional exponents

bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
uint256 public expiryTimestamp;

Expand Down Expand Up @@ -85,4 +93,79 @@ contract ResourceConstraintManager is AccessControlEnumerable {

ARB_OWNER.setGasPricingConstraints(constraints);
}

/// @notice Sets the list of multi-gas pricing constraints for the multi-dimensional multi-constraint pricing model.
/// See ArbOwner.setMultiGasPricingConstraints interface for more information.
/// @param constraints Array of ResourceConstraint structs, each containing:
/// - resources: list of (ResourceKind, weight) pairs
/// - adjustmentWindowSecs: time window (seconds) over which the price will rise by a factor of e if demand is 2x the target (uint32, seconds)
/// - targetPerSec: target gas usage per second for this constraint (uint64, gas/sec)
/// - backlog: initial backlog value for this constraint (uint64, gas units)
function setMultiGasPricingConstraints(
ArbMultiGasConstraintsTypes.ResourceConstraint[] calldata constraints
) external onlyRole(MANAGER_ROLE) {
// If zero constraints are provided, the chain uses the single-constraint pricing model
// Starting from ArbOS 60, there's no limit to the number of constraints to set
uint256 nConstraints = constraints.length;

// We calculate the implied pricing exponent for each resource kind
uint8 numResourceKinds = uint8(type(ArbMultiGasConstraintsTypes.ResourceKind).max) + 1;
uint64[] memory pricingExponents = new uint64[](numResourceKinds);
for (uint256 i = 0; i < nConstraints; ++i) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe also check weight != 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it doesn't matter if a resource has a weight of 0, if I understand the nitro code correctly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary, but setting weight to 0 is probably a mistake (can be omitted instead) so good to check here

uint64 targetPerSec = constraints[i].targetPerSec;
uint32 adjustmentWindowSecs = constraints[i].adjustmentWindowSecs;
uint64 startingBacklogValue = constraints[i].backlog;
if (targetPerSec < MIN_GAS_TARGET_PER_SEC || targetPerSec > MAX_GAS_TARGET_PER_SEC) {
revert InvalidTarget(targetPerSec, adjustmentWindowSecs, startingBacklogValue);
}
if (
adjustmentWindowSecs < MIN_ADJUSTMENT_WINDOW_SECS
|| adjustmentWindowSecs > MAX_ADJUSTMENT_WINDOW_SECS
Comment thread
TucksonDev marked this conversation as resolved.
) {
revert InvalidPeriod(targetPerSec, adjustmentWindowSecs, startingBacklogValue);
}
if (startingBacklogValue > 0) {
// Find the maximum weight among all resources in this constraint
uint64 maxWeight = 0;
uint256 nResources = constraints[i].resources.length;
for (uint256 j = 0; j < nResources; ++j) {
uint64 weight = constraints[i].resources[j].weight;
if (weight > maxWeight) {
maxWeight = weight;
}
}

if (maxWeight > 0) {
// Neither of these values can be zero due to the earlier checks, so this division is safe
uint256 divisor =
uint256(adjustmentWindowSecs) * uint256(targetPerSec) * uint256(maxWeight);

// Calculate per-resource-kind exponent contribution
// we scale by 1000 to improve precision in calculating the exponent
// since this division will round down, it's always possible for the real exponent to be up to
// the number of constraints greater than the value we measure
// Operation is performed in uint256 to avoid overflow, but the result is guaranteed to fit in uint64 due to the earlier check
for (uint256 j = 0; j < nResources; ++j) {
uint8 kind = uint8(constraints[i].resources[j].resource);
uint64 weight = constraints[i].resources[j].weight;
pricingExponents[kind] +=
uint64(uint256(startingBacklogValue) * uint256(weight) * 1000 / divisor);
}
}
}
}

// this calculated pricing exponent will by used by nitro to calculate the gas price
// we check that the pricing exponent is below some reasonable number to avoid setting the gas price astronomically high
// as long as the gas price is not so high that no-one at all can send a transaction the chain will be able to function
// eg. these constraints can be changed again, or the sec council can send admin transactions
// with min base fee of 0.02, exponent of 8 (scaled by 1000) corresponds to a gas price of ~60 Gwei
for (uint8 k = 0; k < numResourceKinds; ++k) {
if (pricingExponents[k] > MAX_PRICING_EXPONENT) {
revert PricingExponentTooHigh(pricingExponents[k]);
}
}

ARB_OWNER.setMultiGasPricingConstraints(constraints);
}
}
2 changes: 1 addition & 1 deletion src/precompiles
Loading
Loading