Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/dispute/FaultDisputeGame.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol";
import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol";
import { IInitializable } from "src/dispute/interfaces/IInitializable.sol";
import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol";
import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol";
import { Clone } from "@solady/utils/Clone.sol";
import { Types } from "src/libraries/Types.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Types } from "src/libraries/Types.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { RLPReader } from "src/libraries/rlp/RLPReader.sol";
import "src/dispute/lib/Types.sol";
import "src/dispute/lib/Errors.sol";
/// @title FaultDisputeGame
/// @notice An implementation of the `IFaultDisputeGame` interface.
contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
////////////////////////////////////////////////////////////////
// State Vars //
////////////////////////////////////////////////////////////////
/// @notice The absolute prestate of the instruction trace. This is a constant that is defined
/// by the program that is being used to execute the trace.
Claim internal immutable ABSOLUTE_PRESTATE;
/// @notice The max depth of the game.
uint256 internal immutable MAX_GAME_DEPTH;
/// @notice The max depth of the output bisection portion of the position tree. Immediately beneath
/// this depth, execution trace bisection begins.
uint256 internal immutable SPLIT_DEPTH;
/// @notice The maximum duration that may accumulate on a team's chess clock before they may no longer respond.
Duration internal immutable MAX_CLOCK_DURATION;
/// @notice An onchain VM that performs single instruction steps on a fault proof program trace.
IBigStepper internal immutable VM;
/// @notice The game type ID.
GameType internal immutable GAME_TYPE;
/// @notice WETH contract for holding ETH.
IDelayedWETH internal immutable WETH;
/// @notice The anchor state registry.
IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY;
/// @notice The chain ID of the L2 network this contract argues about.
uint256 internal immutable L2_CHAIN_ID;
/// @notice The duration of the clock extension. Will be doubled if the grandchild is the root claim of an execution
/// trace bisection subgame.
Duration internal immutable CLOCK_EXTENSION;
/// @notice The global root claim's position is always at gindex 1.
Position internal constant ROOT_POSITION = Position.wrap(1);
/// @notice The index of the block number in the RLP-encoded block header.
/// @dev Consensus encoding reference:
/// https://github.com/paradigmxyz/reth/blob/5f82993c23164ce8ccdc7bf3ae5085205383a5c8/crates/primitives/src/header.rs#L368
uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8;
/// @notice Semantic version.
/// @custom:semver 1.3.1-beta.2
string public constant version = "1.3.1-beta.2";
/// @notice The starting timestamp of the game
Timestamp public createdAt;
/// @notice The timestamp of the game's global resolution.
Timestamp public resolvedAt;
/// @inheritdoc IDisputeGame
GameStatus public status;
/// @notice Flag for the `initialize` function to prevent re-initialization.
bool internal initialized;
/// @notice Flag for whether or not the L2 block number claim has been invalidated via `challengeRootL2Block`.
bool public l2BlockNumberChallenged;
/// @notice The challenger of the L2 block number claim. Should always be `address(0)` if `l2BlockNumberChallenged`
/// is `false`. Should be the address of the challenger if `l2BlockNumberChallenged` is `true`.
address public l2BlockNumberChallenger;
/// @notice An append-only array of all claims made during the dispute game.
ClaimData[] public claimData;
/// @notice Credited balances for winning participants.
mapping(address => uint256) public credit;
/// @notice A mapping to allow for constant-time lookups of existing claims.
mapping(Hash => bool) public claims;
/// @notice A mapping of subgames rooted at a claim index to other claim indices in the subgame.
mapping(uint256 => uint256[]) public subgames;
/// @notice A mapping of resolved subgames rooted at a claim index.
mapping(uint256 => bool) public resolvedSubgames;
/// @notice A mapping of claim indices to resolution checkpoints.
mapping(uint256 => ResolutionCheckpoint) public resolutionCheckpoints;
/// @notice The latest finalized output root, serving as the anchor for output bisection.
OutputRoot public startingOutputRoot;
/// @param _gameType The type ID of the game.
/// @param _absolutePrestate The absolute prestate of the instruction trace.
/// @param _maxGameDepth The maximum depth of bisection.
/// @param _splitDepth The final depth of the output bisection portion of the game.
/// @param _clockExtension The clock extension to perform when the remaining duration is less than the extension.
/// @param _maxClockDuration The maximum amount of time that may accumulate on a team's chess clock.
/// @param _vm An onchain VM that performs single instruction steps on an FPP trace.
/// @param _weth WETH contract for holding ETH.
/// @param _anchorStateRegistry The contract that stores the anchor state for each game type.
/// @param _l2ChainId Chain ID of the L2 network this contract argues about.
constructor(
GameType _gameType,
Claim _absolutePrestate,
uint256 _maxGameDepth,
uint256 _splitDepth,
Duration _clockExtension,
Duration _maxClockDuration,
IBigStepper _vm,
IDelayedWETH _weth,
IAnchorStateRegistry _anchorStateRegistry,
uint256 _l2ChainId
) {
// The max game depth may not be greater than `LibPosition.MAX_POSITION_BITLEN - 1`.
if (_maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge();
// The split depth plus one cannot be greater than or equal to the max game depth. We add
// an additional depth to the split depth to avoid a bug in trace ancestor lookup. We know
// that the case where the split depth is the max value for uint256 is equivalent to the
// second check though we do need to check it explicitly to avoid an overflow.
if (_splitDepth == type(uint256).max || _splitDepth + 1 >= _maxGameDepth) revert InvalidSplitDepth();
// The split depth cannot be 0 or 1 to stay in bounds of clock extension arithmetic.
if (_splitDepth < 2) revert InvalidSplitDepth();
// The PreimageOracle challenge period must fit into uint64 so we can safely use it here.
// Runtime check was added instead of changing the ABI since the contract is already
// deployed in production. We perform the same check within the PreimageOracle for the
// benefit of developers but also perform this check here defensively.
if (_vm.oracle().challengePeriod() > type(uint64).max) revert InvalidChallengePeriod();
// Determine the maximum clock extension which is either the split depth extension or the
// maximum game depth extension depending on the configuration of these contracts.
uint256 splitDepthExtension = uint256(_clockExtension.raw()) * 2;
uint256 maxGameDepthExtension = uint256(_clockExtension.raw()) + uint256(_vm.oracle().challengePeriod());
uint256 maxClockExtension = Math.max(splitDepthExtension, maxGameDepthExtension);
// The maximum clock extension must fit into a uint64.
if (maxClockExtension > type(uint64).max) revert InvalidClockExtension();
// The maximum clock extension may not be greater than the maximum clock duration.
if (uint64(maxClockExtension) > _maxClockDuration.raw()) revert InvalidClockExtension();
// Set up initial game state.
GAME_TYPE = _gameType;
ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth;
SPLIT_DEPTH = _splitDepth;
CLOCK_EXTENSION = _clockExtension;
MAX_CLOCK_DURATION = _maxClockDuration;
VM = _vm;
WETH = _weth;
ANCHOR_STATE_REGISTRY = _anchorStateRegistry;
L2_CHAIN_ID = _l2ChainId;
}
/// @inheritdoc IInitializable
function initialize() public payable virtual {
// SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and
// prevent the game from being created.
//
// Implicit assumptions:
// - The `gameStatus` state variable defaults to 0, which is `GameStatus.IN_PROGRESS`
// - The dispute game factory will enforce the required bond to initialize the game.
//
// Explicit checks:
// - The game must not have already been initialized.
// - An output root cannot be proposed at or before the starting block number.
// INVARIANT: The game must not have already been initialized.
if (initialized) revert AlreadyInitialized();
// Grab the latest anchor root.
(Hash root, uint256 rootBlockNumber) = ANCHOR_STATE_REGISTRY.anchors(GAME_TYPE);
// Should only happen if this is a new game type that hasn't been set up yet.
if (root.raw() == bytes32(0)) revert AnchorRootNotFound();
// Set the starting output root.
startingOutputRoot = OutputRoot({ l2BlockNumber: rootBlockNumber, root: root });
// Revert if the calldata size is not the expected length.
//
// This is to prevent adding extra or omitting bytes from to `extraData` that result in a different game UUID
// in the factory, but are not used by the game, which would allow for multiple dispute games for the same
// output proposal to be created.
//
// Expected length: 0x7A
// - 0x04 selector
// - 0x14 creator address
// - 0x20 root claim
// - 0x20 l1 head
// - 0x20 extraData
// - 0x02 CWIA bytes
assembly {
if iszero(eq(calldatasize(), 0x7A)) {
// Store the selector for `BadExtraData()` & revert
mstore(0x00, 0x9824bdab)
revert(0x1C, 0x04)
}
}
// Do not allow the game to be initialized if the root claim corresponds to a block at or before the
// configured starting block number.
if (l2BlockNumber() <= rootBlockNumber) revert UnexpectedRootClaim(rootClaim());
// Set the root claim
claimData.push(
ClaimData({
parentIndex: type(uint32).max,
counteredBy: address(0),
claimant: gameCreator(),
bond: uint128(msg.value),
claim: rootClaim(),
position: ROOT_POSITION,
clock: LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp)))
})
);
// Set the game as initialized.
initialized = true;
// Deposit the bond.
WETH.deposit{ value: msg.value }();
// Set the game's starting timestamp
createdAt = Timestamp.wrap(uint64(block.timestamp));
}
////////////////////////////////////////////////////////////////
// `IFaultDisputeGame` impl //
////////////////////////////////////////////////////////////////
/// @inheritdoc IFaultDisputeGame
function step(
uint256 _claimIndex,
bool _isAttack,
bytes calldata _stateData,
bytes calldata _proof
)
public
virtual
{
// INVARIANT: Steps cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// Get the parent. If it does not exist, the call will revert with OOB.
ClaimData storage parent = claimData[_claimIndex];
// Pull the parent position out of storage.
Position parentPos = parent.position;
// Determine the position of the step.
Position stepPos = parentPos.move(_isAttack);
// INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH`
if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent();
// Determine the expected pre & post states of the step.
Claim preStateClaim;
ClaimData storage postState;
if (_isAttack) {
// If the step position's index at depth is 0, the prestate is the absolute
// prestate.
// If the step is an attack at a trace index > 0, the prestate exists elsewhere in
// the game state.
// NOTE: We localize the `indexAtDepth` for the current execution trace subgame by finding
// the remainder of the index at depth divided by 2 ** (MAX_GAME_DEPTH - SPLIT_DEPTH),
// which is the number of leaves in each execution trace subgame. This is so that we can
// determine whether or not the step position is represents the `ABSOLUTE_PRESTATE`.
preStateClaim = (stepPos.indexAtDepth() % (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0
? ABSOLUTE_PRESTATE
: _findTraceAncestor(Position.wrap(parentPos.raw() - 1), parent.parentIndex, false).claim;
// For all attacks, the poststate is the parent claim.
postState = parent;
} else {
// If the step is a defense, the poststate exists elsewhere in the game state,
// and the parent claim is the expected pre-state.
preStateClaim = parent.claim;
postState = _findTraceAncestor(Position.wrap(parentPos.raw() + 1), parent.parentIndex, false);
}
// INVARIANT: The prestate is always invalid if the passed `_stateData` is not the
// preimage of the prestate claim hash.
// We ignore the highest order byte of the digest because it is used to
// indicate the VM Status and is added after the digest is computed.
if (keccak256(_stateData) << 8 != preStateClaim.raw() << 8) revert InvalidPrestate();
// Compute the local preimage context for the step.
Hash uuid = _findLocalContext(_claimIndex);
// INVARIANT: If a step is an attack, the poststate is valid if the step produces
// the same poststate hash as the parent claim's value.
// If a step is a defense:
// 1. If the parent claim and the found post state agree with each other
// (depth diff % 2 == 0), the step is valid if it produces the same
// state hash as the post state's claim.
// 2. If the parent claim and the found post state disagree with each other
// (depth diff % 2 != 0), the parent cannot be countered unless the step
// produces the same state hash as `postState.claim`.
// SAFETY: While the `attack` path does not need an extra check for the post
// state's depth in relation to the parent, we don't need another
// branch because (n - n) % 2 == 0.
bool validStep = VM.step(_stateData, _proof, uuid.raw()) == postState.claim.raw();
bool parentPostAgree = (parentPos.depth() - postState.position.depth()) % 2 == 0;
if (parentPostAgree == validStep) revert ValidStep();
// INVARIANT: A step cannot be made against a claim for a second time.
if (parent.counteredBy != address(0)) revert DuplicateStep();
// Set the parent claim as countered. We do not need to append a new claim to the game;
// instead, we can just set the existing parent as countered.
parent.counteredBy = msg.sender;
}
/// @notice Generic move function, used for both `attack` and `defend` moves.
/// @param _disputed The disputed `Claim`.
/// @param _challengeIndex The index of the claim being moved against.
/// @param _claim The claim at the next logical position in the game.
/// @param _isAttack Whether or not the move is an attack or defense.
function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual {
// INVARIANT: Moves cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// Get the parent. If it does not exist, the call will revert with OOB.
ClaimData memory parent = claimData[_challengeIndex];
// INVARIANT: The claim at the _challengeIndex must be the disputed claim.
if (Claim.unwrap(parent.claim) != Claim.unwrap(_disputed)) revert InvalidDisputedClaimIndex();
// Compute the position that the claim commits to. Because the parent's position is already
// known, we can compute the next position by moving left or right depending on whether
// or not the move is an attack or defense.
Position parentPos = parent.position;
Position nextPosition = parentPos.move(_isAttack);
uint256 nextPositionDepth = nextPosition.depth();
// INVARIANT: A defense can never be made against the root claim of either the output root game or any
// of the execution trace bisection subgames. This is because the root claim commits to the
// entire state. Therefore, the only valid defense is to do nothing if it is agreed with.
if ((_challengeIndex == 0 || nextPositionDepth == SPLIT_DEPTH + 2) && !_isAttack) {
revert CannotDefendRootClaim();
}
// INVARIANT: No moves against the root claim can be made after it has been challenged with
// `challengeRootL2Block`.`
if (l2BlockNumberChallenged && _challengeIndex == 0) revert L2BlockNumberChallenged();
// INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a
// claim at this depth is to perform a single instruction step on-chain via
// the `step` function to prove that the state transition produces an unexpected
// post-state.
if (nextPositionDepth > MAX_GAME_DEPTH) revert GameDepthExceeded();
// When the next position surpasses the split depth (i.e., it is the root claim of an execution
// trace bisection sub-game), we need to perform some extra verification steps.
if (nextPositionDepth == SPLIT_DEPTH + 1) {
_verifyExecBisectionRoot(_claim, _challengeIndex, parentPos, _isAttack);
}
// INVARIANT: The `msg.value` must exactly equal the required bond.
if (getRequiredBond(nextPosition) != msg.value) revert IncorrectBondAmount();
// Compute the duration of the next clock. This is done by adding the duration of the
// grandparent claim to the difference between the current block timestamp and the
// parent's clock timestamp.
Duration nextDuration = getChallengerDuration(_challengeIndex);
// INVARIANT: A move can never be made once its clock has exceeded `MAX_CLOCK_DURATION`
// seconds of time.
if (nextDuration.raw() == MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded();
// Clock extension is a mechanism that automatically extends the clock for a potential
// grandchild claim when there would be less than the clock extension time left if a player
// is forced to inherit another team's clock when countering a freeloader claim. Exact
// amount of clock extension time depends exactly where we are within the game.
uint64 actualExtension;
if (nextPositionDepth == MAX_GAME_DEPTH - 1) {
// If the next position is `MAX_GAME_DEPTH - 1` then we're about to execute a step. Our
// clock extension must therefore account for the LPP challenge period in addition to
// the standard clock extension.
actualExtension = CLOCK_EXTENSION.raw() + uint64(VM.oracle().challengePeriod());
} else if (nextPositionDepth == SPLIT_DEPTH - 1) {
// If the next position is `SPLIT_DEPTH - 1` then we're about to begin an execution
// trace bisection and we need to give extra time for the off-chain challenge agent to
// be able to generate the initial instruction trace on the native FPVM.
actualExtension = CLOCK_EXTENSION.raw() * 2;
} else {
// Otherwise, we just use the standard clock extension.
actualExtension = CLOCK_EXTENSION.raw();
}
// Check if we need to apply the clock extension.
if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - actualExtension) {
nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - actualExtension);
}
// Construct the next clock with the new duration and the current block timestamp.
Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp)));
// INVARIANT: There cannot be multiple identical claims with identical moves on the same challengeIndex. Multiple
// claims at the same position may dispute the same challengeIndex. However, they must have different
// values.
Hash claimHash = _claim.hashClaimPos(nextPosition, _challengeIndex);
if (claims[claimHash]) revert ClaimAlreadyExists();
claims[claimHash] = true;
// Create the new claim.
claimData.push(
ClaimData({
parentIndex: uint32(_challengeIndex),
// This is updated during subgame resolution
counteredBy: address(0),
claimant: msg.sender,
bond: uint128(msg.value),
claim: _claim,
position: nextPosition,
clock: nextClock
})
);
// Update the subgame rooted at the parent claim.
subgames[_challengeIndex].push(claimData.length - 1);
// Deposit the bond.
WETH.deposit{ value: msg.value }();
// Emit the appropriate event for the attack or defense.
emit Move(_challengeIndex, _claim, msg.sender);
}
/// @inheritdoc IFaultDisputeGame
function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable {
move(_disputed, _parentIndex, _claim, true);
}
/// @inheritdoc IFaultDisputeGame
function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable {
move(_disputed, _parentIndex, _claim, false);
}
/// @inheritdoc IFaultDisputeGame
function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external {
// INVARIANT: Local data can only be added if the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
(Claim starting, Position startingPos, Claim disputed, Position disputedPos) =
_findStartingAndDisputedOutputs(_execLeafIdx);
Hash uuid = _computeLocalContext(starting, startingPos, disputed, disputedPos);
IPreimageOracle oracle = VM.oracle();
if (_ident == LocalPreimageKey.L1_HEAD_HASH) {
// Load the L1 head hash
oracle.loadLocalData(_ident, uuid.raw(), l1Head().raw(), 32, _partOffset);
} else if (_ident == LocalPreimageKey.STARTING_OUTPUT_ROOT) {
// Load the starting proposal's output root.
oracle.loadLocalData(_ident, uuid.raw(), starting.raw(), 32, _partOffset);
} else if (_ident == LocalPreimageKey.DISPUTED_OUTPUT_ROOT) {
// Load the disputed proposal's output root
oracle.loadLocalData(_ident, uuid.raw(), disputed.raw(), 32, _partOffset);
} else if (_ident == LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER) {
// Load the disputed proposal's L2 block number as a big-endian uint64 in the
// high order 8 bytes of the word.
// We add the index at depth + 1 to the starting block number to get the disputed L2
// block number.
uint256 l2Number = startingOutputRoot.l2BlockNumber + disputedPos.traceIndex(SPLIT_DEPTH) + 1;
// Choose the minimum between the `l2BlockNumber` claim and the bisected-to L2 block number.
l2Number = l2Number < l2BlockNumber() ? l2Number : l2BlockNumber();
oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2Number << 0xC0), 8, _partOffset);
} else if (_ident == LocalPreimageKey.CHAIN_ID) {
// Load the chain ID as a big-endian uint64 in the high order 8 bytes of the word.
oracle.loadLocalData(_ident, uuid.raw(), bytes32(L2_CHAIN_ID << 0xC0), 8, _partOffset);
} else {
revert InvalidLocalIdent();
}
}
/// @inheritdoc IFaultDisputeGame
function getNumToResolve(uint256 _claimIndex) public view returns (uint256 numRemainingChildren_) {
ResolutionCheckpoint storage checkpoint = resolutionCheckpoints[_claimIndex];
uint256[] storage challengeIndices = subgames[_claimIndex];
uint256 challengeIndicesLen = challengeIndices.length;
numRemainingChildren_ = challengeIndicesLen - checkpoint.subgameIndex;
}
/// @inheritdoc IFaultDisputeGame
function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) {
l2BlockNumber_ = _getArgUint256(0x54);
}
/// @inheritdoc IFaultDisputeGame
function startingBlockNumber() external view returns (uint256 startingBlockNumber_) {
startingBlockNumber_ = startingOutputRoot.l2BlockNumber;
}
/// @inheritdoc IFaultDisputeGame
function startingRootHash() external view returns (Hash startingRootHash_) {
startingRootHash_ = startingOutputRoot.root;
}
/// @notice Challenges the root L2 block number by providing the preimage of the output root and the L2 block header
/// and showing that the committed L2 block number is incorrect relative to the claimed L2 block number.
/// @param _outputRootProof The output root proof.
/// @param _headerRLP The RLP-encoded L2 block header.
function challengeRootL2Block(
Types.OutputRootProof calldata _outputRootProof,
bytes calldata _headerRLP
)
external
{
// INVARIANT: Moves cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// The root L2 block claim can only be challenged once.
if (l2BlockNumberChallenged) revert L2BlockNumberChallenged();
// Verify the output root preimage.
if (Hashing.hashOutputRootProof(_outputRootProof) != rootClaim().raw()) revert InvalidOutputRootProof();
// Verify the block hash preimage.
if (keccak256(_headerRLP) != _outputRootProof.latestBlockhash) revert InvalidHeaderRLP();
// Decode the header RLP to find the number of the block. In the consensus encoding, the timestamp
// is the 9th element in the list that represents the block header.
RLPReader.RLPItem[] memory headerContents = RLPReader.readList(RLPReader.toRLPItem(_headerRLP));
bytes memory rawBlockNumber = RLPReader.readBytes(headerContents[HEADER_BLOCK_NUMBER_INDEX]);
// Sanity check the block number string length.
if (rawBlockNumber.length > 32) revert InvalidHeaderRLP();
// Convert the raw, left-aligned block number to a uint256 by aligning it as a big-endian
// number in the low-order bytes of a 32-byte word.
//
// SAFETY: The length of `rawBlockNumber` is checked above to ensure it is at most 32 bytes.
uint256 blockNumber;
assembly {
blockNumber := shr(shl(0x03, sub(0x20, mload(rawBlockNumber))), mload(add(rawBlockNumber, 0x20)))
}
// Ensure the block number does not match the block number claimed in the dispute game.
if (blockNumber == l2BlockNumber()) revert BlockNumberMatches();
// Issue a special counter to the root claim. This counter will always win the root claim subgame, and receive
// the bond from the root claimant.
l2BlockNumberChallenger = msg.sender;
l2BlockNumberChallenged = true;
}
////////////////////////////////////////////////////////////////
// `IDisputeGame` impl //
////////////////////////////////////////////////////////////////
/// @inheritdoc IDisputeGame
function resolve() external returns (GameStatus status_) {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// INVARIANT: Resolution cannot occur unless the absolute root subgame has been resolved.
if (!resolvedSubgames[0]) revert OutOfOrderResolution();
// Update the global game status; The dispute has concluded.
status_ = claimData[0].counteredBy == address(0) ? GameStatus.DEFENDER_WINS : GameStatus.CHALLENGER_WINS;
resolvedAt = Timestamp.wrap(uint64(block.timestamp));
// Update the status and emit the resolved event, note that we're performing an assignment here.
emit Resolved(status = status_);
// Try to update the anchor state, this should not revert.
ANCHOR_STATE_REGISTRY.tryUpdateAnchorState();
}
/// @inheritdoc IFaultDisputeGame
function resolveClaim(uint256 _claimIndex, uint256 _numToResolve) external {
// INVARIANT: Resolution cannot occur unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
ClaimData storage subgameRootClaim = claimData[_claimIndex];
Duration challengeClockDuration = getChallengerDuration(_claimIndex);
// INVARIANT: Cannot resolve a subgame unless the clock of its would-be counter has expired
// INVARIANT: Assuming ordered subgame resolution, challengeClockDuration is always >= MAX_CLOCK_DURATION if all
// descendant subgames are resolved
if (challengeClockDuration.raw() < MAX_CLOCK_DURATION.raw()) revert ClockNotExpired();
// INVARIANT: Cannot resolve a subgame twice.
if (resolvedSubgames[_claimIndex]) revert ClaimAlreadyResolved();
uint256[] storage challengeIndices = subgames[_claimIndex];
uint256 challengeIndicesLen = challengeIndices.length;
// Uncontested claims are resolved implicitly unless they are the root claim. Pay out the bond to the claimant
// and return early.
if (challengeIndicesLen == 0 && _claimIndex != 0) {
// In the event that the parent claim is at the max depth, there will always be 0 subgames. If the
// `counteredBy` field is set and there are no subgames, this implies that the parent claim was successfully
// stepped against. In this case, we pay out the bond to the party that stepped against the parent claim.
// Otherwise, the parent claim is uncontested, and the bond is returned to the claimant.
address counteredBy = subgameRootClaim.counteredBy;
address recipient = counteredBy == address(0) ? subgameRootClaim.claimant : counteredBy;
_distributeBond(recipient, subgameRootClaim);
resolvedSubgames[_claimIndex] = true;
return;
}
// Fetch the resolution checkpoint from storage.
ResolutionCheckpoint memory checkpoint = resolutionCheckpoints[_claimIndex];
// If the checkpoint does not currently exist, initialize the current left most position as max u128.
if (!checkpoint.initialCheckpointComplete) {
checkpoint.leftmostPosition = Position.wrap(type(uint128).max);
checkpoint.initialCheckpointComplete = true;
// If `_numToResolve == 0`, assume that we can check all child subgames in this one callframe.
if (_numToResolve == 0) _numToResolve = challengeIndicesLen;
}
// Assume parent is honest until proven otherwise
uint256 lastToResolve = checkpoint.subgameIndex + _numToResolve;
uint256 finalCursor = lastToResolve > challengeIndicesLen ? challengeIndicesLen : lastToResolve;
for (uint256 i = checkpoint.subgameIndex; i < finalCursor; i++) {
uint256 challengeIndex = challengeIndices[i];
// INVARIANT: Cannot resolve a subgame containing an unresolved claim
if (!resolvedSubgames[challengeIndex]) revert OutOfOrderResolution();
ClaimData storage claim = claimData[challengeIndex];
// If the child subgame is uncountered and further left than the current left-most counter,
// update the parent subgame's `countered` address and the current `leftmostCounter`.
// The left-most correct counter is preferred in bond payouts in order to discourage attackers
// from countering invalid subgame roots via an invalid defense position. As such positions
// cannot be correctly countered.
// Note that correctly positioned defense, but invalid claimes can still be successfully countered.
if (claim.counteredBy == address(0) && checkpoint.leftmostPosition.raw() > claim.position.raw()) {
checkpoint.counteredBy = claim.claimant;
checkpoint.leftmostPosition = claim.position;
}
}
// Increase the checkpoint's cursor position by the number of children that were checked.
checkpoint.subgameIndex = uint32(finalCursor);
// Persist the checkpoint and allow for continuing in a separate transaction, if resolution is not already
// complete.
resolutionCheckpoints[_claimIndex] = checkpoint;
// If all children have been traversed in the above loop, the subgame may be resolved. Otherwise, persist the
// checkpoint and allow for continuation in a separate transaction.
if (checkpoint.subgameIndex == challengeIndicesLen) {
address countered = checkpoint.counteredBy;
// Mark the subgame as resolved.
resolvedSubgames[_claimIndex] = true;
// Distribute the bond to the appropriate party.
if (_claimIndex == 0 && l2BlockNumberChallenged) {
// Special case: If the root claim has been challenged with the `challengeRootL2Block` function,
// the bond is always paid out to the issuer of that challenge.
address challenger = l2BlockNumberChallenger;
_distributeBond(challenger, subgameRootClaim);
subgameRootClaim.counteredBy = challenger;
} else {
// If the parent was not successfully countered, pay out the parent's bond to the claimant.
// If the parent was successfully countered, pay out the parent's bond to the challenger.
_distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim);
// Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to
// resolveClaim will not need to traverse this subgame.
subgameRootClaim.counteredBy = countered;
}
}
}
/// @inheritdoc IDisputeGame
function gameType() public view override returns (GameType gameType_) {
gameType_ = GAME_TYPE;
}
/// @inheritdoc IDisputeGame
function gameCreator() public pure returns (address creator_) {
creator_ = _getArgAddress(0x00);
}
/// @inheritdoc IDisputeGame
function rootClaim() public pure returns (Claim rootClaim_) {
rootClaim_ = Claim.wrap(_getArgBytes32(0x14));
}
/// @inheritdoc IDisputeGame
function l1Head() public pure returns (Hash l1Head_) {
l1Head_ = Hash.wrap(_getArgBytes32(0x34));
}
/// @inheritdoc IDisputeGame
function extraData() public pure returns (bytes memory extraData_) {
// The extra data starts at the second word within the cwia calldata and
// is 32 bytes long.
extraData_ = _getArgBytes(0x54, 0x20);
}
/// @inheritdoc IDisputeGame
function gameData() external view returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_) {
gameType_ = gameType();
rootClaim_ = rootClaim();
extraData_ = extraData();
}
////////////////////////////////////////////////////////////////
// MISC EXTERNAL //
////////////////////////////////////////////////////////////////
/// @notice Returns the required bond for a given move kind.
/// @param _position The position of the bonded interaction.
/// @return requiredBond_ The required ETH bond for the given move, in wei.
function getRequiredBond(Position _position) public view returns (uint256 requiredBond_) {
uint256 depth = uint256(_position.depth());
if (depth > MAX_GAME_DEPTH) revert GameDepthExceeded();
// Values taken from Big Bonds v1.5 (TM) spec.
uint256 assumedBaseFee = 200 gwei;
uint256 baseGasCharged = 400_000;
uint256 highGasCharged = 300_000_000;
// Goal here is to compute the fixed multiplier that will be applied to the base gas
// charged to get the required gas amount for the given depth. We apply this multiplier
// some `n` times where `n` is the depth of the position. We are looking for some number
// that, when multiplied by itself `MAX_GAME_DEPTH` times and then multiplied by the base
// gas charged, will give us the maximum gas that we want to charge.
// We want to solve for (highGasCharged/baseGasCharged) ** (1/MAX_GAME_DEPTH).
// We know that a ** (b/c) is equal to e ** (ln(a) * (b/c)).
// We can compute e ** (ln(a) * (b/c)) quite easily with FixedPointMathLib.
// Set up a, b, and c.
uint256 a = highGasCharged / baseGasCharged;
uint256 b = FixedPointMathLib.WAD;
uint256 c = MAX_GAME_DEPTH * FixedPointMathLib.WAD;
// Compute ln(a).
// slither-disable-next-line divide-before-multiply
uint256 lnA = uint256(FixedPointMathLib.lnWad(int256(a * FixedPointMathLib.WAD)));
// Computes (b / c) with full precision using WAD = 1e18.
uint256 bOverC = FixedPointMathLib.divWad(b, c);
// Compute e ** (ln(a) * (b/c))
// sMulWad can be used here since WAD = 1e18 maintains the same precision.
uint256 numerator = FixedPointMathLib.mulWad(lnA, bOverC);
int256 base = FixedPointMathLib.expWad(int256(numerator));
// Compute the required gas amount.
int256 rawGas = FixedPointMathLib.powWad(base, int256(depth * FixedPointMathLib.WAD));
uint256 requiredGas = FixedPointMathLib.mulWad(baseGasCharged, uint256(rawGas));
// Compute the required bond.
requiredBond_ = assumedBaseFee * requiredGas;
}
/// @notice Claim the credit belonging to the recipient address.
/// @param _recipient The owner and recipient of the credit.
function claimCredit(address _recipient) external {
// Remove the credit from the recipient prior to performing the external call.
uint256 recipientCredit = credit[_recipient];
credit[_recipient] = 0;
// Revert if the recipient has no credit to claim.
if (recipientCredit == 0) revert NoCreditToClaim();
// Try to withdraw the WETH amount so it can be used here.
WETH.withdraw(_recipient, recipientCredit);
// Transfer the credit to the recipient.
(bool success,) = _recipient.call{ value: recipientCredit }(hex"");
if (!success) revert BondTransferFailed();
}
/// @notice Returns the amount of time elapsed on the potential challenger to `_claimIndex`'s chess clock. Maxes
/// out at `MAX_CLOCK_DURATION`.
/// @param _claimIndex The index of the subgame root claim.
/// @return duration_ The time elapsed on the potential challenger to `_claimIndex`'s chess clock.
function getChallengerDuration(uint256 _claimIndex) public view returns (Duration duration_) {
// INVARIANT: The game must be in progress to query the remaining time to respond to a given claim.
if (status != GameStatus.IN_PROGRESS) {
revert GameNotInProgress();
}
// Fetch the subgame root claim.
ClaimData storage subgameRootClaim = claimData[_claimIndex];
// Fetch the parent of the subgame root's clock, if it exists.
Clock parentClock;
if (subgameRootClaim.parentIndex != type(uint32).max) {
parentClock = claimData[subgameRootClaim.parentIndex].clock;
}
// Compute the duration elapsed of the potential challenger's clock.
uint64 challengeDuration =
uint64(parentClock.duration().raw() + (block.timestamp - subgameRootClaim.clock.timestamp().raw()));
duration_ = challengeDuration > MAX_CLOCK_DURATION.raw() ? MAX_CLOCK_DURATION : Duration.wrap(challengeDuration);
}
/// @notice Returns the length of the `claimData` array.
function claimDataLen() external view returns (uint256 len_) {
len_ = claimData.length;
}
////////////////////////////////////////////////////////////////
// IMMUTABLE GETTERS //
////////////////////////////////////////////////////////////////
/// @notice Returns the absolute prestate of the instruction trace.
function absolutePrestate() external view returns (Claim absolutePrestate_) {
absolutePrestate_ = ABSOLUTE_PRESTATE;
}
/// @notice Returns the max game depth.
function maxGameDepth() external view returns (uint256 maxGameDepth_) {
maxGameDepth_ = MAX_GAME_DEPTH;
}
/// @notice Returns the split depth.
function splitDepth() external view returns (uint256 splitDepth_) {
splitDepth_ = SPLIT_DEPTH;
}
/// @notice Returns the max clock duration.
function maxClockDuration() external view returns (Duration maxClockDuration_) {
maxClockDuration_ = MAX_CLOCK_DURATION;
}
/// @notice Returns the clock extension constant.
function clockExtension() external view returns (Duration clockExtension_) {
clockExtension_ = CLOCK_EXTENSION;
}
/// @notice Returns the address of the VM.
function vm() external view returns (IBigStepper vm_) {
vm_ = VM;
}
/// @notice Returns the WETH contract for holding ETH.
function weth() external view returns (IDelayedWETH weth_) {
weth_ = WETH;
}
/// @notice Returns the anchor state registry contract.
function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_) {
registry_ = ANCHOR_STATE_REGISTRY;
}
/// @notice Returns the chain ID of the L2 network this contract argues about.
function l2ChainId() external view returns (uint256 l2ChainId_) {
l2ChainId_ = L2_CHAIN_ID;
}
////////////////////////////////////////////////////////////////
// HELPERS //
////////////////////////////////////////////////////////////////
/// @notice Pays out the bond of a claim to a given recipient.
/// @param _recipient The recipient of the bond.
/// @param _bonded The claim to pay out the bond of.
function _distributeBond(address _recipient, ClaimData storage _bonded) internal {
// Set all bits in the bond value to indicate that the bond has been paid out.
uint256 bond = _bonded.bond;
// Increase the recipient's credit.
credit[_recipient] += bond;
// Unlock the bond.
WETH.unlock(_recipient, bond);
}
/// @notice Verifies the integrity of an execution bisection subgame's root claim. Reverts if the claim
/// is invalid.
/// @param _rootClaim The root claim of the execution bisection subgame.
function _verifyExecBisectionRoot(
Claim _rootClaim,
uint256 _parentIdx,
Position _parentPos,
bool _isAttack
)
internal
view
{
// The root claim of an execution trace bisection sub-game must:
// 1. Signal that the VM panicked or resulted in an invalid transition if the disputed output root
// was made by the opposing party.
// 2. Signal that the VM resulted in a valid transition if the disputed output root was made by the same party.
// If the move is a defense, the disputed output could have been made by either party. In this case, we
// need to search for the parent output to determine what the expected status byte should be.
Position disputedLeafPos = Position.wrap(_parentPos.raw() + 1);
ClaimData storage disputed = _findTraceAncestor({ _pos: disputedLeafPos, _start: _parentIdx, _global: true });
uint8 vmStatus = uint8(_rootClaim.raw()[0]);
if (_isAttack || disputed.position.depth() % 2 == SPLIT_DEPTH % 2) {
// If the move is an attack, the parent output is always deemed to be disputed. In this case, we only need
// to check that the root claim signals that the VM panicked or resulted in an invalid transition.
// If the move is a defense, and the disputed output and creator of the execution trace subgame disagree,
// the root claim should also signal that the VM panicked or resulted in an invalid transition.
if (!(vmStatus == VMStatuses.INVALID.raw() || vmStatus == VMStatuses.PANIC.raw())) {
revert UnexpectedRootClaim(_rootClaim);
}
} else if (vmStatus != VMStatuses.VALID.raw()) {
// The disputed output and the creator of the execution trace subgame agree. The status byte should
// have signaled that the VM succeeded.
revert UnexpectedRootClaim(_rootClaim);
}
}
/// @notice Finds the trace ancestor of a given position within the DAG.
/// @param _pos The position to find the trace ancestor claim of.
/// @param _start The index to start searching from.
/// @param _global Whether or not to search the entire dag or just within an execution trace subgame. If set to
/// `true`, and `_pos` is at or above the split depth, this function will revert.
/// @return ancestor_ The ancestor claim that commits to the same trace index as `_pos`.
function _findTraceAncestor(
Position _pos,
uint256 _start,
bool _global
)
internal
view
returns (ClaimData storage ancestor_)
{
// Grab the trace ancestor's expected position.
Position traceAncestorPos = _global ? _pos.traceAncestor() : _pos.traceAncestorBounded(SPLIT_DEPTH);
// Walk up the DAG to find a claim that commits to the same trace index as `_pos`. It is
// guaranteed that such a claim exists.
ancestor_ = claimData[_start];
while (ancestor_.position.raw() != traceAncestorPos.raw()) {
ancestor_ = claimData[ancestor_.parentIndex];
}
}
/// @notice Finds the starting and disputed output root for a given `ClaimData` within the DAG. This
/// `ClaimData` must be below the `SPLIT_DEPTH`.
/// @param _start The index within `claimData` of the claim to start searching from.
/// @return startingClaim_ The starting output root claim.
/// @return startingPos_ The starting output root position.
/// @return disputedClaim_ The disputed output root claim.
/// @return disputedPos_ The disputed output root position.
function _findStartingAndDisputedOutputs(uint256 _start)
internal
view
returns (Claim startingClaim_, Position startingPos_, Claim disputedClaim_, Position disputedPos_)
{
// Fatch the starting claim.
uint256 claimIdx = _start;
ClaimData storage claim = claimData[claimIdx];
// If the starting claim's depth is less than or equal to the split depth, we revert as this is UB.
if (claim.position.depth() <= SPLIT_DEPTH) revert ClaimAboveSplit();
// We want to:
// 1. Find the first claim at the split depth.
// 2. Determine whether it was the starting or disputed output for the exec game.
// 3. Find the complimentary claim depending on the info from #2 (pre or post).
// Walk up the DAG until the ancestor's depth is equal to the split depth.
uint256 currentDepth;
ClaimData storage execRootClaim = claim;
while ((currentDepth = claim.position.depth()) > SPLIT_DEPTH) {
uint256 parentIndex = claim.parentIndex;
// If we're currently at the split depth + 1, we're at the root of the execution sub-game.
// We need to keep track of the root claim here to determine whether the execution sub-game was
// started with an attack or defense against the output leaf claim.
if (currentDepth == SPLIT_DEPTH + 1) execRootClaim = claim;
claim = claimData[parentIndex];
claimIdx = parentIndex;
}
// Determine whether the start of the execution sub-game was an attack or defense to the output root
// above. This is important because it determines which claim is the starting output root and which
// is the disputed output root.
(Position execRootPos, Position outputPos) = (execRootClaim.position, claim.position);
bool wasAttack = execRootPos.parent().raw() == outputPos.raw();
// Determine the starting and disputed output root indices.
// 1. If it was an attack, the disputed output root is `claim`, and the starting output root is
// elsewhere in the DAG (it must commit to the block # index at depth of `outputPos - 1`).
// 2. If it was a defense, the starting output root is `claim`, and the disputed output root is
// elsewhere in the DAG (it must commit to the block # index at depth of `outputPos + 1`).
if (wasAttack) {
// If this is an attack on the first output root (the block directly after the starting
// block number), the starting claim nor position exists in the tree. We leave these as
// 0, which can be easily identified due to 0 being an invalid Gindex.
if (outputPos.indexAtDepth() > 0) {
ClaimData storage starting = _findTraceAncestor(Position.wrap(outputPos.raw() - 1), claimIdx, true);
(startingClaim_, startingPos_) = (starting.claim, starting.position);
} else {
startingClaim_ = Claim.wrap(startingOutputRoot.root.raw());
}
(disputedClaim_, disputedPos_) = (claim.claim, claim.position);
} else {
ClaimData storage disputed = _findTraceAncestor(Position.wrap(outputPos.raw() + 1), claimIdx, true);
(startingClaim_, startingPos_) = (claim.claim, claim.position);
(disputedClaim_, disputedPos_) = (disputed.claim, disputed.position);
}
}
/// @notice Finds the local context hash for a given claim index that is present in an execution trace subgame.
/// @param _claimIndex The index of the claim to find the local context hash for.
/// @return uuid_ The local context hash.
function _findLocalContext(uint256 _claimIndex) internal view returns (Hash uuid_) {
(Claim starting, Position startingPos, Claim disputed, Position disputedPos) =
_findStartingAndDisputedOutputs(_claimIndex);
uuid_ = _computeLocalContext(starting, startingPos, disputed, disputedPos);
}
/// @notice Computes the local context hash for a set of starting/disputed claim values and positions.
/// @param _starting The starting claim.
/// @param _startingPos The starting claim's position.
/// @param _disputed The disputed claim.
/// @param _disputedPos The disputed claim's position.
/// @return uuid_ The local context hash.
function _computeLocalContext(
Claim _starting,
Position _startingPos,
Claim _disputed,
Position _disputedPos
)
internal
pure
returns (Hash uuid_)
{
// A position of 0 indicates that the starting claim is the absolute prestate. In this special case,
// we do not include the starting claim within the local context hash.
uuid_ = _startingPos.raw() == 0
? Hash.wrap(keccak256(abi.encode(_disputed, _disputedPos)))
: Hash.wrap(keccak256(abi.encode(_starting, _startingPos, _disputed, _disputedPos)));
}
}
"
},
"lib/solady/src/utils/FixedPointMathLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(and(iszero(iszero(y)), eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/exp-ln
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= floor(log(0.5e18) * 1e18) ≈ -42e18`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x
Submitted on: 2025-09-28 11:36:57
Comments
Log in to comment.
No comments yet.