Contract Source Code:
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
import "./MerkleLib.sol";
import "./HubPoolInterface.sol";
import "./Lockable.sol";
import "./interfaces/LpTokenFactoryInterface.sol";
import "./interfaces/WETH9.sol";
import "@uma/core/contracts/common/implementation/Testable.sol";
import "@uma/core/contracts/common/implementation/MultiCaller.sol";
import "@uma/core/contracts/oracle/implementation/Constants.sol";
import "@uma/core/contracts/common/interfaces/AddressWhitelistInterface.sol";
import "@uma/core/contracts/oracle/interfaces/IdentifierWhitelistInterface.sol";
import "@uma/core/contracts/oracle/interfaces/FinderInterface.sol";
import "@uma/core/contracts/oracle/interfaces/StoreInterface.sol";
import "@uma/core/contracts/oracle/interfaces/SkinnyOptimisticOracleInterface.sol";
import "@uma/core/contracts/common/interfaces/ExpandedIERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/**
* @notice Contract deployed on Ethereum that houses L1 token liquidity for all SpokePools. A dataworker can interact
* with merkle roots stored in this contract via inclusion proofs to instruct this contract to send tokens to L2
* SpokePools via "pool rebalances" that can be used to pay out relayers on those networks. This contract is also
* responsible for publishing relayer refund and slow relay merkle roots to SpokePools.
* @notice This contract is meant to act as the cross chain administrator and owner of all L2 spoke pools, so all
* governance actions and pool rebalances originate from here and bridge instructions to L2s.
* @dev This contract should be deprecated by the year 2106, at which point uint32 timestamps will roll over. This is
* an issue for this contract because fee calculations will become bizarre when multiplying by negative time deltas.
* Before this date, this contract should be paused from accepting new root bundles and all LP tokens should be
* disabled by the admin.
*/
contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {
using SafeERC20 for IERC20;
using Address for address;
// Only one root bundle can be stored at a time. Once all pool rebalance leaves are executed, a new proposal
// can be submitted.
RootBundle public rootBundleProposal;
// Mapping of L1 token addresses to the associated pool information.
mapping(address => PooledToken) public pooledTokens;
// Stores paths from L1 token + destination ID to destination token. Since different tokens on L1 might map to
// to the same address on different destinations, we hash (L1 token address, destination ID) to
// use as a key that maps to a destination token. This mapping is used to direct pool rebalances from
// HubPool to SpokePool, and also is designed to be used as a lookup for off-chain data workers to determine
// which L1 tokens to relay to SpokePools to refund relayers. The admin can set the "destination token"
// to 0x0 to disable a pool rebalance route and block executeRootBundle() from executing.
mapping(bytes32 => address) private poolRebalanceRoutes;
// Mapping of chainId to the associated adapter and spokePool contracts.
mapping(uint256 => CrossChainContract) public crossChainContracts;
mapping(address => uint256) public unclaimedAccumulatedProtocolFees;
// Whether the bundle proposal process is paused.
bool public paused;
// WETH contract for Ethereum.
WETH9 public immutable weth;
// Helper factory to deploy new LP tokens for enabled L1 tokens
LpTokenFactoryInterface public immutable lpTokenFactory;
// Finder contract for this network.
FinderInterface public immutable finder;
// Address that captures protocol fees. Accumulated protocol fees can be claimed by this address.
address public protocolFeeCaptureAddress;
// Token used to bond the data worker for proposing relayer refund bundles.
IERC20 public bondToken;
// Each root bundle proposal must stay in liveness for this period of time before it can be considered finalized.
// It can be disputed only during this period of time. Defaults to 2 hours, like the rest of the UMA ecosystem.
uint32 public liveness = 7200;
// When root bundles are disputed a price request is enqueued with the DVM to resolve the resolution.
bytes32 public identifier = "IS_ACROSS_V2_BUNDLE_VALID";
// Interest rate payment that scales the amount of pending fees per second paid to LPs. 0.0000015e18 will pay out
// the full amount of fees entitled to LPs in ~ 7.72 days assuming no contract interactions. If someone interacts
// with the contract then the LP rewards are smeared sublinearly over the window (i.e spread over the remaining
// period for each interaction which approximates a decreasing exponential function).
uint256 public lpFeeRatePerSecond = 1500000000000;
// Percentage of lpFees that are captured by the protocol and claimable by the protocolFeeCaptureAddress.
uint256 public protocolFeeCapturePct;
// The computed bond amount as the UMA Store's final fee multiplied by the bondTokenFinalFeeMultiplier.
uint256 public bondAmount;
event Paused(bool indexed isPaused);
event EmergencyRootBundleDeleted(
bytes32 indexed poolRebalanceRoot,
bytes32 indexed relayerRefundRoot,
bytes32 slowRelayRoot,
address indexed proposer
);
event ProtocolFeeCaptureSet(address indexed newProtocolFeeCaptureAddress, uint256 indexed newProtocolFeeCapturePct);
event ProtocolFeesCapturedClaimed(address indexed l1Token, uint256 indexed accumulatedFees);
event BondSet(address indexed newBondToken, uint256 newBondAmount);
event LivenessSet(uint256 newLiveness);
event IdentifierSet(bytes32 newIdentifier);
event CrossChainContractsSet(uint256 l2ChainId, address adapter, address spokePool);
event L1TokenEnabledForLiquidityProvision(address l1Token, address lpToken);
event L2TokenDisabledForLiquidityProvision(address l1Token, address lpToken);
event LiquidityAdded(
address indexed l1Token,
uint256 amount,
uint256 lpTokensMinted,
address indexed liquidityProvider
);
event LiquidityRemoved(
address indexed l1Token,
uint256 amount,
uint256 lpTokensBurnt,
address indexed liquidityProvider
);
event SetPoolRebalanceRoute(
uint256 indexed destinationChainId,
address indexed l1Token,
address indexed destinationToken
);
event SetEnableDepositRoute(
uint256 indexed originChainId,
uint256 indexed destinationChainId,
address indexed originToken,
bool depositsEnabled
);
event ProposeRootBundle(
uint32 challengePeriodEndTimestamp,
uint8 poolRebalanceLeafCount,
uint256[] bundleEvaluationBlockNumbers,
bytes32 indexed poolRebalanceRoot,
bytes32 indexed relayerRefundRoot,
bytes32 slowRelayRoot,
address indexed proposer
);
event RootBundleExecuted(
uint256 groupIndex,
uint256 indexed leafId,
uint256 indexed chainId,
address[] l1Tokens,
uint256[] bundleLpFees,
int256[] netSendAmounts,
int256[] runningBalances,
address indexed caller
);
event SpokePoolAdminFunctionTriggered(uint256 indexed chainId, bytes message);
event RootBundleDisputed(address indexed disputer, uint256 requestTime);
event RootBundleCanceled(address indexed disputer, uint256 requestTime);
modifier noActiveRequests() {
require(!_activeRequest(), "Proposal has unclaimed leaves");
_;
}
modifier unpaused() {
require(!paused, "Contract is paused");
_;
}
modifier zeroOptimisticOracleApproval() {
_;
bondToken.safeApprove(address(_getOptimisticOracle()), 0);
}
/**
* @notice Construct HubPool.
* @param _lpTokenFactory LP Token factory address used to deploy LP tokens for new collateral types.
* @param _finder Finder address.
* @param _weth WETH address.
* @param _timer Timer address.
*/
constructor(
LpTokenFactoryInterface _lpTokenFactory,
FinderInterface _finder,
WETH9 _weth,
address _timer
) Testable(_timer) {
lpTokenFactory = _lpTokenFactory;
finder = _finder;
weth = _weth;
protocolFeeCaptureAddress = owner();
}
/*************************************************
* ADMIN FUNCTIONS *
*************************************************/
/**
* @notice Pauses the bundle proposal and execution process. This is intended to be used during upgrades or when
* something goes awry.
* @param pause true if the call is meant to pause the system, false if the call is meant to unpause it.
*/
function setPaused(bool pause) public onlyOwner nonReentrant {
paused = pause;
emit Paused(pause);
}
/**
* @notice This allows for the deletion of the active proposal in case of emergency.
* @dev This is primarily intended to rectify situations where an unexecutable bundle gets through liveness in the
* case of a non-malicious bug in the proposal/dispute code. Without this function, the contract would be
* indefinitely blocked, migration would be required, and in-progress transfers would never be repaid.
*/
function emergencyDeleteProposal() public onlyOwner nonReentrant {
RootBundle memory _rootBundleProposal = rootBundleProposal;
delete rootBundleProposal;
if (_rootBundleProposal.unclaimedPoolRebalanceLeafCount > 0)
bondToken.safeTransfer(_rootBundleProposal.proposer, bondAmount);
emit EmergencyRootBundleDeleted(
_rootBundleProposal.poolRebalanceRoot,
_rootBundleProposal.relayerRefundRoot,
_rootBundleProposal.slowRelayRoot,
_rootBundleProposal.proposer
);
}
/**
* @notice Sends message to SpokePool from this contract. Callable only by owner.
* @dev This function has permission to call onlyAdmin functions on the SpokePool, so it's imperative that this
* contract only allows the owner to call this method directly or indirectly.
* @param chainId Chain with SpokePool to send message to.
* @param functionData ABI encoded function call to send to SpokePool, but can be any arbitrary data technically.
*/
function relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData)
public
override
onlyOwner
nonReentrant
{
_relaySpokePoolAdminFunction(chainId, functionData);
}
/**
* @notice Sets protocolFeeCaptureAddress and protocolFeeCapturePct. Callable only by owner.
* @param newProtocolFeeCaptureAddress New protocol fee capture address.
* @param newProtocolFeeCapturePct New protocol fee capture %.
*/
function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint256 newProtocolFeeCapturePct)
public
override
onlyOwner
nonReentrant
{
require(newProtocolFeeCapturePct <= 1e18, "Bad protocolFeeCapturePct");
require(newProtocolFeeCaptureAddress != address(0), "Bad protocolFeeCaptureAddress");
protocolFeeCaptureAddress = newProtocolFeeCaptureAddress;
protocolFeeCapturePct = newProtocolFeeCapturePct;
emit ProtocolFeeCaptureSet(newProtocolFeeCaptureAddress, newProtocolFeeCapturePct);
}
/**
* @notice Sets bond token and amount. Callable only by owner.
* @param newBondToken New bond currency.
* @param newBondAmount New bond amount.
*/
function setBond(IERC20 newBondToken, uint256 newBondAmount)
public
override
onlyOwner
noActiveRequests
nonReentrant
{
// Bond should not equal final fee otherwise every proposal will get cancelled in a dispute.
// In practice we expect that bond amounts are set >> final fees so this shouldn't be an inconvenience.
// The only way for the bond amount to be equal to the final fee is if the newBondAmount == 0.
require(newBondAmount != 0, "bond equal to final fee");
// Check that this token is on the whitelist.
AddressWhitelistInterface addressWhitelist = AddressWhitelistInterface(
finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)
);
require(addressWhitelist.isOnWhitelist(address(newBondToken)), "Not on whitelist");
// The bond should be the passed in bondAmount + the final fee.
bondToken = newBondToken;
uint256 _bondAmount = newBondAmount + _getBondTokenFinalFee();
bondAmount = _bondAmount;
emit BondSet(address(newBondToken), _bondAmount);
}
/**
* @notice Sets root bundle proposal liveness period. Callable only by owner.
* @param newLiveness New liveness period.
*/
function setLiveness(uint32 newLiveness) public override onlyOwner nonReentrant {
require(newLiveness > 10 minutes, "Liveness too short");
liveness = newLiveness;
emit LivenessSet(newLiveness);
}
/**
* @notice Sets identifier for root bundle disputes. Callable only by owner.
* @param newIdentifier New identifier.
*/
function setIdentifier(bytes32 newIdentifier) public override onlyOwner noActiveRequests nonReentrant {
IdentifierWhitelistInterface identifierWhitelist = IdentifierWhitelistInterface(
finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)
);
require(identifierWhitelist.isIdentifierSupported(newIdentifier), "Identifier not supported");
identifier = newIdentifier;
emit IdentifierSet(newIdentifier);
}
/**
* @notice Sets cross chain relay helper contracts for L2 chain ID. Callable only by owner.
* @dev We do not block setting the adapter or SpokePool to invalid/zero addresses because we want to allow the
* admin to block relaying roots to the spoke pool for emergency recovery purposes.
* @param l2ChainId Chain to set contracts for.
* @param adapter Adapter used to relay messages and tokens to spoke pool. Deployed on current chain.
* @param spokePool Recipient of relayed messages and tokens on spoke pool. Deployed on l2ChainId.
*/
function setCrossChainContracts(
uint256 l2ChainId,
address adapter,
address spokePool
) public override onlyOwner nonReentrant {
crossChainContracts[l2ChainId] = CrossChainContract(adapter, spokePool);
emit CrossChainContractsSet(l2ChainId, adapter, spokePool);
}
/**
* @notice Store canonical destination token counterpart for l1 token. Callable only by owner.
* @dev Admin can set destinationToken to 0x0 to effectively disable executing any root bundles with leaves
* containing this l1 token + destination chain ID combination.
* @param destinationChainId Destination chain where destination token resides.
* @param l1Token Token enabled for liquidity in this pool, and the L1 counterpart to the destination token on the
* destination chain ID.
* @param destinationToken Destination chain counterpart of L1 token.
*/
function setPoolRebalanceRoute(
uint256 destinationChainId,
address l1Token,
address destinationToken
) public override onlyOwner nonReentrant {
poolRebalanceRoutes[_poolRebalanceRouteKey(l1Token, destinationChainId)] = destinationToken;
emit SetPoolRebalanceRoute(destinationChainId, l1Token, destinationToken);
}
/**
* @notice Sends cross-chain message to SpokePool on originChainId to enable or disable deposit route from that
* SpokePool to another one. Callable only by owner.
* @dev Admin is responsible for ensuring that `originToken` is linked to some L1 token on this contract, via
* poolRebalanceRoutes(), and that this L1 token also has a counterpart on the destination chain. If either
* condition fails, then the deposit will be unrelayable by off-chain relayers because they will not know which
* token to relay to recipients on the destination chain, and data workers wouldn't know which L1 token to send
* to the destination chain to refund the relayer.
* @param originChainId Chain where token deposit occurs.
* @param destinationChainId Chain where token depositor wants to receive funds.
* @param originToken Token sent in deposit.
* @param depositsEnabled Set to true to whitelist this route for deposits, set to false if caller just wants to
* map the origin token + destination ID to the destination token address on the origin chain's SpokePool.
*/
function setDepositRoute(
uint256 originChainId,
uint256 destinationChainId,
address originToken,
bool depositsEnabled
) public override nonReentrant onlyOwner {
_relaySpokePoolAdminFunction(
originChainId,
abi.encodeWithSignature(
"setEnableRoute(address,uint256,bool)",
originToken,
destinationChainId,
depositsEnabled
)
);
emit SetEnableDepositRoute(originChainId, destinationChainId, originToken, depositsEnabled);
}
/**
* @notice Enables LPs to provide liquidity for L1 token. Deploys new LP token for L1 token if appropriate.
* Callable only by owner.
* @param l1Token Token to provide liquidity for.
*/
function enableL1TokenForLiquidityProvision(address l1Token) public override onlyOwner nonReentrant {
// If token is being enabled for the first time, create a new LP token and set the timestamp once. We don't
// want to ever reset this timestamp otherwise fees that have accrued will be lost since the last update. This
// could happen for example if an L1 token is enabled, disabled, and then enabled again.
if (pooledTokens[l1Token].lpToken == address(0)) {
pooledTokens[l1Token].lpToken = lpTokenFactory.createLpToken(l1Token);
pooledTokens[l1Token].lastLpFeeUpdate = uint32(getCurrentTime());
}
pooledTokens[l1Token].isEnabled = true;
emit L1TokenEnabledForLiquidityProvision(l1Token, pooledTokens[l1Token].lpToken);
}
/**
* @notice Disables LPs from providing liquidity for L1 token. Callable only by owner.
* @param l1Token Token to disable liquidity provision for.
*/
function disableL1TokenForLiquidityProvision(address l1Token) public override onlyOwner nonReentrant {
pooledTokens[l1Token].isEnabled = false;
emit L2TokenDisabledForLiquidityProvision(l1Token, pooledTokens[l1Token].lpToken);
}
/**
* @notice Enables the owner of the protocol to haircut reserves in the event of an irrecoverable loss of funds on
* one of the L2s. Consider funds are leant out onto a L2 that dies irrecoverably. This value will offset the
* exchangeRateCurrent such that all LPs receive a pro rata loss of the the reserves. Should be used in conjunction
* with pause logic to prevent LPs from adding/withdrawing liquidity during the haircut process.
* Callable only by owner.
* @param l1Token Token to execute the haircut on.
* @param haircutAmount The amount of reserves to haircut the LPs by.
*/
function haircutReserves(address l1Token, int256 haircutAmount) public onlyOwner nonReentrant {
// Note that we do not call sync first in this method. The Owner should call this manually before haircutting.
// This is done in the event sync is reverting due to too low balanced in the contract relative to bond amount.
pooledTokens[l1Token].utilizedReserves -= haircutAmount;
}
/*************************************************
* LIQUIDITY PROVIDER FUNCTIONS *
*************************************************/
/**
* @notice Deposit liquidity into this contract to earn LP fees in exchange for funding relays on SpokePools.
* Caller is essentially loaning their funds to be sent from this contract to the SpokePool, where it will be used
* to repay a relayer, and ultimately receives their loan back after the tokens are bridged back to this contract
* via the canonical token bridge. Then, the caller's loans are used again. This loan cycle repeats continuously
* and the caller, or "liquidity provider" earns a continuous fee for their credit that they are extending relayers.
* @notice Caller will receive an LP token representing their share of this pool. The LP token's redemption value
* increments from the time that they enter the pool to reflect their accrued fees.
* @notice The caller of this function must approve this contract to spend l1TokenAmount of l1Token.
* @param l1Token Token to deposit into this contract.
* @param l1TokenAmount Amount of liquidity to provide.
*/
function addLiquidity(address l1Token, uint256 l1TokenAmount) public payable override nonReentrant unpaused {
require(pooledTokens[l1Token].isEnabled, "Token not enabled");
// If this is the weth pool and the caller sends msg.value then the msg.value must match the l1TokenAmount.
// Else, msg.value must be set to 0.
require(((address(weth) == l1Token) && msg.value == l1TokenAmount) || msg.value == 0, "Bad msg.value");
// Since _exchangeRateCurrent() reads this contract's balance and updates contract state using it, it must be
// first before transferring any tokens to this contract to ensure synchronization.
uint256 lpTokensToMint = (l1TokenAmount * 1e18) / _exchangeRateCurrent(l1Token);
pooledTokens[l1Token].liquidReserves += l1TokenAmount;
ExpandedIERC20(pooledTokens[l1Token].lpToken).mint(msg.sender, lpTokensToMint);
if (address(weth) == l1Token && msg.value > 0) WETH9(address(l1Token)).deposit{ value: msg.value }();
else IERC20(l1Token).safeTransferFrom(msg.sender, address(this), l1TokenAmount);
emit LiquidityAdded(l1Token, l1TokenAmount, lpTokensToMint, msg.sender);
}
/**
* @notice Burns LP share to redeem for underlying l1Token original deposit amount plus fees.
* @param l1Token Token to redeem LP share for.
* @param lpTokenAmount Amount of LP tokens to burn. Exchange rate between L1 token and LP token can be queried
* via public exchangeRateCurrent method.
* @param sendEth Set to True if L1 token is WETH and user wants to receive ETH. Note that if caller
* is a contract, then the contract should have a way to receive ETH if this value is set to True. Similarly,
* if this value is set to False, then the calling contract should have a way to handle WETH.
*/
function removeLiquidity(
address l1Token,
uint256 lpTokenAmount,
bool sendEth
) public override nonReentrant unpaused {
require(address(weth) == l1Token || !sendEth, "Cant send eth");
uint256 l1TokensToReturn = (lpTokenAmount * _exchangeRateCurrent(l1Token)) / 1e18;
ExpandedIERC20(pooledTokens[l1Token].lpToken).burnFrom(msg.sender, lpTokenAmount);
// Note this method does not make any liquidity utilization checks before letting the LP redeem their LP tokens.
// If they try access more funds than available (i.e l1TokensToReturn > liquidReserves) this will underflow.
pooledTokens[l1Token].liquidReserves -= l1TokensToReturn;
if (sendEth) {
weth.withdraw(l1TokensToReturn);
Address.sendValue(payable(msg.sender), l1TokensToReturn); // This will revert if the caller is a contract that does not implement a fallback function.
} else {
IERC20(address(l1Token)).safeTransfer(msg.sender, l1TokensToReturn);
}
emit LiquidityRemoved(l1Token, l1TokensToReturn, lpTokenAmount, msg.sender);
}
/**
* @notice Returns exchange rate of L1 token to LP token.
* @param l1Token L1 token redeemable by burning LP token.
* @return Amount of L1 tokens redeemable for 1 unit LP token.
*/
function exchangeRateCurrent(address l1Token) public override nonReentrant returns (uint256) {
return _exchangeRateCurrent(l1Token);
}
/**
* @notice Returns % of liquid reserves currently being "used" and sitting in SpokePools.
* @param l1Token L1 token to query utilization for.
* @return % of liquid reserves currently being "used" and sitting in SpokePools.
*/
function liquidityUtilizationCurrent(address l1Token) public override nonReentrant returns (uint256) {
return _liquidityUtilizationPostRelay(l1Token, 0);
}
/**
* @notice Returns % of liquid reserves currently being "used" and sitting in SpokePools and accounting for
* relayedAmount of tokens to be withdrawn from the pool.
* @param l1Token L1 token to query utilization for.
* @param relayedAmount The higher this amount, the higher the utilization.
* @return % of liquid reserves currently being "used" and sitting in SpokePools plus the relayedAmount.
*/
function liquidityUtilizationPostRelay(address l1Token, uint256 relayedAmount)
public
nonReentrant
returns (uint256)
{
return _liquidityUtilizationPostRelay(l1Token, relayedAmount);
}
/**
* @notice Synchronize any balance changes in this contract with the utilized & liquid reserves. This should be done
* at the conclusion of a L2->L1 token transfer via the canonical token bridge, when this contract's reserves do not
* reflect its true balance due to new tokens being dropped onto the contract at the conclusion of a bridging action.
*/
function sync(address l1Token) public override nonReentrant {
_sync(l1Token);
}
/*************************************************
* DATA WORKER FUNCTIONS *
*************************************************/
/**
* @notice Publish a new root bundle along with all of the block numbers that the merkle roots are relevant for.
* This is used to aid off-chain validators in evaluating the correctness of this bundle. Caller stakes a bond that
* can be slashed if the root bundle proposal is invalid, and they will receive it back if accepted.
* @notice After proposeRootBundle is called, if the any props are wrong then this proposal can be challenged.
* Once the challenge period passes, then the roots are no longer disputable, and only executeRootBundle can be
* called; moreover, this method can't be called again until all leaves are executed.
* @param bundleEvaluationBlockNumbers should contain the latest block number for all chains, even if there are no
* relays contained on some of them. The usage of this variable should be defined in an off chain UMIP.
* @notice The caller of this function must approve this contract to spend bondAmount of bondToken.
* @param poolRebalanceLeafCount Number of leaves contained in pool rebalance root. Max is # of whitelisted chains.
* @param poolRebalanceRoot Pool rebalance root containing leaves that sends tokens from this contract to SpokePool.
* @param relayerRefundRoot Relayer refund root to publish to SpokePool where a data worker can execute leaves to
* refund relayers on their chosen refund chainId.
* @param slowRelayRoot Slow relay root to publish to Spoke Pool where a data worker can execute leaves to
* fulfill slow relays.
*/
function proposeRootBundle(
uint256[] calldata bundleEvaluationBlockNumbers,
uint8 poolRebalanceLeafCount,
bytes32 poolRebalanceRoot,
bytes32 relayerRefundRoot,
bytes32 slowRelayRoot
) public override nonReentrant noActiveRequests unpaused {
// Note: this is to prevent "empty block" style attacks where someone can make empty proposals that are
// technically valid but not useful. This could also potentially be enforced at the UMIP-level.
require(poolRebalanceLeafCount > 0, "Bundle must have at least 1 leaf");
uint32 challengePeriodEndTimestamp = uint32(getCurrentTime()) + liveness;
delete rootBundleProposal; // Only one bundle of roots can be executed at a time. Delete the previous bundle.
rootBundleProposal.challengePeriodEndTimestamp = challengePeriodEndTimestamp;
rootBundleProposal.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount;
rootBundleProposal.poolRebalanceRoot = poolRebalanceRoot;
rootBundleProposal.relayerRefundRoot = relayerRefundRoot;
rootBundleProposal.slowRelayRoot = slowRelayRoot;
rootBundleProposal.proposer = msg.sender;
// Pull bondAmount of bondToken from the caller.
bondToken.safeTransferFrom(msg.sender, address(this), bondAmount);
emit ProposeRootBundle(
challengePeriodEndTimestamp,
poolRebalanceLeafCount,
bundleEvaluationBlockNumbers,
poolRebalanceRoot,
relayerRefundRoot,
slowRelayRoot,
msg.sender
);
}
/**
* @notice Executes a pool rebalance leaf as part of the currently published root bundle. Will bridge any tokens
* from this contract to the SpokePool designated in the leaf, and will also publish relayer refund and slow
* relay roots to the SpokePool on the network specified in the leaf.
* @dev In some cases, will instruct spokePool to send funds back to L1.
* @param chainId ChainId number of the target spoke pool on which the bundle is executed.
* @param groupIndex If set to 0, then relay roots to SpokePool via cross chain bridge. Used by off-chain validator
* to organize leaves with the same chain ID and also set which leaves should result in relayed messages.
* @param bundleLpFees Array representing the total LP fee amount per token in this bundle for all bundled relays.
* @param netSendAmounts Array representing the amount of tokens to send to the SpokePool on the target chainId.
* @param runningBalances Array used to track any unsent tokens that are not included in the netSendAmounts.
* @param leafId Index of this executed leaf within the poolRebalance tree.
* @param l1Tokens Array of all the tokens associated with the bundleLpFees, nedSendAmounts and runningBalances.
* @param proof Inclusion proof for this leaf in pool rebalance root in root bundle.
*/
function executeRootBundle(
uint256 chainId,
uint256 groupIndex,
uint256[] memory bundleLpFees,
int256[] memory netSendAmounts,
int256[] memory runningBalances,
uint8 leafId,
address[] memory l1Tokens,
bytes32[] calldata proof
) public nonReentrant unpaused {
require(getCurrentTime() > rootBundleProposal.challengePeriodEndTimestamp, "Not passed liveness");
// Verify the leafId in the poolRebalanceLeaf has not yet been claimed.
require(!MerkleLib.isClaimed1D(rootBundleProposal.claimedBitMap, leafId), "Already claimed");
// Verify the props provided generate a leaf that, along with the proof, are included in the merkle root.
require(
MerkleLib.verifyPoolRebalance(
rootBundleProposal.poolRebalanceRoot,
PoolRebalanceLeaf({
chainId: chainId,
groupIndex: groupIndex,
bundleLpFees: bundleLpFees,
netSendAmounts: netSendAmounts,
runningBalances: runningBalances,
leafId: leafId,
l1Tokens: l1Tokens
}),
proof
),
"Bad Proof"
);
// Grouping code that uses adapter and spokepool to avoid stack too deep warning.
// Get cross chain helpers for leaf's destination chain ID. This internal method will revert if either helper
// is set improperly.
(address adapter, address spokePool) = _getInitializedCrossChainContracts(chainId);
// Set the leafId in the claimed bitmap.
rootBundleProposal.claimedBitMap = MerkleLib.setClaimed1D(rootBundleProposal.claimedBitMap, leafId);
// Decrement the unclaimedPoolRebalanceLeafCount.
--rootBundleProposal.unclaimedPoolRebalanceLeafCount;
// Relay each L1 token to destination chain.
// Note: if any of the keccak256(l1Tokens, chainId) combinations are not mapped to a destination token address,
// then this internal method will revert. In this case the admin will have to associate a destination token
// with each l1 token. If the destination token mapping was missing at the time of the proposal, we assume
// that the root bundle would have been disputed because the off-chain data worker would have been unable to
// determine if the relayers used the correct destination token for a given origin token.
_sendTokensToChainAndUpdatePooledTokenTrackers(
adapter,
spokePool,
chainId,
l1Tokens,
netSendAmounts,
bundleLpFees
);
// Check bool used by data worker to prevent relaying redundant roots to SpokePool.
if (groupIndex == 0) {
// Relay root bundles to spoke pool on destination chain by
// performing delegatecall to use the adapter's code with this contract's context.
// We are ok with this low-level call since the adapter address is set by the admin and we've
// already checked that its not the zero address.
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = adapter.delegatecall(
abi.encodeWithSignature(
"relayMessage(address,bytes)",
spokePool, // target. This should be the spokePool on the L2.
abi.encodeWithSignature(
"relayRootBundle(bytes32,bytes32)",
rootBundleProposal.relayerRefundRoot,
rootBundleProposal.slowRelayRoot
) // message
)
);
require(success, "delegatecall failed");
}
// Transfer the bondAmount back to the proposer, if this the last executed leaf. Only sending this once all
// leaves have been executed acts to force the data worker to execute all bundles or they won't receive their bond.
if (rootBundleProposal.unclaimedPoolRebalanceLeafCount == 0)
bondToken.safeTransfer(rootBundleProposal.proposer, bondAmount);
emit RootBundleExecuted(
groupIndex,
leafId,
chainId,
l1Tokens,
bundleLpFees,
netSendAmounts,
runningBalances,
msg.sender
);
}
/**
* @notice Caller stakes a bond to dispute the current root bundle proposal assuming it has not passed liveness
* yet. The proposal is deleted, allowing a follow-up proposal to be submitted, and the dispute is sent to the
* optimistic oracle to be adjudicated. Can only be called within the liveness period of the current proposal.
* @notice The caller of this function must approve this contract to spend bondAmount of l1Token.
*/
function disputeRootBundle() public nonReentrant zeroOptimisticOracleApproval {
uint32 currentTime = uint32(getCurrentTime());
require(currentTime <= rootBundleProposal.challengePeriodEndTimestamp, "Request passed liveness");
// Request price from OO and dispute it.
uint256 finalFee = _getBondTokenFinalFee();
// This method will request a price from the OO and dispute it. Note that we set the ancillary data to
// the empty string (""). The root bundle that is being disputed was the most recently proposed one with a
// block number less than or equal to the dispute block time. All of this root bundle data can be found in
// the ProposeRootBundle event params. Moreover, the optimistic oracle will stamp the requester's address
// (i.e. this contract address) meaning that ancillary data for a dispute originating from another HubPool
// will always be distinct from a dispute originating from this HubPool. Moreover, since
// bundleEvaluationNumbers for a root bundle proposal are not stored in this contract, DVM voters will always
// have to look up the ProposeRootBundle event to evaluate a dispute, therefore there is no point emitting extra
// data in this ancillary data that is already included in the ProposeRootBundle event.
// If the finalFee is larger than the bond amount, the bond amount needs to be reset before a request can go
// through. Cancel to avoid a revert. Similarly, if the final fee == bond amount, then the proposer bond
// set in the optimistic oracle would be 0. The optimistic oracle would then default the bond to be equal
// to the final fee, which would mean that the allowance set to the bondAmount would be insufficient and the
// requestAndProposePriceFor() call would revert. Source: https://github.com/UMAprotocol/protocol/blob/5b37ea818a28479c01e458389a83c3e736306b17/packages/core/contracts/oracle/implementation/SkinnyOptimisticOracle.sol#L321
if (finalFee >= bondAmount) {
_cancelBundle();
return;
}
SkinnyOptimisticOracleInterface optimisticOracle = _getOptimisticOracle();
// Only approve exact tokens to avoid more tokens than expected being pulled into the OptimisticOracle.
bondToken.safeIncreaseAllowance(address(optimisticOracle), bondAmount);
try
optimisticOracle.requestAndProposePriceFor(
identifier,
currentTime,
"",
bondToken,
// Set reward to 0, since we'll settle proposer reward payouts directly from this contract after a root
// proposal has passed the challenge period.
0,
// Set the Optimistic oracle proposer bond for the request. We can assume that bondAmount > finalFee.
bondAmount - finalFee,
// Set the Optimistic oracle liveness for the price request.
liveness,
rootBundleProposal.proposer,
// Canonical value representing "True"; i.e. the proposed relay is valid.
int256(1e18)
)
returns (uint256) {
// Ensure that approval == 0 after the call so the increaseAllowance call below doesn't allow more tokens
// to transfer than intended.
bondToken.safeApprove(address(optimisticOracle), 0);
} catch {
// Cancel the bundle since the proposal failed.
_cancelBundle();
return;
}
// Dispute the request that we just sent.
SkinnyOptimisticOracleInterface.Request memory ooPriceRequest = SkinnyOptimisticOracleInterface.Request({
proposer: rootBundleProposal.proposer,
disputer: address(0),
currency: bondToken,
settled: false,
proposedPrice: int256(1e18),
resolvedPrice: 0,
expirationTime: currentTime + liveness,
reward: 0,
finalFee: finalFee,
bond: bondAmount - finalFee,
customLiveness: liveness
});
// Finally, delete the state pertaining to the active proposal so that another proposer can submit a new bundle.
delete rootBundleProposal;
bondToken.safeTransferFrom(msg.sender, address(this), bondAmount);
bondToken.safeIncreaseAllowance(address(optimisticOracle), bondAmount);
optimisticOracle.disputePriceFor(identifier, currentTime, "", ooPriceRequest, msg.sender, address(this));
emit RootBundleDisputed(msg.sender, currentTime);
}
/**
* @notice Send unclaimed accumulated protocol fees to fee capture address.
* @param l1Token Token whose protocol fees the caller wants to disburse.
*/
function claimProtocolFeesCaptured(address l1Token) public override nonReentrant {
uint256 _unclaimedAccumulatedProtocolFees = unclaimedAccumulatedProtocolFees[l1Token];
unclaimedAccumulatedProtocolFees[l1Token] = 0;
IERC20(l1Token).safeTransfer(protocolFeeCaptureAddress, _unclaimedAccumulatedProtocolFees);
emit ProtocolFeesCapturedClaimed(l1Token, _unclaimedAccumulatedProtocolFees);
}
/**
* @notice Conveniently queries which destination token is mapped to the hash of an l1 token + destination chain ID.
* @dev Admin must be considerate to the compatibility of originToken and destinationToken within the protocol. Some
* token implementations will not function correctly within the Across v2 system. For example ERC20s that charge
* fees will break internal accounting, ERC777 can cause some functions to revert and upgradable tokens can pose
* risks if the implementation is shifted between whitelisting and usage.
* @dev If the pool rebalance route is not whitelisted then this will return address(0).
* @param destinationChainId Where destination token is deployed.
* @param l1Token Ethereum version token.
* @return destinationToken address The destination token that is sent to spoke pools after this contract bridges
* the l1Token to the destination chain.
*/
function poolRebalanceRoute(uint256 destinationChainId, address l1Token)
external
view
override
returns (address destinationToken)
{
return poolRebalanceRoutes[_poolRebalanceRouteKey(l1Token, destinationChainId)];
}
/**
* @notice This function allows a caller to load the contract with raw ETH to perform L2 calls. This is needed for
* Arbitrum calls, but may also be needed for others.
* @dev This function cannot be included in a multicall transaction call because it is payable. A realistic
* situation where this might be an issue is if the caller is executing a PoolRebalanceLeaf that needs to relay
* messages to Arbitrum. Relaying messages to Arbitrum requires that this contract has an ETH balance, so in this
* case the caller would need to pre-load this contract with ETH before multicall-executing the leaf.
*/
function loadEthForL2Calls() public payable override {
/* solhint-disable-line no-empty-blocks */
}
/*************************************************
* INTERNAL FUNCTIONS *
*************************************************/
// Called when a dispute fails due to parameter changes. This effectively resets the state and cancels the request
// with no loss of funds, thereby enabling a new bundle to be added.
function _cancelBundle() internal {
bondToken.transfer(rootBundleProposal.proposer, bondAmount);
delete rootBundleProposal;
emit RootBundleCanceled(msg.sender, getCurrentTime());
}
function _getOptimisticOracle() internal view returns (SkinnyOptimisticOracleInterface) {
return
SkinnyOptimisticOracleInterface(finder.getImplementationAddress(OracleInterfaces.SkinnyOptimisticOracle));
}
function _getBondTokenFinalFee() internal view returns (uint256) {
return
StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store))
.computeFinalFee(address(bondToken))
.rawValue;
}
// Note this method does a lot and wraps together the sending of tokens and updating the pooled token trackers. This
// is done as a gas saving so we don't need to iterate over the l1Tokens multiple times.
function _sendTokensToChainAndUpdatePooledTokenTrackers(
address adapter,
address spokePool,
uint256 chainId,
address[] memory l1Tokens,
int256[] memory netSendAmounts,
uint256[] memory bundleLpFees
) internal {
uint256 length = l1Tokens.length;
for (uint256 i = 0; i < length; ) {
address l1Token = l1Tokens[i];
// Validate the L1 -> L2 token route is stored. If it is not then the output of the bridging action
// could send tokens to the 0x0 address on the L2.
address l2Token = poolRebalanceRoutes[_poolRebalanceRouteKey(l1Token, chainId)];
require(l2Token != address(0), "Route not whitelisted");
// If the net send amount for this token is positive then: 1) send tokens from L1->L2 to facilitate the L2
// relayer refund, 2) Update the liquidity trackers for the associated pooled tokens.
if (netSendAmounts[i] > 0) {
// Perform delegatecall to use the adapter's code with this contract's context. Opt for delegatecall's
// complexity in exchange for lower gas costs.
// We are ok with this low-level call since the adapter address is set by the admin and we've
// already checked that its not the zero address.
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = adapter.delegatecall(
abi.encodeWithSignature(
"relayTokens(address,address,uint256,address)",
l1Token, // l1Token.
l2Token, // l2Token.
uint256(netSendAmounts[i]), // amount.
spokePool // to. This should be the spokePool.
)
);
require(success, "delegatecall failed");
// Liquid reserves is decreased by the amount sent. utilizedReserves is increased by the amount sent.
pooledTokens[l1Token].utilizedReserves += netSendAmounts[i];
pooledTokens[l1Token].liquidReserves -= uint256(netSendAmounts[i]);
}
// Allocate LP fees and protocol fees from the bundle to the associated pooled token trackers.
_allocateLpAndProtocolFees(l1Token, bundleLpFees[i]);
// L1 tokens length won't be > types(uint256).length, so use unchecked block to save gas. Based on the
// stress test results in /test/gas-analytics/HubPool.RootExecution.ts, the UMIP should limit the L1 token
// count in valid proposals to be ~100 so any PoolRebalanceLeaves with > 100 l1Tokens should not make it
// to this stage.
unchecked {
++i;
}
}
}
function _exchangeRateCurrent(address l1Token) internal returns (uint256) {
PooledToken storage pooledToken = pooledTokens[l1Token]; // Note this is storage so the state can be modified.
uint256 lpTokenTotalSupply = IERC20(pooledToken.lpToken).totalSupply();
if (lpTokenTotalSupply == 0) return 1e18; // initial rate is 1:1 between LP tokens and collateral.
// First, update fee counters and local accounting of finalized transfers from L2 -> L1.
_updateAccumulatedLpFees(pooledToken); // Accumulate all allocated fees from the last time this method was called.
_sync(l1Token); // Fetch any balance changes due to token bridging finalization and factor them in.
// ExchangeRate := (liquidReserves + utilizedReserves - undistributedLpFees) / lpTokenSupply
// Both utilizedReserves and undistributedLpFees contain assigned LP fees. UndistributedLpFees is gradually
// decreased over the smear duration using _updateAccumulatedLpFees. This means that the exchange rate will
// gradually increase over time as undistributedLpFees goes to zero.
// utilizedReserves can be negative. If this is the case, then liquidReserves is offset by an equal
// and opposite size. LiquidReserves + utilizedReserves will always be larger than undistributedLpFees so this
// int will always be positive so there is no risk in underflow in type casting in the return line.
int256 numerator = int256(pooledToken.liquidReserves) +
pooledToken.utilizedReserves -
int256(pooledToken.undistributedLpFees);
return (uint256(numerator) * 1e18) / lpTokenTotalSupply;
}
// Update internal fee counters by adding in any accumulated fees from the last time this logic was called.
function _updateAccumulatedLpFees(PooledToken storage pooledToken) internal {
uint256 accumulatedFees = _getAccumulatedFees(pooledToken.undistributedLpFees, pooledToken.lastLpFeeUpdate);
pooledToken.undistributedLpFees -= accumulatedFees;
pooledToken.lastLpFeeUpdate = uint32(getCurrentTime());
}
// Calculate the unallocated accumulatedFees from the last time the contract was called.
function _getAccumulatedFees(uint256 undistributedLpFees, uint256 lastLpFeeUpdate) internal view returns (uint256) {
// accumulatedFees := min(undistributedLpFees * lpFeeRatePerSecond * timeFromLastInteraction, undistributedLpFees)
// The min acts to pay out all fees in the case the equation returns more than the remaining fees.
uint256 timeFromLastInteraction = getCurrentTime() - lastLpFeeUpdate;
uint256 maxUndistributedLpFees = (undistributedLpFees * lpFeeRatePerSecond * timeFromLastInteraction) / (1e18);
return maxUndistributedLpFees < undistributedLpFees ? maxUndistributedLpFees : undistributedLpFees;
}
function _sync(address l1Token) internal {
// Check if the l1Token balance of the contract is greater than the liquidReserves. If it is then the bridging
// action from L2 -> L1 has concluded and the local accounting can be updated.
// Note: this calculation must take into account the bond when it's acting on the bond token and there's an
// active request.
uint256 balance = IERC20(l1Token).balanceOf(address(this));
uint256 balanceSansBond = l1Token == address(bondToken) && _activeRequest() ? balance - bondAmount : balance;
if (balanceSansBond > pooledTokens[l1Token].liquidReserves) {
// Note the numerical operation below can send utilizedReserves to negative. This can occur when tokens are
// dropped onto the contract, exceeding the liquidReserves.
pooledTokens[l1Token].utilizedReserves -= int256(balanceSansBond - pooledTokens[l1Token].liquidReserves);
pooledTokens[l1Token].liquidReserves = balanceSansBond;
}
}
function _liquidityUtilizationPostRelay(address l1Token, uint256 relayedAmount) internal returns (uint256) {
_sync(l1Token); // Fetch any balance changes due to token bridging finalization and factor them in.
// liquidityUtilizationRatio := (relayedAmount + max(utilizedReserves,0)) / (liquidReserves + max(utilizedReserves,0))
// UtilizedReserves has a dual meaning: if it's greater than zero then it represents funds pending in the bridge
// that will flow from L2 to L1. In this case, we can use it normally in the equation. However, if it is
// negative, then it is already counted in liquidReserves. This occurs if tokens are transferred directly to the
// contract. In this case, ignore it as it is captured in liquid reserves and has no meaning in the numerator.
PooledToken memory pooledL1Token = pooledTokens[l1Token];
uint256 flooredUtilizedReserves = pooledL1Token.utilizedReserves > 0
? uint256(pooledL1Token.utilizedReserves) // If positive: take the uint256 cast utilizedReserves.
: 0; // Else, if negative, then the is already captured in liquidReserves and should be ignored.
uint256 numerator = relayedAmount + flooredUtilizedReserves;
uint256 denominator = pooledL1Token.liquidReserves + flooredUtilizedReserves;
// If the denominator equals zero, return 1e18 (max utilization).
if (denominator == 0) return 1e18;
// In all other cases, return the utilization ratio.
return (numerator * 1e18) / denominator;
}
function _allocateLpAndProtocolFees(address l1Token, uint256 bundleLpFees) internal {
// Calculate the fraction of bundledLpFees that are allocated to the protocol and to the LPs.
uint256 protocolFeesCaptured = (bundleLpFees * protocolFeeCapturePct) / 1e18;
uint256 lpFeesCaptured = bundleLpFees - protocolFeesCaptured;
// Assign any LP fees included into the bundle to the pooled token. These LP fees are tracked in the
// undistributedLpFees and within the utilizedReserves. undistributedLpFees is gradually decreased
// over the smear duration to give the LPs their rewards over a period of time. Adding to utilizedReserves
// acts to track these rewards after the smear duration. See _exchangeRateCurrent for more details.
if (lpFeesCaptured > 0) {
pooledTokens[l1Token].undistributedLpFees += lpFeesCaptured;
pooledTokens[l1Token].utilizedReserves += int256(lpFeesCaptured);
}
// If there are any protocol fees, allocate them to the unclaimed protocol tracker amount.
if (protocolFeesCaptured > 0) unclaimedAccumulatedProtocolFees[l1Token] += protocolFeesCaptured;
}
function _relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData) internal {
(address adapter, address spokePool) = _getInitializedCrossChainContracts(chainId);
// Perform delegatecall to use the adapter's code with this contract's context.
// We are ok with this low-level call since the adapter address is set by the admin and we've
// already checked that its not the zero address.
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = adapter.delegatecall(
abi.encodeWithSignature(
"relayMessage(address,bytes)",
spokePool, // target. This should be the spokePool on the L2.
functionData
)
);
require(success, "delegatecall failed");
emit SpokePoolAdminFunctionTriggered(chainId, functionData);
}
function _poolRebalanceRouteKey(address l1Token, uint256 destinationChainId) internal pure returns (bytes32) {
return keccak256(abi.encode(l1Token, destinationChainId));
}
function _getInitializedCrossChainContracts(uint256 chainId)
internal
view
returns (address adapter, address spokePool)
{
adapter = crossChainContracts[chainId].adapter;
spokePool = crossChainContracts[chainId].spokePool;
require(spokePool != address(0), "SpokePool not initialized");
require(adapter.isContract(), "Adapter not initialized");
}
function _activeRequest() internal view returns (bool) {
return rootBundleProposal.unclaimedPoolRebalanceLeafCount != 0;
}
// If functionCallStackOriginatesFromOutsideThisContract is true then this was called by the callback function
// by dropping ETH onto the contract. In this case, deposit the ETH into WETH. This would happen if ETH was sent
// over the optimism bridge, for example. If false then this was set as a result of unwinding LP tokens, with the
// intention of sending ETH to the LP. In this case, do nothing as we intend on sending the ETH to the LP.
function _depositEthToWeth() internal {
if (functionCallStackOriginatesFromOutsideThisContract()) weth.deposit{ value: msg.value }();
}
// Added to enable the HubPool to receive ETH. This will occur both when the HubPool unwraps WETH to send to LPs and
// when ETH is sent over the canonical Optimism bridge, which sends ETH.
fallback() external payable {
_depositEthToWeth();
}
receive() external payable {
_depositEthToWeth();
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
import "./SpokePoolInterface.sol";
import "./HubPoolInterface.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
/**
* @notice Library to help with merkle roots, proofs, and claims.
*/
library MerkleLib {
/**
* @notice Verifies that a repayment is contained within a merkle root.
* @param root the merkle root.
* @param rebalance the rebalance struct.
* @param proof the merkle proof.
* @return bool to signal if the pool rebalance proof correctly shows inclusion of the rebalance within the tree.
*/
function verifyPoolRebalance(
bytes32 root,
HubPoolInterface.PoolRebalanceLeaf memory rebalance,
bytes32[] memory proof
) internal pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(rebalance)));
}
/**
* @notice Verifies that a relayer refund is contained within a merkle root.
* @param root the merkle root.
* @param refund the refund struct.
* @param proof the merkle proof.
* @return bool to signal if the relayer refund proof correctly shows inclusion of the refund within the tree.
*/
function verifyRelayerRefund(
bytes32 root,
SpokePoolInterface.RelayerRefundLeaf memory refund,
bytes32[] memory proof
) internal pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(refund)));
}
/**
* @notice Verifies that a distribution is contained within a merkle root.
* @param root the merkle root.
* @param slowRelayFulfillment the relayData fulfillment struct.
* @param proof the merkle proof.
* @return bool to signal if the slow relay's proof correctly shows inclusion of the slow relay within the tree.
*/
function verifySlowRelayFulfillment(
bytes32 root,
SpokePoolInterface.RelayData memory slowRelayFulfillment,
bytes32[] memory proof
) internal pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(slowRelayFulfillment)));
}
// The following functions are primarily copied from
// https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol with minor changes.
/**
* @notice Tests whether a claim is contained within a claimedBitMap mapping.
* @param claimedBitMap a simple uint256 mapping in storage used as a bitmap.
* @param index the index to check in the bitmap.
* @return bool indicating if the index within the claimedBitMap has been marked as claimed.
*/
function isClaimed(mapping(uint256 => uint256) storage claimedBitMap, uint256 index) internal view returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}
/**
* @notice Marks an index in a claimedBitMap as claimed.
* @param claimedBitMap a simple uint256 mapping in storage used as a bitmap.
* @param index the index to mark in the bitmap.
*/
function setClaimed(mapping(uint256 => uint256) storage claimedBitMap, uint256 index) internal {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}
/**
* @notice Tests whether a claim is contained within a 1D claimedBitMap mapping.
* @param claimedBitMap a simple uint256 value, encoding a 1D bitmap.
* @param index the index to check in the bitmap. Uint8 type enforces that index can't be > 255.
* @return bool indicating if the index within the claimedBitMap has been marked as claimed.
*/
function isClaimed1D(uint256 claimedBitMap, uint8 index) internal pure returns (bool) {
uint256 mask = (1 << index);
return claimedBitMap & mask == mask;
}
/**
* @notice Marks an index in a claimedBitMap as claimed.
* @param claimedBitMap a simple uint256 mapping in storage used as a bitmap. Uint8 type enforces that index
* can't be > 255.
* @param index the index to mark in the bitmap.
* @return uint256 representing the modified input claimedBitMap with the index set to true.
*/
function setClaimed1D(uint256 claimedBitMap, uint8 index) internal pure returns (uint256) {
return claimedBitMap | (1 << index % 256);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
import "./interfaces/AdapterInterface.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice Concise list of functions in HubPool implementation.
*/
interface HubPoolInterface {
// This leaf is meant to be decoded in the HubPool to rebalance tokens between HubPool and SpokePool.
struct PoolRebalanceLeaf {
// This is used to know which chain to send cross-chain transactions to (and which SpokePool to send to).
uint256 chainId;
// Total LP fee amount per token in this bundle, encompassing all associated bundled relays.
uint256[] bundleLpFees;
// Represents the amount to push to or pull from the SpokePool. If +, the pool pays the SpokePool. If negative
// the SpokePool pays the HubPool. There can be arbitrarily complex rebalancing rules defined offchain. This
// number is only nonzero when the rules indicate that a rebalancing action should occur. When a rebalance does
// occur, runningBalances must be set to zero for this token and netSendAmounts should be set to the previous
// runningBalances + relays - deposits in this bundle. If non-zero then it must be set on the SpokePool's
// RelayerRefundLeaf amountToReturn as -1 * this value to show if funds are being sent from or to the SpokePool.
int256[] netSendAmounts;
// This is only here to be emitted in an event to track a running unpaid balance between the L2 pool and the L1
// pool. A positive number indicates that the HubPool owes the SpokePool funds. A negative number indicates that
// the SpokePool owes the HubPool funds. See the comment above for the dynamics of this and netSendAmounts.
int256[] runningBalances;
// Used by data worker to mark which leaves should relay roots to SpokePools, and to otherwise organize leaves.
// For example, each leaf should contain all the rebalance information for a single chain, but in the case where
// the list of l1Tokens is very large such that they all can't fit into a single leaf that can be executed under
// the block gas limit, then the data worker can use this groupIndex to organize them. Any leaves with
// a groupIndex equal to 0 will relay roots to the SpokePool, so the data worker should ensure that only one
// leaf for a specific chainId should have a groupIndex equal to 0.
uint256 groupIndex;
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint8 leafId;
// The bundleLpFees, netSendAmounts, and runningBalances are required to be the same length. They are parallel
// arrays for the given chainId and should be ordered by the l1Tokens field. All whitelisted tokens with nonzero
// relays on this chain in this bundle in the order of whitelisting.
address[] l1Tokens;
}
// A data worker can optimistically store several merkle roots on this contract by staking a bond and calling
// proposeRootBundle. By staking a bond, the data worker is alleging that the merkle roots all contain valid leaves
// that can be executed later to:
// - Send funds from this contract to a SpokePool or vice versa
// - Send funds from a SpokePool to Relayer as a refund for a relayed deposit
// - Send funds from a SpokePool to a deposit recipient to fulfill a "slow" relay
// Anyone can dispute this struct if the merkle roots contain invalid leaves before the
// challengePeriodEndTimestamp. Once the expiration timestamp is passed, executeRootBundle to execute a leaf
// from the poolRebalanceRoot on this contract and it will simultaneously publish the relayerRefundRoot and
// slowRelayRoot to a SpokePool. The latter two roots, once published to the SpokePool, contain
// leaves that can be executed on the SpokePool to pay relayers or recipients.
struct RootBundle {
// Contains leaves instructing this contract to send funds to SpokePools.
bytes32 poolRebalanceRoot;
// Relayer refund merkle root to be published to a SpokePool.
bytes32 relayerRefundRoot;
// Slow relay merkle root to be published to a SpokePool.
bytes32 slowRelayRoot;
// This is a 1D bitmap, with max size of 256 elements, limiting us to 256 chainsIds.
uint256 claimedBitMap;
// Proposer of this root bundle.
address proposer;
// Number of pool rebalance leaves to execute in the poolRebalanceRoot. After this number
// of leaves are executed, a new root bundle can be proposed
uint8 unclaimedPoolRebalanceLeafCount;
// When root bundle challenge period passes and this root bundle becomes executable.
uint32 challengePeriodEndTimestamp;
}
// Each whitelisted L1 token has an associated pooledToken struct that contains all information used to track the
// cumulative LP positions and if this token is enabled for deposits.
struct PooledToken {
// LP token given to LPs of a specific L1 token.
address lpToken;
// True if accepting new LP's.
bool isEnabled;
// Timestamp of last LP fee update.
uint32 lastLpFeeUpdate;
// Number of LP funds sent via pool rebalances to SpokePools and are expected to be sent
// back later.
int256 utilizedReserves;
// Number of LP funds held in contract less utilized reserves.
uint256 liquidReserves;
// Number of LP funds reserved to pay out to LPs as fees.
uint256 undistributedLpFees;
}
// Helper contracts to facilitate cross chain actions between HubPool and SpokePool for a specific network.
struct CrossChainContract {
address adapter;
address spokePool;
}
function setPaused(bool pause) external;
function emergencyDeleteProposal() external;
function relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData) external;
function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint256 newProtocolFeeCapturePct) external;
function setBond(IERC20 newBondToken, uint256 newBondAmount) external;
function setLiveness(uint32 newLiveness) external;
function setIdentifier(bytes32 newIdentifier) external;
function setCrossChainContracts(
uint256 l2ChainId,
address adapter,
address spokePool
) external;
function enableL1TokenForLiquidityProvision(address l1Token) external;
function disableL1TokenForLiquidityProvision(address l1Token) external;
function addLiquidity(address l1Token, uint256 l1TokenAmount) external payable;
function removeLiquidity(
address l1Token,
uint256 lpTokenAmount,
bool sendEth
) external;
function exchangeRateCurrent(address l1Token) external returns (uint256);
function liquidityUtilizationCurrent(address l1Token) external returns (uint256);
function liquidityUtilizationPostRelay(address l1Token, uint256 relayedAmount) external returns (uint256);
function sync(address l1Token) external;
function proposeRootBundle(
uint256[] memory bundleEvaluationBlockNumbers,
uint8 poolRebalanceLeafCount,
bytes32 poolRebalanceRoot,
bytes32 relayerRefundRoot,
bytes32 slowRelayRoot
) external;
function executeRootBundle(
uint256 chainId,
uint256 groupIndex,
uint256[] memory bundleLpFees,
int256[] memory netSendAmounts,
int256[] memory runningBalances,
uint8 leafId,
address[] memory l1Tokens,
bytes32[] memory proof
) external;
function disputeRootBundle() external;
function claimProtocolFeesCaptured(address l1Token) external;
function setPoolRebalanceRoute(
uint256 destinationChainId,
address l1Token,
address destinationToken
) external;
function setDepositRoute(
uint256 originChainId,
uint256 destinationChainId,
address originToken,
bool depositsEnabled
) external;
function poolRebalanceRoute(uint256 destinationChainId, address l1Token)
external
view
returns (address destinationToken);
function loadEthForL2Calls() external payable;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract
* is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol
* and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol.
* @dev The reason why we use this local contract instead of importing from uma/contracts is because of the addition
* of the internal method `functionCallStackOriginatesFromOutsideThisContract` which doesn't exist in the one exported
* by uma/contracts.
*/
contract Lockable {
bool internal _notEntered;
constructor() {
// Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every
// call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full
// refund coming into effect.
_notEntered = true;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a nonReentrant function from another nonReentrant function is not supported. It is possible to
* prevent this from happening by making the nonReentrant function external, and making it call a private
* function that does the actual state modification.
*/
modifier nonReentrant() {
_preEntranceCheck();
_preEntranceSet();
_;
_postEntranceReset();
}
/**
* @dev Designed to prevent a view-only method from being re-entered during a call to a nonReentrant() state-changing method.
*/
modifier nonReentrantView() {
_preEntranceCheck();
_;
}
/**
* @dev Returns true if the contract is currently in a non-entered state, meaning that the origination of the call
* came from outside the contract. This is relevant with fallback/receive methods to see if the call came from ETH
* being dropped onto the contract externally or due to ETH dropped on the the contract from within a method in this
* contract, such as unwrapping WETH to ETH within the contract.
*/
function functionCallStackOriginatesFromOutsideThisContract() internal view returns (bool) {
return _notEntered;
}
// Internal methods are used to avoid copying the require statement's bytecode to every nonReentrant() method.
// On entry into a function, _preEntranceCheck() should always be called to check if the function is being
// re-entered. Then, if the function modifies state, it should call _postEntranceSet(), perform its logic, and
// then call _postEntranceReset().
// View-only methods can simply call _preEntranceCheck() to make sure that it is not being re-entered.
function _preEntranceCheck() internal view {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
}
function _preEntranceSet() internal {
// Any calls to nonReentrant after this point will fail
_notEntered = false;
}
function _postEntranceReset() internal {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
interface LpTokenFactoryInterface {
function createLpToken(address l1Token) external returns (address);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
interface WETH9 {
function withdraw(uint256 wad) external;
function deposit() external payable;
function balanceOf(address guy) external view returns (uint256 wad);
function transfer(address guy, uint256 wad) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "./Timer.sol";
/**
* @title Base class that provides time overrides, but only if being run in test mode.
*/
abstract contract Testable {
// If the contract is being run in production, then `timerAddress` will be the 0x0 address.
// Note: this variable should be set on construction and never modified.
address public timerAddress;
/**
* @notice Constructs the Testable contract. Called by child contracts.
* @param _timerAddress Contract that stores the current time in a testing environment.
* Must be set to 0x0 for production environments that use live time.
*/
constructor(address _timerAddress) {
timerAddress = _timerAddress;
}
/**
* @notice Reverts if not running in test mode.
*/
modifier onlyIfTest {
require(timerAddress != address(0x0));
_;
}
/**
* @notice Sets the current time.
* @dev Will revert if not running in test mode.
* @param time timestamp to set current Testable time to.
*/
function setCurrentTime(uint256 time) external onlyIfTest {
Timer(timerAddress).setCurrentTime(time);
}
/**
* @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode.
* Otherwise, it will return the block timestamp.
* @return uint for the current Testable timestamp.
*/
function getCurrentTime() public view virtual returns (uint256) {
if (timerAddress != address(0x0)) {
return Timer(timerAddress).getCurrentTime();
} else {
return block.timestamp; // solhint-disable-line not-rely-on-time
}
}
}
// This contract is taken from Uniswaps's multi call implementation (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/base/Multicall.sol)
// and was modified to be solidity 0.8 compatible. Additionally, the method was restricted to only work with msg.value
// set to 0 to avoid any nasty attack vectors on function calls that use value sent with deposits.
pragma solidity ^0.8.0;
/// @title MultiCaller
/// @notice Enables calling multiple methods in a single call to the contract
contract MultiCaller {
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) {
require(msg.value == 0, "Only multicall with 0 value");
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
results[i] = result;
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @title Stores common interface names used throughout the DVM by registration in the Finder.
*/
library OracleInterfaces {
bytes32 public constant Oracle = "Oracle";
bytes32 public constant IdentifierWhitelist = "IdentifierWhitelist";
bytes32 public constant Store = "Store";
bytes32 public constant FinancialContractsAdmin = "FinancialContractsAdmin";
bytes32 public constant Registry = "Registry";
bytes32 public constant CollateralWhitelist = "CollateralWhitelist";
bytes32 public constant OptimisticOracle = "OptimisticOracle";
bytes32 public constant Bridge = "Bridge";
bytes32 public constant GenericHandler = "GenericHandler";
bytes32 public constant SkinnyOptimisticOracle = "SkinnyOptimisticOracle";
bytes32 public constant ChildMessenger = "ChildMessenger";
bytes32 public constant OracleHub = "OracleHub";
bytes32 public constant OracleSpoke = "OracleSpoke";
}
/**
* @title Commonly re-used values for contracts associated with the OptimisticOracle.
*/
library OptimisticOracleConstraints {
// Any price request submitted to the OptimisticOracle must contain ancillary data no larger than this value.
// This value must be <= the Voting contract's `ancillaryBytesLimit` constant value otherwise it is possible
// that a price can be requested to the OptimisticOracle successfully, but cannot be resolved by the DVM which
// refuses to accept a price request made with ancillary data length over a certain size.
uint256 public constant ancillaryBytesLimit = 8192;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
interface AddressWhitelistInterface {
function addToWhitelist(address newElement) external;
function removeFromWhitelist(address newElement) external;
function isOnWhitelist(address newElement) external view returns (bool);
function getWhitelist() external view returns (address[] memory);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @title Interface for whitelists of supported identifiers that the oracle can provide prices for.
*/
interface IdentifierWhitelistInterface {
/**
* @notice Adds the provided identifier as a supported identifier.
* @dev Price requests using this identifier will succeed after this call.
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
*/
function addSupportedIdentifier(bytes32 identifier) external;
/**
* @notice Removes the identifier from the whitelist.
* @dev Price requests using this identifier will no longer succeed after this call.
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
*/
function removeSupportedIdentifier(bytes32 identifier) external;
/**
* @notice Checks whether an identifier is on the whitelist.
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
* @return bool if the identifier is supported (or not).
*/
function isIdentifierSupported(bytes32 identifier) external view returns (bool);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @title Provides addresses of the live contracts implementing certain interfaces.
* @dev Examples are the Oracle or Store interfaces.
*/
interface FinderInterface {
/**
* @notice Updates the address of the contract that implements `interfaceName`.
* @param interfaceName bytes32 encoding of the interface name that is either changed or registered.
* @param implementationAddress address of the deployed contract that implements the interface.
*/
function changeImplementationAddress(bytes32 interfaceName, address implementationAddress) external;
/**
* @notice Gets the address of the contract that implements the given `interfaceName`.
* @param interfaceName queried interface.
* @return implementationAddress address of the deployed contract that implements the interface.
*/
function getImplementationAddress(bytes32 interfaceName) external view returns (address);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../common/implementation/FixedPoint.sol";
/**
* @title Interface that allows financial contracts to pay oracle fees for their use of the system.
*/
interface StoreInterface {
/**
* @notice Pays Oracle fees in ETH to the store.
* @dev To be used by contracts whose margin currency is ETH.
*/
function payOracleFees() external payable;
/**
* @notice Pays oracle fees in the margin currency, erc20Address, to the store.
* @dev To be used if the margin currency is an ERC20 token rather than ETH.
* @param erc20Address address of the ERC20 token used to pay the fee.
* @param amount number of tokens to transfer. An approval for at least this amount must exist.
*/
function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external;
/**
* @notice Computes the regular oracle fees that a contract should pay for a period.
* @param startTime defines the beginning time from which the fee is paid.
* @param endTime end time until which the fee is paid.
* @param pfc "profit from corruption", or the maximum amount of margin currency that a
* token sponsor could extract from the contract through corrupting the price feed in their favor.
* @return regularFee amount owed for the duration from start to end time for the given pfc.
* @return latePenalty for paying the fee after the deadline.
*/
function computeRegularFee(
uint256 startTime,
uint256 endTime,
FixedPoint.Unsigned calldata pfc
) external view returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty);
/**
* @notice Computes the final oracle fees that a contract should pay at settlement.
* @param currency token used to pay the final fee.
* @return finalFee amount due.
*/
function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/OptimisticOracleInterface.sol";
/**
* @title Interface for the gas-cost-reduced version of the OptimisticOracle.
* @notice Differences from normal OptimisticOracle:
* - refundOnDispute: flag is removed, by default there are no refunds on disputes.
* - customizing request parameters: In the OptimisticOracle, parameters like `bond` and `customLiveness` can be reset
* after a request is already made via `requestPrice`. In the SkinnyOptimisticOracle, these parameters can only be
* set in `requestPrice`, which has an expanded input set.
* - settleAndGetPrice: Replaced by `settle`, which can only be called once per settleable request. The resolved price
* can be fetched via the `Settle` event or the return value of `settle`.
* - general changes to interface: Functions that interact with existing requests all require the parameters of the
* request to modify to be passed as input. These parameters must match with the existing request parameters or the
* function will revert. This change reflects the internal refactor to store hashed request parameters instead of the
* full request struct.
* @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface.
*/
abstract contract SkinnyOptimisticOracleInterface {
// Struct representing a price request. Note that this differs from the OptimisticOracleInterface's Request struct
// in that refundOnDispute is removed.
struct Request {
address proposer; // Address of the proposer.
address disputer; // Address of the disputer.
IERC20 currency; // ERC20 token used to pay rewards and fees.
bool settled; // True if the request is settled.
int256 proposedPrice; // Price that the proposer submitted.
int256 resolvedPrice; // Price resolved once the request is settled.
uint256 expirationTime; // Time at which the request auto-settles without a dispute.
uint256 reward; // Amount of the currency to pay to the proposer on settlement.
uint256 finalFee; // Final fee to pay to the Store upon request to the DVM.
uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee.
uint256 customLiveness; // Custom liveness value set by the requester.
}
// This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible
// that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses
// to accept a price request made with ancillary data length over a certain size.
uint256 public constant ancillaryBytesLimit = 8192;
/**
* @notice Requests a new price.
* @param identifier price identifier being requested.
* @param timestamp timestamp of the price being requested.
* @param ancillaryData ancillary data representing additional args being passed with the price request.
* @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM.
* @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0,
* which could make sense if the contract requests and proposes the value in the same call or
* provides its own reward system.
* @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee.
* @param customLiveness custom proposal liveness to set for request.
* @return totalBond default bond + final fee that the proposer and disputer will be required to pay.
*/
function requestPrice(
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
IERC20 currency,
uint256 reward,
uint256 bond,
uint256 customLiveness
) external virtual returns (uint256 totalBond);
/**
* @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come
* from this proposal. However, any bonds are pulled from the caller.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters whose hash must match the request that the caller wants to
* propose a price for.
* @param proposer address to set as the proposer.
* @param proposedPrice price being proposed.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the proposer once settled if the proposal is correct.
*/
function proposePriceFor(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request,
address proposer,
int256 proposedPrice
) public virtual returns (uint256 totalBond);
/**
* @notice Proposes a price value where caller is the proposer.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters whose hash must match the request that the caller wants to
* propose a price for.
* @param proposedPrice price being proposed.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the proposer once settled if the proposal is correct.
*/
function proposePrice(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request,
int256 proposedPrice
) external virtual returns (uint256 totalBond);
/**
* @notice Combines logic of requestPrice and proposePrice while taking advantage of gas savings from not having to
* overwrite Request params that a normal requestPrice() => proposePrice() flow would entail. Note: The proposer
* will receive any rewards that come from this proposal. However, any bonds are pulled from the caller.
* @dev The caller is the requester, but the proposer can be customized.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM.
* @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0,
* which could make sense if the contract requests and proposes the value in the same call or
* provides its own reward system.
* @param bond custom proposal bond to set for request. If set to 0, defaults to the final fee.
* @param customLiveness custom proposal liveness to set for request.
* @param proposer address to set as the proposer.
* @param proposedPrice price being proposed.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the proposer once settled if the proposal is correct.
*/
function requestAndProposePriceFor(
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
IERC20 currency,
uint256 reward,
uint256 bond,
uint256 customLiveness,
address proposer,
int256 proposedPrice
) external virtual returns (uint256 totalBond);
/**
* @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will
* receive any rewards that come from this dispute. However, any bonds are pulled from the caller.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters whose hash must match the request that the caller wants to
* dispute.
* @param disputer address to set as the disputer.
* @param requester sender of the initial price request.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the disputer once settled if the dispute was valid (the proposal was incorrect).
*/
function disputePriceFor(
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request,
address disputer,
address requester
) public virtual returns (uint256 totalBond);
/**
* @notice Disputes a price request with an active proposal where caller is the disputer.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters whose hash must match the request that the caller wants to
* dispute.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the disputer once settled if the dispute was valid (the proposal was incorrect).
*/
function disputePrice(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request
) external virtual returns (uint256 totalBond);
/**
* @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters whose hash must match the request that the caller wants to
* settle.
* @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes
* the returned bonds as well as additional rewards.
* @return resolvedPrice the price that the request settled to.
*/
function settle(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request
) external virtual returns (uint256 payout, int256 resolvedPrice);
/**
* @notice Computes the current state of a price request. See the State enum for more details.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters.
* @return the State.
*/
function getState(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request
) external virtual returns (OptimisticOracleInterface.State);
/**
* @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price).
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param request price request parameters. The hash of these parameters must match with the request hash that is
* associated with the price request unique ID {requester, identifier, timestamp, ancillaryData}, or this method
* will revert.
* @return boolean indicating true if price exists and false if not.
*/
function hasPrice(
address requester,
bytes32 identifier,
uint32 timestamp,
bytes memory ancillaryData,
Request memory request
) public virtual returns (bool);
/**
* @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute.
* @param ancillaryData ancillary data of the price being requested.
* @param requester sender of the initial price request.
* @return the stamped ancillary bytes.
*/
function stampAncillaryData(bytes memory ancillaryData, address requester)
public
pure
virtual
returns (bytes memory);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ERC20 interface that includes burn and mint methods.
*/
abstract contract ExpandedIERC20 is IERC20 {
/**
* @notice Burns a specific amount of the caller's tokens.
* @dev Only burns the caller's tokens, so it is safe to leave this method permissionless.
*/
function burn(uint256 value) external virtual;
/**
* @dev Burns `value` tokens owned by `recipient`.
* @param recipient address to burn tokens from.
* @param value amount of tokens to burn.
*/
function burnFrom(address recipient, uint256 value) external virtual returns (bool);
/**
* @notice Mints tokens and adds them to the balance of the `to` address.
* @dev This method should be permissioned to only allow designated parties to mint tokens.
*/
function mint(address to, uint256 value) external virtual returns (bool);
function addMinter(address account) external virtual;
function addBurner(address account) external virtual;
function resetOwner(address account) external virtual;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
/**
* @notice Contains common data structures and functions used by all SpokePool implementations.
*/
interface SpokePoolInterface {
// This leaf is meant to be decoded in the SpokePool to pay out successful relayers.
struct RelayerRefundLeaf {
// This is the amount to return to the HubPool. This occurs when there is a PoolRebalanceLeaf netSendAmount that
// is negative. This is just the negative of this value.
uint256 amountToReturn;
// Used to verify that this is being executed on the correct destination chainId.
uint256 chainId;
// This array designates how much each of those addresses should be refunded.
uint256[] refundAmounts;
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint32 leafId;
// The associated L2TokenAddress that these claims apply to.
address l2TokenAddress;
// Must be same length as refundAmounts and designates each address that must be refunded.
address[] refundAddresses;
}
// This struct represents the data to fully specify a relay. If any portion of this data differs, the relay is
// considered to be completely distinct. Only one relay for a particular depositId, chainId pair should be
// considered valid and repaid. This data is hashed and inserted into the slow relay merkle root so that an off
// chain validator can choose when to refund slow relayers.
struct RelayData {
// The address that made the deposit on the origin chain.
address depositor;
// The recipient address on the destination chain.
address recipient;
// The corresponding token address on the destination chain.
address destinationToken;
// The total relay amount before fees are taken out.
uint256 amount;
// Origin chain id.
uint256 originChainId;
// Destination chain id.
uint256 destinationChainId;
// The LP Fee percentage computed by the relayer based on the deposit's quote timestamp
// and the HubPool's utilization.
uint64 realizedLpFeePct;
// The relayer fee percentage specified in the deposit.
uint64 relayerFeePct;
// The id uniquely identifying this deposit on the origin chain.
uint32 depositId;
}
// Stores collection of merkle roots that can be published to this contract from the HubPool, which are referenced
// by "data workers" via inclusion proofs to execute leaves in the roots.
struct RootBundle {
// Merkle root of slow relays that were not fully filled and whose recipient is still owed funds from the LP pool.
bytes32 slowRelayRoot;
// Merkle root of relayer refunds for successful relays.
bytes32 relayerRefundRoot;
// This is a 2D bitmap tracking which leaves in the relayer refund root have been claimed, with max size of
// 256x(2^248) leaves per root.
mapping(uint256 => uint256) claimedBitmap;
}
function setCrossDomainAdmin(address newCrossDomainAdmin) external;
function setHubPool(address newHubPool) external;
function setEnableRoute(
address originToken,
uint256 destinationChainId,
bool enable
) external;
function setDepositQuoteTimeBuffer(uint32 buffer) external;
function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayRoot) external;
function emergencyDeleteRootBundle(uint256 rootBundleId) external;
function deposit(
address recipient,
address originToken,
uint256 amount,
uint256 destinationChainId,
uint64 relayerFeePct,
uint32 quoteTimestamp
) external payable;
function speedUpDeposit(
address depositor,
uint64 newRelayerFeePct,
uint32 depositId,
bytes memory depositorSignature
) external;
function fillRelay(
address depositor,
address recipient,
address destinationToken,
uint256 amount,
uint256 maxTokensToSend,
uint256 repaymentChainId,
uint256 originChainId,
uint64 realizedLpFeePct,
uint64 relayerFeePct,
uint32 depositId
) external;
function fillRelayWithUpdatedFee(
address depositor,
address recipient,
address destinationToken,
uint256 amount,
uint256 maxTokensToSend,
uint256 repaymentChainId,
uint256 originChainId,
uint64 realizedLpFeePct,
uint64 relayerFeePct,
uint64 newRelayerFeePct,
uint32 depositId,
bytes memory depositorSignature
) external;
function executeSlowRelayLeaf(
address depositor,
address recipient,
address destinationToken,
uint256 amount,
uint256 originChainId,
uint64 realizedLpFeePct,
uint64 relayerFeePct,
uint32 depositId,
uint32 rootBundleId,
bytes32[] memory proof
) external;
function executeRelayerRefundLeaf(
uint32 rootBundleId,
SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf,
bytes32[] memory proof
) external;
function chainId() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Trees proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
}
return computedHash;
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @notice Sends cross chain messages and tokens to contracts on a specific L2 network.
*/
interface AdapterInterface {
event MessageRelayed(address target, bytes message);
event TokensRelayed(address l1Token, address l2Token, uint256 amount, address to);
function relayMessage(address target, bytes calldata message) external payable;
function relayTokens(
address l1Token,
address l2Token,
uint256 amount,
address to
) external payable;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
/**
* @title Universal store of current contract time for testing environments.
*/
contract Timer {
uint256 private currentTime;
constructor() {
currentTime = block.timestamp; // solhint-disable-line not-rely-on-time
}
/**
* @notice Sets the current time.
* @dev Will revert if not running in test mode.
* @param time timestamp to set `currentTime` to.
*/
function setCurrentTime(uint256 time) external {
currentTime = time;
}
/**
* @notice Gets the currentTime variable set in the Timer.
* @return uint256 for the current Testable timestamp.
*/
function getCurrentTime() public view returns (uint256) {
return currentTime;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/math/SignedSafeMath.sol";
/**
* @title Library for fixed point arithmetic on uints
*/
library FixedPoint {
using SafeMath for uint256;
using SignedSafeMath for int256;
// Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5".
// For unsigned values:
// This can represent a value up to (2^256 - 1)/10^18 = ~10^59. 10^59 will be stored internally as uint256 10^77.
uint256 private constant FP_SCALING_FACTOR = 10**18;
// --------------------------------------- UNSIGNED -----------------------------------------------------------------------------
struct Unsigned {
uint256 rawValue;
}
/**
* @notice Constructs an `Unsigned` from an unscaled uint, e.g., `b=5` gets stored internally as `5*(10**18)`.
* @param a uint to convert into a FixedPoint.
* @return the converted FixedPoint.
*/
function fromUnscaledUint(uint256 a) internal pure returns (Unsigned memory) {
return Unsigned(a.mul(FP_SCALING_FACTOR));
}
/**
* @notice Whether `a` is equal to `b`.
* @param a a FixedPoint.
* @param b a uint256.
* @return True if equal, or False.
*/
function isEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
return a.rawValue == fromUnscaledUint(b).rawValue;
}
/**
* @notice Whether `a` is equal to `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return True if equal, or False.
*/
function isEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
return a.rawValue == b.rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return True if `a > b`, or False.
*/
function isGreaterThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
return a.rawValue > b.rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a a FixedPoint.
* @param b a uint256.
* @return True if `a > b`, or False.
*/
function isGreaterThan(Unsigned memory a, uint256 b) internal pure returns (bool) {
return a.rawValue > fromUnscaledUint(b).rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a a uint256.
* @param b a FixedPoint.
* @return True if `a > b`, or False.
*/
function isGreaterThan(uint256 a, Unsigned memory b) internal pure returns (bool) {
return fromUnscaledUint(a).rawValue > b.rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
return a.rawValue >= b.rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a a FixedPoint.
* @param b a uint256.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
return a.rawValue >= fromUnscaledUint(b).rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a a uint256.
* @param b a FixedPoint.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) {
return fromUnscaledUint(a).rawValue >= b.rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return True if `a < b`, or False.
*/
function isLessThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
return a.rawValue < b.rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a a FixedPoint.
* @param b a uint256.
* @return True if `a < b`, or False.
*/
function isLessThan(Unsigned memory a, uint256 b) internal pure returns (bool) {
return a.rawValue < fromUnscaledUint(b).rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a a uint256.
* @param b a FixedPoint.
* @return True if `a < b`, or False.
*/
function isLessThan(uint256 a, Unsigned memory b) internal pure returns (bool) {
return fromUnscaledUint(a).rawValue < b.rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
return a.rawValue <= b.rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a a FixedPoint.
* @param b a uint256.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
return a.rawValue <= fromUnscaledUint(b).rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a a uint256.
* @param b a FixedPoint.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) {
return fromUnscaledUint(a).rawValue <= b.rawValue;
}
/**
* @notice The minimum of `a` and `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the minimum of `a` and `b`.
*/
function min(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
return a.rawValue < b.rawValue ? a : b;
}
/**
* @notice The maximum of `a` and `b`.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the maximum of `a` and `b`.
*/
function max(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
return a.rawValue > b.rawValue ? a : b;
}
/**
* @notice Adds two `Unsigned`s, reverting on overflow.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the sum of `a` and `b`.
*/
function add(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
return Unsigned(a.rawValue.add(b.rawValue));
}
/**
* @notice Adds an `Unsigned` to an unscaled uint, reverting on overflow.
* @param a a FixedPoint.
* @param b a uint256.
* @return the sum of `a` and `b`.
*/
function add(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
return add(a, fromUnscaledUint(b));
}
/**
* @notice Subtracts two `Unsigned`s, reverting on overflow.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the difference of `a` and `b`.
*/
function sub(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
return Unsigned(a.rawValue.sub(b.rawValue));
}
/**
* @notice Subtracts an unscaled uint256 from an `Unsigned`, reverting on overflow.
* @param a a FixedPoint.
* @param b a uint256.
* @return the difference of `a` and `b`.
*/
function sub(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
return sub(a, fromUnscaledUint(b));
}
/**
* @notice Subtracts an `Unsigned` from an unscaled uint256, reverting on overflow.
* @param a a uint256.
* @param b a FixedPoint.
* @return the difference of `a` and `b`.
*/
function sub(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) {
return sub(fromUnscaledUint(a), b);
}
/**
* @notice Multiplies two `Unsigned`s, reverting on overflow.
* @dev This will "floor" the product.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the product of `a` and `b`.
*/
function mul(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
// There are two caveats with this computation:
// 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is
// stored internally as a uint256 ~10^59.
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which
// would round to 3, but this computation produces the result 2.
// No need to use SafeMath because FP_SCALING_FACTOR != 0.
return Unsigned(a.rawValue.mul(b.rawValue) / FP_SCALING_FACTOR);
}
/**
* @notice Multiplies an `Unsigned` and an unscaled uint256, reverting on overflow.
* @dev This will "floor" the product.
* @param a a FixedPoint.
* @param b a uint256.
* @return the product of `a` and `b`.
*/
function mul(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
return Unsigned(a.rawValue.mul(b));
}
/**
* @notice Multiplies two `Unsigned`s and "ceil's" the product, reverting on overflow.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the product of `a` and `b`.
*/
function mulCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
uint256 mulRaw = a.rawValue.mul(b.rawValue);
uint256 mulFloor = mulRaw / FP_SCALING_FACTOR;
uint256 mod = mulRaw.mod(FP_SCALING_FACTOR);
if (mod != 0) {
return Unsigned(mulFloor.add(1));
} else {
return Unsigned(mulFloor);
}
}
/**
* @notice Multiplies an `Unsigned` and an unscaled uint256 and "ceil's" the product, reverting on overflow.
* @param a a FixedPoint.
* @param b a FixedPoint.
* @return the product of `a` and `b`.
*/
function mulCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
// Since b is an uint, there is no risk of truncation and we can just mul it normally
return Unsigned(a.rawValue.mul(b));
}
/**
* @notice Divides one `Unsigned` by an `Unsigned`, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a a FixedPoint numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
// There are two caveats with this computation:
// 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows.
// 10^41 is stored internally as a uint256 10^59.
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which
// would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666.
return Unsigned(a.rawValue.mul(FP_SCALING_FACTOR).div(b.rawValue));
}
/**
* @notice Divides one `Unsigned` by an unscaled uint256, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a a FixedPoint numerator.
* @param b a uint256 denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
return Unsigned(a.rawValue.div(b));
}
/**
* @notice Divides one unscaled uint256 by an `Unsigned`, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a a uint256 numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) {
return div(fromUnscaledUint(a), b);
}
/**
* @notice Divides one `Unsigned` by an `Unsigned` and "ceil's" the quotient, reverting on overflow or division by 0.
* @param a a FixedPoint numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function divCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
uint256 aScaled = a.rawValue.mul(FP_SCALING_FACTOR);
uint256 divFloor = aScaled.div(b.rawValue);
uint256 mod = aScaled.mod(b.rawValue);
if (mod != 0) {
return Unsigned(divFloor.add(1));
} else {
return Unsigned(divFloor);
}
}
/**
* @notice Divides one `Unsigned` by an unscaled uint256 and "ceil's" the quotient, reverting on overflow or division by 0.
* @param a a FixedPoint numerator.
* @param b a uint256 denominator.
* @return the quotient of `a` divided by `b`.
*/
function divCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
// Because it is possible that a quotient gets truncated, we can't just call "Unsigned(a.rawValue.div(b))"
// similarly to mulCeil with a uint256 as the second parameter. Therefore we need to convert b into an Unsigned.
// This creates the possibility of overflow if b is very large.
return divCeil(a, fromUnscaledUint(b));
}
/**
* @notice Raises an `Unsigned` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`.
* @dev This will "floor" the result.
* @param a a FixedPoint numerator.
* @param b a uint256 denominator.
* @return output is `a` to the power of `b`.
*/
function pow(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory output) {
output = fromUnscaledUint(1);
for (uint256 i = 0; i < b; i = i.add(1)) {
output = mul(output, a);
}
}
// ------------------------------------------------- SIGNED -------------------------------------------------------------
// Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5".
// For signed values:
// This can represent a value up (or down) to +-(2^255 - 1)/10^18 = ~10^58. 10^58 will be stored internally as int256 10^76.
int256 private constant SFP_SCALING_FACTOR = 10**18;
struct Signed {
int256 rawValue;
}
function fromSigned(Signed memory a) internal pure returns (Unsigned memory) {
require(a.rawValue >= 0, "Negative value provided");
return Unsigned(uint256(a.rawValue));
}
function fromUnsigned(Unsigned memory a) internal pure returns (Signed memory) {
require(a.rawValue <= uint256(type(int256).max), "Unsigned too large");
return Signed(int256(a.rawValue));
}
/**
* @notice Constructs a `Signed` from an unscaled int, e.g., `b=5` gets stored internally as `5*(10**18)`.
* @param a int to convert into a FixedPoint.Signed.
* @return the converted FixedPoint.Signed.
*/
function fromUnscaledInt(int256 a) internal pure returns (Signed memory) {
return Signed(a.mul(SFP_SCALING_FACTOR));
}
/**
* @notice Whether `a` is equal to `b`.
* @param a a FixedPoint.Signed.
* @param b a int256.
* @return True if equal, or False.
*/
function isEqual(Signed memory a, int256 b) internal pure returns (bool) {
return a.rawValue == fromUnscaledInt(b).rawValue;
}
/**
* @notice Whether `a` is equal to `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return True if equal, or False.
*/
function isEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
return a.rawValue == b.rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return True if `a > b`, or False.
*/
function isGreaterThan(Signed memory a, Signed memory b) internal pure returns (bool) {
return a.rawValue > b.rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return True if `a > b`, or False.
*/
function isGreaterThan(Signed memory a, int256 b) internal pure returns (bool) {
return a.rawValue > fromUnscaledInt(b).rawValue;
}
/**
* @notice Whether `a` is greater than `b`.
* @param a an int256.
* @param b a FixedPoint.Signed.
* @return True if `a > b`, or False.
*/
function isGreaterThan(int256 a, Signed memory b) internal pure returns (bool) {
return fromUnscaledInt(a).rawValue > b.rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
return a.rawValue >= b.rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) {
return a.rawValue >= fromUnscaledInt(b).rawValue;
}
/**
* @notice Whether `a` is greater than or equal to `b`.
* @param a an int256.
* @param b a FixedPoint.Signed.
* @return True if `a >= b`, or False.
*/
function isGreaterThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) {
return fromUnscaledInt(a).rawValue >= b.rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return True if `a < b`, or False.
*/
function isLessThan(Signed memory a, Signed memory b) internal pure returns (bool) {
return a.rawValue < b.rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return True if `a < b`, or False.
*/
function isLessThan(Signed memory a, int256 b) internal pure returns (bool) {
return a.rawValue < fromUnscaledInt(b).rawValue;
}
/**
* @notice Whether `a` is less than `b`.
* @param a an int256.
* @param b a FixedPoint.Signed.
* @return True if `a < b`, or False.
*/
function isLessThan(int256 a, Signed memory b) internal pure returns (bool) {
return fromUnscaledInt(a).rawValue < b.rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
return a.rawValue <= b.rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) {
return a.rawValue <= fromUnscaledInt(b).rawValue;
}
/**
* @notice Whether `a` is less than or equal to `b`.
* @param a an int256.
* @param b a FixedPoint.Signed.
* @return True if `a <= b`, or False.
*/
function isLessThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) {
return fromUnscaledInt(a).rawValue <= b.rawValue;
}
/**
* @notice The minimum of `a` and `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the minimum of `a` and `b`.
*/
function min(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
return a.rawValue < b.rawValue ? a : b;
}
/**
* @notice The maximum of `a` and `b`.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the maximum of `a` and `b`.
*/
function max(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
return a.rawValue > b.rawValue ? a : b;
}
/**
* @notice Adds two `Signed`s, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the sum of `a` and `b`.
*/
function add(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
return Signed(a.rawValue.add(b.rawValue));
}
/**
* @notice Adds an `Signed` to an unscaled int, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return the sum of `a` and `b`.
*/
function add(Signed memory a, int256 b) internal pure returns (Signed memory) {
return add(a, fromUnscaledInt(b));
}
/**
* @notice Subtracts two `Signed`s, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the difference of `a` and `b`.
*/
function sub(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
return Signed(a.rawValue.sub(b.rawValue));
}
/**
* @notice Subtracts an unscaled int256 from an `Signed`, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return the difference of `a` and `b`.
*/
function sub(Signed memory a, int256 b) internal pure returns (Signed memory) {
return sub(a, fromUnscaledInt(b));
}
/**
* @notice Subtracts an `Signed` from an unscaled int256, reverting on overflow.
* @param a an int256.
* @param b a FixedPoint.Signed.
* @return the difference of `a` and `b`.
*/
function sub(int256 a, Signed memory b) internal pure returns (Signed memory) {
return sub(fromUnscaledInt(a), b);
}
/**
* @notice Multiplies two `Signed`s, reverting on overflow.
* @dev This will "floor" the product.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the product of `a` and `b`.
*/
function mul(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
// There are two caveats with this computation:
// 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is
// stored internally as an int256 ~10^59.
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which
// would round to 3, but this computation produces the result 2.
// No need to use SafeMath because SFP_SCALING_FACTOR != 0.
return Signed(a.rawValue.mul(b.rawValue) / SFP_SCALING_FACTOR);
}
/**
* @notice Multiplies an `Signed` and an unscaled int256, reverting on overflow.
* @dev This will "floor" the product.
* @param a a FixedPoint.Signed.
* @param b an int256.
* @return the product of `a` and `b`.
*/
function mul(Signed memory a, int256 b) internal pure returns (Signed memory) {
return Signed(a.rawValue.mul(b));
}
/**
* @notice Multiplies two `Signed`s and "ceil's" the product, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the product of `a` and `b`.
*/
function mulAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
int256 mulRaw = a.rawValue.mul(b.rawValue);
int256 mulTowardsZero = mulRaw / SFP_SCALING_FACTOR;
// Manual mod because SignedSafeMath doesn't support it.
int256 mod = mulRaw % SFP_SCALING_FACTOR;
if (mod != 0) {
bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0);
int256 valueToAdd = isResultPositive ? int256(1) : int256(-1);
return Signed(mulTowardsZero.add(valueToAdd));
} else {
return Signed(mulTowardsZero);
}
}
/**
* @notice Multiplies an `Signed` and an unscaled int256 and "ceil's" the product, reverting on overflow.
* @param a a FixedPoint.Signed.
* @param b a FixedPoint.Signed.
* @return the product of `a` and `b`.
*/
function mulAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) {
// Since b is an int, there is no risk of truncation and we can just mul it normally
return Signed(a.rawValue.mul(b));
}
/**
* @notice Divides one `Signed` by an `Signed`, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a a FixedPoint numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
// There are two caveats with this computation:
// 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows.
// 10^41 is stored internally as an int256 10^59.
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which
// would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666.
return Signed(a.rawValue.mul(SFP_SCALING_FACTOR).div(b.rawValue));
}
/**
* @notice Divides one `Signed` by an unscaled int256, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a a FixedPoint numerator.
* @param b an int256 denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(Signed memory a, int256 b) internal pure returns (Signed memory) {
return Signed(a.rawValue.div(b));
}
/**
* @notice Divides one unscaled int256 by an `Signed`, reverting on overflow or division by 0.
* @dev This will "floor" the quotient.
* @param a an int256 numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function div(int256 a, Signed memory b) internal pure returns (Signed memory) {
return div(fromUnscaledInt(a), b);
}
/**
* @notice Divides one `Signed` by an `Signed` and "ceil's" the quotient, reverting on overflow or division by 0.
* @param a a FixedPoint numerator.
* @param b a FixedPoint denominator.
* @return the quotient of `a` divided by `b`.
*/
function divAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
int256 aScaled = a.rawValue.mul(SFP_SCALING_FACTOR);
int256 divTowardsZero = aScaled.div(b.rawValue);
// Manual mod because SignedSafeMath doesn't support it.
int256 mod = aScaled % b.rawValue;
if (mod != 0) {
bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0);
int256 valueToAdd = isResultPositive ? int256(1) : int256(-1);
return Signed(divTowardsZero.add(valueToAdd));
} else {
return Signed(divTowardsZero);
}
}
/**
* @notice Divides one `Signed` by an unscaled int256 and "ceil's" the quotient, reverting on overflow or division by 0.
* @param a a FixedPoint numerator.
* @param b an int256 denominator.
* @return the quotient of `a` divided by `b`.
*/
function divAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) {
// Because it is possible that a quotient gets truncated, we can't just call "Signed(a.rawValue.div(b))"
// similarly to mulCeil with an int256 as the second parameter. Therefore we need to convert b into an Signed.
// This creates the possibility of overflow if b is very large.
return divAwayFromZero(a, fromUnscaledInt(b));
}
/**
* @notice Raises an `Signed` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`.
* @dev This will "floor" the result.
* @param a a FixedPoint.Signed.
* @param b a uint256 (negative exponents are not allowed).
* @return output is `a` to the power of `b`.
*/
function pow(Signed memory a, uint256 b) internal pure returns (Signed memory output) {
output = fromUnscaledInt(1);
for (uint256 i = 0; i < b; i = i.add(1)) {
output = mul(output, a);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SignedSafeMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler
* now has built in overflow checking.
*/
library SignedSafeMath {
/**
* @dev Returns the multiplication of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(int256 a, int256 b) internal pure returns (int256) {
return a * b;
}
/**
* @dev Returns the integer division of two signed integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(int256 a, int256 b) internal pure returns (int256) {
return a / b;
}
/**
* @dev Returns the subtraction of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(int256 a, int256 b) internal pure returns (int256) {
return a - b;
}
/**
* @dev Returns the addition of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(int256 a, int256 b) internal pure returns (int256) {
return a + b;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Financial contract facing Oracle interface.
* @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface.
*/
abstract contract OptimisticOracleInterface {
// Struct representing the state of a price request.
enum State {
Invalid, // Never requested.
Requested, // Requested, no other actions taken.
Proposed, // Proposed, but not expired or disputed yet.
Expired, // Proposed, not disputed, past liveness.
Disputed, // Disputed, but no DVM price returned yet.
Resolved, // Disputed and DVM price is available.
Settled // Final price has been set in the contract (can get here from Expired or Resolved).
}
// Struct representing a price request.
struct Request {
address proposer; // Address of the proposer.
address disputer; // Address of the disputer.
IERC20 currency; // ERC20 token used to pay rewards and fees.
bool settled; // True if the request is settled.
bool refundOnDispute; // True if the requester should be refunded their reward on dispute.
int256 proposedPrice; // Price that the proposer submitted.
int256 resolvedPrice; // Price resolved once the request is settled.
uint256 expirationTime; // Time at which the request auto-settles without a dispute.
uint256 reward; // Amount of the currency to pay to the proposer on settlement.
uint256 finalFee; // Final fee to pay to the Store upon request to the DVM.
uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee.
uint256 customLiveness; // Custom liveness value set by the requester.
}
// This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible
// that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses
// to accept a price request made with ancillary data length over a certain size.
uint256 public constant ancillaryBytesLimit = 8192;
/**
* @notice Requests a new price.
* @param identifier price identifier being requested.
* @param timestamp timestamp of the price being requested.
* @param ancillaryData ancillary data representing additional args being passed with the price request.
* @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM.
* @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0,
* which could make sense if the contract requests and proposes the value in the same call or
* provides its own reward system.
* @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay.
* This can be changed with a subsequent call to setBond().
*/
function requestPrice(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
IERC20 currency,
uint256 reward
) external virtual returns (uint256 totalBond);
/**
* @notice Set the proposal bond associated with a price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param bond custom bond amount to set.
* @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be
* changed again with a subsequent call to setBond().
*/
function setBond(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
uint256 bond
) external virtual returns (uint256 totalBond);
/**
* @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller
* in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's
* bond, so there is still profit to be made even if the reward is refunded.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
*/
function setRefundOnDispute(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) external virtual;
/**
* @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before
* being auto-resolved.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param customLiveness new custom liveness.
*/
function setCustomLiveness(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
uint256 customLiveness
) external virtual;
/**
* @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come
* from this proposal. However, any bonds are pulled from the caller.
* @param proposer address to set as the proposer.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param proposedPrice price being proposed.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the proposer once settled if the proposal is correct.
*/
function proposePriceFor(
address proposer,
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
int256 proposedPrice
) public virtual returns (uint256 totalBond);
/**
* @notice Proposes a price value for an existing price request.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @param proposedPrice price being proposed.
* @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to
* the proposer once settled if the proposal is correct.
*/
function proposePrice(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
int256 proposedPrice
) external virtual returns (uint256 totalBond);
/**
* @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will
* receive any rewards that come from this dispute. However, any bonds are pulled from the caller.
* @param disputer address to set as the disputer.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
* the disputer once settled if the dispute was value (the proposal was incorrect).
*/
function disputePriceFor(
address disputer,
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) public virtual returns (uint256 totalBond);
/**
* @notice Disputes a price value for an existing price request with an active proposal.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to
* the disputer once settled if the dispute was valid (the proposal was incorrect).
*/
function disputePrice(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) external virtual returns (uint256 totalBond);
/**
* @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled
* or settleable. Note: this method is not view so that this call may actually settle the price request if it
* hasn't been settled.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return resolved price.
*/
function settleAndGetPrice(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) external virtual returns (int256);
/**
* @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes
* the returned bonds as well as additional rewards.
*/
function settle(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) external virtual returns (uint256 payout);
/**
* @notice Gets the current data structure containing all information about a price request.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return the Request data structure.
*/
function getRequest(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) public view virtual returns (Request memory);
/**
* @notice Returns the state of a price request.
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return the State enum value.
*/
function getState(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) public view virtual returns (State);
/**
* @notice Checks if a given request has resolved or been settled (i.e the optimistic oracle has a price).
* @param requester sender of the initial price request.
* @param identifier price identifier to identify the existing request.
* @param timestamp timestamp to identify the existing request.
* @param ancillaryData ancillary data of the price being requested.
* @return true if price has resolved or settled, false otherwise.
*/
function hasPrice(
address requester,
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData
) public view virtual returns (bool);
function stampAncillaryData(bytes memory ancillaryData, address requester)
public
view
virtual
returns (bytes memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}