Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
16383407 | 13 days ago | Contract Creation | 0 S |
Loading...
Loading
Contract Name:
AccountantWithRateProviders
Compiler Version
v0.8.21+commit.d9974bed
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {IRateProvider} from "src/interfaces/IRateProvider.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {BoringVault} from "src/base/BoringVault.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {IPausable} from "src/interfaces/IPausable.sol"; contract AccountantWithRateProviders is Auth, IRateProvider, IPausable { using FixedPointMathLib for uint256; using SafeTransferLib for ERC20; // ========================================= STRUCTS ========================================= /** * @param payoutAddress the address `claimFees` sends fees to * @param highwaterMark the highest value of the BoringVault's share price * @param feesOwedInBase total pending fees owed in terms of base * @param totalSharesLastUpdate total amount of shares the last exchange rate update * @param exchangeRate the current exchange rate in terms of base * @param allowedExchangeRateChangeUpper the max allowed change to exchange rate from an update * @param allowedExchangeRateChangeLower the min allowed change to exchange rate from an update * @param lastUpdateTimestamp the block timestamp of the last exchange rate update * @param isPaused whether or not this contract is paused * @param minimumUpdateDelayInSeconds the minimum amount of time that must pass between * exchange rate updates, such that the update won't trigger the contract to be paused * @param platformFee the platform fee * @param performanceFee the performance fee */ struct AccountantState { address payoutAddress; uint96 highwaterMark; uint128 feesOwedInBase; uint128 totalSharesLastUpdate; uint96 exchangeRate; uint16 allowedExchangeRateChangeUpper; uint16 allowedExchangeRateChangeLower; uint64 lastUpdateTimestamp; bool isPaused; uint24 minimumUpdateDelayInSeconds; uint16 platformFee; uint16 performanceFee; } /** * @param isPeggedToBase whether or not the asset is 1:1 with the base asset * @param rateProvider the rate provider for this asset if `isPeggedToBase` is false */ struct RateProviderData { bool isPeggedToBase; IRateProvider rateProvider; } // ========================================= STATE ========================================= /** * @notice Store the accountant state in 3 packed slots. */ AccountantState public accountantState; /** * @notice Maps ERC20s to their RateProviderData. */ mapping(ERC20 => RateProviderData) public rateProviderData; //============================== ERRORS =============================== error AccountantWithRateProviders__UpperBoundTooSmall(); error AccountantWithRateProviders__LowerBoundTooLarge(); error AccountantWithRateProviders__PlatformFeeTooLarge(); error AccountantWithRateProviders__PerformanceFeeTooLarge(); error AccountantWithRateProviders__Paused(); error AccountantWithRateProviders__ZeroFeesOwed(); error AccountantWithRateProviders__OnlyCallableByBoringVault(); error AccountantWithRateProviders__UpdateDelayTooLarge(); error AccountantWithRateProviders__ExchangeRateAboveHighwaterMark(); //============================== EVENTS =============================== event Paused(); event Unpaused(); event DelayInSecondsUpdated(uint24 oldDelay, uint24 newDelay); event UpperBoundUpdated(uint16 oldBound, uint16 newBound); event LowerBoundUpdated(uint16 oldBound, uint16 newBound); event PlatformFeeUpdated(uint16 oldFee, uint16 newFee); event PerformanceFeeUpdated(uint16 oldFee, uint16 newFee); event PayoutAddressUpdated(address oldPayout, address newPayout); event RateProviderUpdated(address asset, bool isPegged, address rateProvider); event ExchangeRateUpdated(uint96 oldRate, uint96 newRate, uint64 currentTime); event FeesClaimed(address indexed feeAsset, uint256 amount); event HighwaterMarkReset(); //============================== IMMUTABLES =============================== /** * @notice The base asset rates are provided in. */ ERC20 public immutable base; /** * @notice The decimals rates are provided in. */ uint8 public immutable decimals; /** * @notice The BoringVault this accountant is working with. * Used to determine share supply for fee calculation. */ BoringVault public immutable vault; /** * @notice One share of the BoringVault. */ uint256 internal immutable ONE_SHARE; constructor( address _owner, address _vault, address payoutAddress, uint96 startingExchangeRate, address _base, uint16 allowedExchangeRateChangeUpper, uint16 allowedExchangeRateChangeLower, uint24 minimumUpdateDelayInSeconds, uint16 platformFee, uint16 performanceFee ) Auth(_owner, Authority(address(0))) { base = ERC20(_base); decimals = ERC20(_base).decimals(); vault = BoringVault(payable(_vault)); ONE_SHARE = 10 ** vault.decimals(); accountantState = AccountantState({ payoutAddress: payoutAddress, highwaterMark: startingExchangeRate, feesOwedInBase: 0, totalSharesLastUpdate: uint128(vault.totalSupply()), exchangeRate: startingExchangeRate, allowedExchangeRateChangeUpper: allowedExchangeRateChangeUpper, allowedExchangeRateChangeLower: allowedExchangeRateChangeLower, lastUpdateTimestamp: uint64(block.timestamp), isPaused: false, minimumUpdateDelayInSeconds: minimumUpdateDelayInSeconds, platformFee: platformFee, performanceFee: performanceFee }); } // ========================================= ADMIN FUNCTIONS ========================================= /** * @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate * calls will revert. * @dev Callable by MULTISIG_ROLE. */ function pause() external requiresAuth { accountantState.isPaused = true; emit Paused(); } /** * @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate * calls will stop reverting. * @dev Callable by MULTISIG_ROLE. */ function unpause() external requiresAuth { accountantState.isPaused = false; emit Unpaused(); } /** * @notice Update the minimum time delay between `updateExchangeRate` calls. * @dev There are no input requirements, as it is possible the admin would want * the exchange rate updated as frequently as needed. * @dev Callable by OWNER_ROLE. */ function updateDelay(uint24 minimumUpdateDelayInSeconds) external requiresAuth { if (minimumUpdateDelayInSeconds > 14 days) revert AccountantWithRateProviders__UpdateDelayTooLarge(); uint24 oldDelay = accountantState.minimumUpdateDelayInSeconds; accountantState.minimumUpdateDelayInSeconds = minimumUpdateDelayInSeconds; emit DelayInSecondsUpdated(oldDelay, minimumUpdateDelayInSeconds); } /** * @notice Update the allowed upper bound change of exchange rate between `updateExchangeRateCalls`. * @dev Callable by OWNER_ROLE. */ function updateUpper(uint16 allowedExchangeRateChangeUpper) external requiresAuth { if (allowedExchangeRateChangeUpper < 1e4) revert AccountantWithRateProviders__UpperBoundTooSmall(); uint16 oldBound = accountantState.allowedExchangeRateChangeUpper; accountantState.allowedExchangeRateChangeUpper = allowedExchangeRateChangeUpper; emit UpperBoundUpdated(oldBound, allowedExchangeRateChangeUpper); } /** * @notice Update the allowed lower bound change of exchange rate between `updateExchangeRateCalls`. * @dev Callable by OWNER_ROLE. */ function updateLower(uint16 allowedExchangeRateChangeLower) external requiresAuth { if (allowedExchangeRateChangeLower > 1e4) revert AccountantWithRateProviders__LowerBoundTooLarge(); uint16 oldBound = accountantState.allowedExchangeRateChangeLower; accountantState.allowedExchangeRateChangeLower = allowedExchangeRateChangeLower; emit LowerBoundUpdated(oldBound, allowedExchangeRateChangeLower); } /** * @notice Update the platform fee to a new value. * @dev Callable by OWNER_ROLE. */ function updatePlatformFee(uint16 platformFee) external requiresAuth { if (platformFee > 0.2e4) revert AccountantWithRateProviders__PlatformFeeTooLarge(); uint16 oldFee = accountantState.platformFee; accountantState.platformFee = platformFee; emit PlatformFeeUpdated(oldFee, platformFee); } /** * @notice Update the performance fee to a new value. * @dev Callable by OWNER_ROLE. */ function updatePerformanceFee(uint16 performanceFee) external requiresAuth { if (performanceFee > 0.5e4) revert AccountantWithRateProviders__PerformanceFeeTooLarge(); uint16 oldFee = accountantState.performanceFee; accountantState.performanceFee = performanceFee; emit PerformanceFeeUpdated(oldFee, performanceFee); } /** * @notice Update the payout address fees are sent to. * @dev Callable by OWNER_ROLE. */ function updatePayoutAddress(address payoutAddress) external requiresAuth { address oldPayout = accountantState.payoutAddress; accountantState.payoutAddress = payoutAddress; emit PayoutAddressUpdated(oldPayout, payoutAddress); } /** * @notice Update the rate provider data for a specific `asset`. * @dev Rate providers must return rates in terms of `base` or * an asset pegged to base and they must use the same decimals * as `asset`. * @dev Callable by OWNER_ROLE. */ function setRateProviderData(ERC20 asset, bool isPeggedToBase, address rateProvider) external requiresAuth { rateProviderData[asset] = RateProviderData({isPeggedToBase: isPeggedToBase, rateProvider: IRateProvider(rateProvider)}); emit RateProviderUpdated(address(asset), isPeggedToBase, rateProvider); } /** * @notice Reset the highwater mark to the current exchange rate. * @dev Callable by OWNER_ROLE. */ function resetHighwaterMark() external virtual requiresAuth { AccountantState storage state = accountantState; if (state.exchangeRate > state.highwaterMark) { revert AccountantWithRateProviders__ExchangeRateAboveHighwaterMark(); } uint64 currentTime = uint64(block.timestamp); uint256 currentTotalShares = vault.totalSupply(); _calculateFeesOwed(state, state.exchangeRate, state.exchangeRate, currentTotalShares, currentTime); state.totalSharesLastUpdate = uint128(currentTotalShares); state.highwaterMark = accountantState.exchangeRate; state.lastUpdateTimestamp = currentTime; emit HighwaterMarkReset(); } // ========================================= UPDATE EXCHANGE RATE/FEES FUNCTIONS ========================================= /** * @notice Updates this contract exchangeRate. * @dev If new exchange rate is outside of accepted bounds, or if not enough time has passed, this * will pause the contract, and this function will NOT calculate fees owed. * @dev Callable by UPDATE_EXCHANGE_RATE_ROLE. */ function updateExchangeRate(uint96 newExchangeRate) external virtual requiresAuth { ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) = _beforeUpdateExchangeRate(newExchangeRate); if (shouldPause) { // Instead of reverting, pause the contract. This way the exchange rate updater is able to update the exchange rate // to a better value, and pause it. state.isPaused = true; } else { _calculateFeesOwed(state, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime); } newExchangeRate = _setExchangeRate(newExchangeRate, state); state.totalSharesLastUpdate = uint128(currentTotalShares); state.lastUpdateTimestamp = currentTime; emit ExchangeRateUpdated(uint96(currentExchangeRate), newExchangeRate, currentTime); } /** * @notice Claim pending fees. * @dev This function must be called by the BoringVault. * @dev This function will lose precision if the exchange rate * decimals is greater than the feeAsset's decimals. */ function claimFees(ERC20 feeAsset) external { if (msg.sender != address(vault)) revert AccountantWithRateProviders__OnlyCallableByBoringVault(); AccountantState storage state = accountantState; if (state.isPaused) revert AccountantWithRateProviders__Paused(); if (state.feesOwedInBase == 0) revert AccountantWithRateProviders__ZeroFeesOwed(); // Determine amount of fees owed in feeAsset. uint256 feesOwedInFeeAsset; RateProviderData memory data = rateProviderData[feeAsset]; if (address(feeAsset) == address(base)) { feesOwedInFeeAsset = state.feesOwedInBase; } else { uint8 feeAssetDecimals = ERC20(feeAsset).decimals(); uint256 feesOwedInBaseUsingFeeAssetDecimals = _changeDecimals(state.feesOwedInBase, decimals, feeAssetDecimals); if (data.isPeggedToBase) { feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals; } else { uint256 rate = data.rateProvider.getRate(); feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals.mulDivDown(10 ** feeAssetDecimals, rate); } } // Zero out fees owed. state.feesOwedInBase = 0; // Transfer fee asset to payout address. feeAsset.safeTransferFrom(msg.sender, state.payoutAddress, feesOwedInFeeAsset); emit FeesClaimed(address(feeAsset), feesOwedInFeeAsset); } // ========================================= VIEW FUNCTIONS ========================================= /** * @notice Get this BoringVault's current rate in the base. */ function getRate() public view returns (uint256 rate) { rate = accountantState.exchangeRate; } /** * @notice Get this BoringVault's current rate in the base. * @dev Revert if paused. */ function getRateSafe() external view returns (uint256 rate) { if (accountantState.isPaused) revert AccountantWithRateProviders__Paused(); rate = getRate(); } /** * @notice Get this BoringVault's current rate in the provided quote. * @dev `quote` must have its RateProviderData set, else this will revert. * @dev This function will lose precision if the exchange rate * decimals is greater than the quote's decimals. */ function getRateInQuote(ERC20 quote) public view returns (uint256 rateInQuote) { if (address(quote) == address(base)) { rateInQuote = accountantState.exchangeRate; } else { RateProviderData memory data = rateProviderData[quote]; uint8 quoteDecimals = ERC20(quote).decimals(); uint256 exchangeRateInQuoteDecimals = _changeDecimals(accountantState.exchangeRate, decimals, quoteDecimals); if (data.isPeggedToBase) { rateInQuote = exchangeRateInQuoteDecimals; } else { uint256 quoteRate = data.rateProvider.getRate(); uint256 oneQuote = 10 ** quoteDecimals; rateInQuote = oneQuote.mulDivDown(exchangeRateInQuoteDecimals, quoteRate); } } } /** * @notice Get this BoringVault's current rate in the provided quote. * @dev `quote` must have its RateProviderData set, else this will revert. * @dev Revert if paused. */ function getRateInQuoteSafe(ERC20 quote) external view returns (uint256 rateInQuote) { if (accountantState.isPaused) revert AccountantWithRateProviders__Paused(); rateInQuote = getRateInQuote(quote); } /** * @notice Preview the result of an update to the exchange rate. * @return updateWillPause Whether the update will pause the contract. * @return newFeesOwedInBase The new fees owed in base. * @return totalFeesOwedInBase The total fees owed in base. */ function previewUpdateExchangeRate(uint96 newExchangeRate) external view virtual returns (bool updateWillPause, uint256 newFeesOwedInBase, uint256 totalFeesOwedInBase) { ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) = _beforeUpdateExchangeRate(newExchangeRate); updateWillPause = shouldPause; totalFeesOwedInBase = state.feesOwedInBase; if (!shouldPause) { (uint256 platformFeesOwedInBase, uint256 shareSupplyToUse) = _calculatePlatformFee( state.totalSharesLastUpdate, state.lastUpdateTimestamp, state.platformFee, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime ); uint256 performanceFeesOwedInBase; if (newExchangeRate > state.highwaterMark) { (performanceFeesOwedInBase,) = _calculatePerformanceFee( newExchangeRate, shareSupplyToUse, state.highwaterMark, state.performanceFee ); } newFeesOwedInBase = platformFeesOwedInBase + performanceFeesOwedInBase; totalFeesOwedInBase += newFeesOwedInBase; } } // ========================================= INTERNAL HELPER FUNCTIONS ========================================= /** * @notice Used to change the decimals of precision used for an amount. */ function _changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) { if (fromDecimals == toDecimals) { return amount; } else if (fromDecimals < toDecimals) { return amount * 10 ** (toDecimals - fromDecimals); } else { return amount / 10 ** (fromDecimals - toDecimals); } } /** * @notice Check if the new exchange rate is outside of the allowed bounds or if not enough time has passed. */ function _beforeUpdateExchangeRate(uint96 newExchangeRate) internal view returns ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) { state = accountantState; if (state.isPaused) revert AccountantWithRateProviders__Paused(); currentTime = uint64(block.timestamp); currentExchangeRate = state.exchangeRate; currentTotalShares = vault.totalSupply(); shouldPause = currentTime < state.lastUpdateTimestamp + state.minimumUpdateDelayInSeconds || newExchangeRate > currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeUpper, 1e4) || newExchangeRate < currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeLower, 1e4); } /** * @notice Set the exchange rate. */ function _setExchangeRate(uint96 newExchangeRate, AccountantState storage state) internal virtual returns (uint96) { state.exchangeRate = newExchangeRate; return newExchangeRate; } /** * @notice Calculate platform fees. */ function _calculatePlatformFee( uint128 totalSharesLastUpdate, uint64 lastUpdateTimestamp, uint16 platformFee, uint96 newExchangeRate, uint256 currentExchangeRate, uint256 currentTotalShares, uint64 currentTime ) internal view returns (uint256 platformFeesOwedInBase, uint256 shareSupplyToUse) { shareSupplyToUse = currentTotalShares; // Use the minimum between current total supply and total supply for last update. if (totalSharesLastUpdate < shareSupplyToUse) { shareSupplyToUse = totalSharesLastUpdate; } // Determine platform fees owned. if (platformFee > 0) { uint256 timeDelta = currentTime - lastUpdateTimestamp; uint256 minimumAssets = newExchangeRate > currentExchangeRate ? shareSupplyToUse.mulDivDown(currentExchangeRate, ONE_SHARE) : shareSupplyToUse.mulDivDown(newExchangeRate, ONE_SHARE); uint256 platformFeesAnnual = minimumAssets.mulDivDown(platformFee, 1e4); platformFeesOwedInBase = platformFeesAnnual.mulDivDown(timeDelta, 365 days); } } /** * @notice Calculate performance fees. */ function _calculatePerformanceFee( uint96 newExchangeRate, uint256 shareSupplyToUse, uint96 datum, uint16 performanceFee ) internal view returns (uint256 performanceFeesOwedInBase, uint256 yieldEarned) { uint256 changeInExchangeRate = newExchangeRate - datum; yieldEarned = changeInExchangeRate.mulDivDown(shareSupplyToUse, ONE_SHARE); if (performanceFee > 0) { performanceFeesOwedInBase = yieldEarned.mulDivDown(performanceFee, 1e4); } } /** * @notice Calculate fees owed in base. * @dev This function will update the highwater mark if the new exchange rate is higher. */ function _calculateFeesOwed( AccountantState storage state, uint96 newExchangeRate, uint256 currentExchangeRate, uint256 currentTotalShares, uint64 currentTime ) internal virtual { // Only update fees if we are not paused. // Update fee accounting. (uint256 newFeesOwedInBase, uint256 shareSupplyToUse) = _calculatePlatformFee( state.totalSharesLastUpdate, state.lastUpdateTimestamp, state.platformFee, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime ); // Account for performance fees. if (newExchangeRate > state.highwaterMark) { (uint256 performanceFeesOwedInBase,) = _calculatePerformanceFee(newExchangeRate, shareSupplyToUse, state.highwaterMark, state.performanceFee); // Add performance fees to fees owed. newFeesOwedInBase += performanceFeesOwedInBase; // Always update the highwater mark if the new exchange rate is higher. // This way if we are not iniitiall taking performance fees, we can start taking them // without back charging them on past performance. state.highwaterMark = newExchangeRate; } state.feesOwedInBase += uint128(newFeesOwedInBase); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Arithmetic library with operations for fixed-point numbers. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) library FixedPointMathLib { /*////////////////////////////////////////////////////////////// SIMPLIFIED FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ uint256 internal constant MAX_UINT256 = 2**256 - 1; uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. } function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. } function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. } function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. } /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ function mulDivDown( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // Divide x * y by the denominator. z := div(mul(x, y), denominator) } } function mulDivUp( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // If x * y modulo the denominator is strictly greater than 0, // 1 is added to round up the division of x * y by the denominator. z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) } } function rpow( uint256 x, uint256 n, uint256 scalar ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { switch x case 0 { switch n case 0 { // 0 ** 0 = 1 z := scalar } default { // 0 ** n = 0 z := 0 } } default { switch mod(n, 2) case 0 { // If n is even, store scalar in z for now. z := scalar } default { // If n is odd, store x in z for now. z := x } // Shifting right by 1 is like dividing by 2. let half := shr(1, scalar) for { // Shift n right by 1 before looping to halve it. n := shr(1, n) } n { // Shift n right by 1 each iteration to halve it. n := shr(1, n) } { // Revert immediately if x ** 2 would overflow. // Equivalent to iszero(eq(div(xx, x), x)) here. if shr(128, x) { revert(0, 0) } // Store x squared. let xx := mul(x, x) // Round to the nearest number. let xxRound := add(xx, half) // Revert if xx + half overflowed. if lt(xxRound, xx) { revert(0, 0) } // Set x to scaled xxRound. x := div(xxRound, scalar) // If n is even: if mod(n, 2) { // Compute z * x. let zx := mul(z, x) // If z * x overflowed: if iszero(eq(div(zx, x), z)) { // Revert if x is non-zero. if iszero(iszero(x)) { revert(0, 0) } } // Round to the nearest number. let zxRound := add(zx, half) // Revert if zx + half overflowed. if lt(zxRound, zx) { revert(0, 0) } // Return properly scaled zxRound. z := div(zxRound, scalar) } } } } } /*////////////////////////////////////////////////////////////// GENERAL NUMBER UTILITIES //////////////////////////////////////////////////////////////*/ function sqrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let y := x // We start y at x, which will help us make our initial estimate. z := 181 // The "correct" value is 1, but this saves a multiplication later. // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. // We check y >= 2^(k + 8) but shift right by k bits // each branch to ensure that if x >= 256, then y >= 256. if iszero(lt(y, 0x10000000000000000000000000000000000)) { y := shr(128, y) z := shl(64, z) } if iszero(lt(y, 0x1000000000000000000)) { y := shr(64, y) z := shl(32, z) } if iszero(lt(y, 0x10000000000)) { y := shr(32, y) z := shl(16, z) } if iszero(lt(y, 0x1000000)) { y := shr(16, y) z := shl(8, z) } // Goal was to get z*z*y within a small factor of x. More iterations could // get y in a tighter range. Currently, we will have y in [256, 256*2^16). // We ensured y >= 256 so that the relative difference between y and y+1 is small. // That's not possible if x < 256 but we can just verify those cases exhaustively. // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. // There is no overflow risk here since y < 2^136 after the first branch above. z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) // If x+1 is a perfect square, the Babylonian method cycles between // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. // If you don't care whether the floor or ceil square root is returned, you can remove this statement. z := sub(z, lt(div(x, z), z)) } } function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Mod x by y. Note this will return // 0 instead of reverting if y is zero. z := mod(x, y) } } function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { // Divide x by y. Note this will return // 0 instead of reverting if y is zero. r := div(x, y) } } function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Add 1 to x * y if x % y > 0. Note this will // return 0 instead of reverting if y is zero. z := add(gt(mod(x, y), 0), div(x, y)) } } }
// SPDX-License-Identifier: UNLICENSED // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.8.0; interface IRateProvider { function getRate() external view returns (uint256); }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. success := call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { success := iszero(or(iszero(extcodesize(token)), returndatasize())) } } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { success := iszero(or(iszero(extcodesize(token)), returndatasize())) } } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { success := iszero(or(iszero(extcodesize(token)), returndatasize())) } } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {BoringChef} from "src/boring-chef/BoringChef.sol"; contract BoringVault is BoringChef, ERC721Holder, ERC1155Holder { using Address for address; using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; // ========================================= STATE ========================================= /** * @notice Contract responsbile for implementing `beforeTransfer`. */ BeforeTransferHook public hook; //============================== EVENTS =============================== event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares); event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares); //============================== CONSTRUCTOR =============================== constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals) BoringChef(_owner, _name, _symbol, _decimals) {} //============================== MANAGE =============================== /** * @notice Allows manager to make an arbitrary function call from this contract. * @dev Callable by MANAGER_ROLE. */ function manage(address target, bytes calldata data, uint256 value) external requiresAuth returns (bytes memory result) { // Start a new epoch on every rebalance _rollOverEpoch(); // Continue with rebalance result = target.functionCallWithValue(data, value); } /** * @notice Allows manager to make arbitrary function calls from this contract. * @dev Callable by MANAGER_ROLE. */ function manage(address[] calldata targets, bytes[] calldata data, uint256[] calldata values) external requiresAuth returns (bytes[] memory results) { // Start a new epoch on every rebalance _rollOverEpoch(); // Continue with rebalance uint256 targetsLength = targets.length; results = new bytes[](targetsLength); for (uint256 i; i < targetsLength; ++i) { results[i] = targets[i].functionCallWithValue(data[i], values[i]); } } //============================== ENTER =============================== /** * @notice Allows minter to mint shares, in exchange for assets. * @dev If assetAmount is zero, no assets are transferred in. * @dev Callable by MINTER_ROLE. */ function enter(address from, ERC20 asset, uint256 assetAmount, address to, uint256 shareAmount) external requiresAuth { // Transfer assets in if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount); // Mint shares. _mint(to, shareAmount); emit Enter(from, address(asset), assetAmount, to, shareAmount); } //============================== EXIT =============================== /** * @notice Allows burner to burn shares, in exchange for assets. * @dev If assetAmount is zero, no assets are transferred out. * @dev Callable by BURNER_ROLE. */ function exit(address to, ERC20 asset, uint256 assetAmount, address from, uint256 shareAmount) external requiresAuth { // Burn shares. _burn(from, shareAmount); // Transfer assets out. if (assetAmount > 0) asset.safeTransfer(to, assetAmount); emit Exit(to, address(asset), assetAmount, from, shareAmount); } //============================== BEFORE TRANSFER HOOK =============================== /** * @notice Sets the share locker. * @notice If set to zero address, the share locker logic is disabled. * @dev Callable by OWNER_ROLE. */ function setBeforeTransferHook(address _hook) external requiresAuth { hook = BeforeTransferHook(_hook); } /** * @notice Call `beforeTransferHook` passing in `from` `to`, and `msg.sender`. */ function _callBeforeTransfer(address from, address to) internal view { if (address(hook) != address(0)) hook.beforeTransfer(from, to, msg.sender); } function transfer(address to, uint256 amount) public override(BoringChef) returns (bool) { _callBeforeTransfer(msg.sender, to); return super.transfer(to, amount); } function transferFrom(address from, address to, uint256 amount) public override(BoringChef) returns (bool) { _callBeforeTransfer(from, to); return super.transferFrom(from, to, amount); } //============================== RECEIVE =============================== receive() external payable {} }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) abstract contract Auth { event OwnershipTransferred(address indexed user, address indexed newOwner); event AuthorityUpdated(address indexed user, Authority indexed newAuthority); address public owner; Authority public authority; constructor(address _owner, Authority _authority) { owner = _owner; authority = _authority; emit OwnershipTransferred(msg.sender, _owner); emit AuthorityUpdated(msg.sender, _authority); } modifier requiresAuth() virtual { require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED"); _; } function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be // aware that this makes protected functions uncallable even to the owner if the authority is out of order. return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; } function setAuthority(Authority newAuthority) public virtual { // We check if the caller is the owner first because we want to ensure they can // always swap out the authority even if it's reverting or using up a lot of gas. require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); authority = newAuthority; emit AuthorityUpdated(msg.sender, newAuthority); } function transferOwnership(address newOwner) public virtual requiresAuth { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } } /// @notice A generic interface for a contract which provides authorization data to an Auth instance. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) interface Authority { function canCall( address user, address target, bytes4 functionSig ) external view returns (bool); }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; interface IPausable { function pause() external; function unpause() external; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol) pragma solidity ^0.8.20; import {Errors} from "./Errors.sol"; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @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://consensys.net/diligence/blog/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.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert Errors.InsufficientBalance(address(this).balance, amount); } (bool success, bytes memory returndata) = recipient.call{value: amount}(""); if (!success) { _revert(returndata); } } /** * @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 or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {Errors.FailedCall} error. * * 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. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @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`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert Errors.InsufficientBalance(address(this).balance, value); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case * of an unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {Errors.FailedCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}. */ function _revert(bytes memory returndata) private pure { // 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 ("memory-safe") { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert Errors.FailedCall(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/utils/ERC721Holder.sol) pragma solidity ^0.8.20; import {IERC721Receiver} from "../IERC721Receiver.sol"; /** * @dev Implementation of the {IERC721Receiver} interface. * * Accepts all token transfers. * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or * {IERC721-setApprovalForAll}. */ abstract contract ERC721Holder is IERC721Receiver { /** * @dev See {IERC721Receiver-onERC721Received}. * * Always returns `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC721Received.selector; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol) pragma solidity ^0.8.20; import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; import {IERC1155Receiver} from "../IERC1155Receiver.sol"; /** * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens. * * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be * stuck. */ abstract contract ERC1155Holder is ERC165, IERC1155Receiver { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); } function onERC1155Received( address, address, uint256, uint256, bytes memory ) public virtual override returns (bytes4) { return this.onERC1155Received.selector; } function onERC1155BatchReceived( address, address, uint256[] memory, uint256[] memory, bytes memory ) public virtual override returns (bytes4) { return this.onERC1155BatchReceived.selector; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; interface BeforeTransferHook { function beforeTransfer(address from, address to, address operator) external view; }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {BoringSafe} from "./BoringSafe.sol"; /// @title BoringChef /// @author Shivaansh Kapoor, Jet Jadeja, Jack Corddry /// @notice A contract for reward accounting, retroactive distribution, and claims for share based vaults. contract BoringChef is Auth, ERC20 { using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ error ArrayLengthMismatch(); error NoFutureEpochRewards(); error InvalidRewardCampaignDuration(); error MustClaimAtLeastOneReward(); error CannotClaimFutureReward(); error RewardClaimedAlready(uint256 rewardId); error CannotDisableRewardAccrualMoreThanOnce(); error CannotEnableRewardAccrualMoreThanOnce(); error OnlyClaimant(); /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event EpochStarted(uint256 indexed epoch, uint256 eligibleShares, uint256 startTimestamp); event UserRewardsClaimed(address indexed user, address indexed token, uint256 rewardId, uint256 amount); event RewardsDistributed( address indexed token, uint256 indexed startEpoch, uint256 indexed endEpoch, uint256 amount, uint256 rewardId ); event UserDepositedIntoEpoch(address indexed user, uint256 indexed epoch, uint256 shareAmount); event UserWithdrawnFromEpoch(address indexed user, uint256 indexed epoch, uint256 shareAmount); /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ /// @dev A record of a user's balance changing at a specific epoch struct BalanceUpdate { /// @dev The epoch in which the deposit was made uint48 epoch; /// @dev The total number of shares the user has at this epoch uint128 totalSharesBalance; } /// @dev A record of an epoch struct Epoch { /// @dev The total number of shares eligible for rewards at this epoch /// This is not the total number of shares deposited, but the total number /// of shares that have been deposited and are eligible for rewards uint128 eligibleShares; /// @dev The timestamp at which the epoch starts uint64 startTimestamp; /// @dev The timestamp at which the epoch ends /// This is set to 0 if the epoch is not over uint64 endTimestamp; } /// @dev A record of a reward struct Reward { /// @dev The epoch at which the reward starts uint48 startEpoch; /// @dev The epoch at which the reward ends uint48 endEpoch; /// @dev The token being rewarded address token; /// @dev The rate at which the reward token is distributed per second uint256 rewardRate; } /*////////////////////////////////////////////////////////////// STATE VARIABLES //////////////////////////////////////////////////////////////*/ /// @dev A contract to hold rewards to make sure the BoringVault doesn't spend them BoringSafe public immutable boringSafe; /// @dev The current epoch uint48 public currentEpoch; /// @dev A record of all epochs mapping(uint48 => Epoch) public epochs; /// @dev Maps users to an array of their balance changes mapping(address user => BalanceUpdate[]) public balanceUpdates; /// @dev Maps rewards to reward IDs mapping(uint256 rewardId => Reward) public rewards; uint256 public maxRewardId; /// @dev Maps users to a boolean indicating if they have disabled reward accrual mapping(address user => bool isDisabled) public addressToIsDisabled; /// @dev Maps users to a claimant who can claim rewards on their behalf mapping(address user => address claimant) public addressToClaimant; /// @dev Nested mapping to efficiently keep track of claimed rewards per user /// @dev A rewardBucket contains batches of 256 contiguous rewardIds (Bucket 0: rewardIds 0-255, Bucket 1: rewardIds 256-527, ...) /// @dev claimedRewards is a 256 bit bit-field where each bit represents if a rewardId in that bucket (monotonically increasing) has been claimed. mapping(address user => mapping(uint256 rewardBucket => uint256 claimedRewards)) public userToRewardBucketToClaimedRewards; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /// @notice Initialize the contract. /// @dev We do this by setting the share token and initializing the first epoch. /// @param _owner The owner of the BoringVault. /// @param _name The name of the share token. /// @param _symbol The symbol of the share token. /// @param _decimals The decimals of the share token. constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals) Auth(_owner, Authority(address(0))) ERC20(_name, _symbol, _decimals) { // Deploy the BoringSafe that the BoringChef will use for escrowing distributed rewards. boringSafe = new BoringSafe(); } /*////////////////////////////////////////////////////////////// REWARD DISTRIBUTION LOGIC //////////////////////////////////////////////////////////////*/ /// @notice Roll over to the next epoch. /// @dev Can only be called by an authorized address. function rollOverEpoch() external requiresAuth { _rollOverEpoch(); } /// @notice Disable reward accrual for a given address /// @dev Can only be called by an authorized address function disableRewardAccrual(address user) external requiresAuth { // Check that reward accrual hasn't been disabled already if (addressToIsDisabled[user] == true) { revert CannotDisableRewardAccrualMoreThanOnce(); } // Decrease the user's participation by their entire balance // They won't be eligible for rewards from the current epoch onwards unless they are reenabled _decreaseCurrentAndNextEpochParticipation(user, uint128(balanceOf[user])); addressToIsDisabled[user] = true; } /// @notice Enable reward accrual for a given address /// @dev Can only be called by an authorized address function enableRewardAccrual(address user) external requiresAuth { // Check that reward accrual hasn't been enabled already if (addressToIsDisabled[user] == false) { revert CannotEnableRewardAccrualMoreThanOnce(); } // Increase the user's participation by their entire balance // Their entire balance will be eligible for rewards from the next epoch onwards addressToIsDisabled[user] = false; _increaseNextEpochParticipation(user, uint128(balanceOf[user])); } /// @notice Assign a claimant to be able to claim rewards for a user. /// @dev Can only be called by an authorized address function assignClaimantForUser(address user, address claimant) external requiresAuth { // Set the claimant as the address that can claim rewards on the user's behalf addressToClaimant[user] = claimant; } /// @notice Distribute rewards retroactively to users deposited during a given epoch range for multiple campaigns. /// @dev Creates new Reward objects and stores them in the rewards mapping, and transfers the reward tokens to the BoringSafe. /// @param tokens Array of addresses for the reward tokens. /// @param amounts Array of reward token amounts to distribute. /// @param startEpochs Array of start epochs for each reward distribution. /// @param endEpochs Array of end epochs for each reward distribution. function distributeRewards( address[] calldata tokens, uint256[] calldata amounts, uint48[] calldata startEpochs, uint48[] calldata endEpochs ) external requiresAuth { // Cache length for gas op uint256 numRewards = tokens.length; // Ensure that all arrays are the same length. if (numRewards != amounts.length || numRewards != startEpochs.length || numRewards != endEpochs.length) { revert ArrayLengthMismatch(); } // Loop over each set of parameters. for (uint256 i = 0; i < numRewards; ++i) { // Check that the start and end epochs are valid. if (startEpochs[i] > endEpochs[i]) { revert InvalidRewardCampaignDuration(); } if (endEpochs[i] >= currentEpoch) { revert NoFutureEpochRewards(); } // Get the start and end epoch data. Epoch storage startEpochData = epochs[startEpochs[i]]; Epoch storage endEpochData = epochs[endEpochs[i]]; // Create a new reward and update the max reward ID. rewards[maxRewardId] = Reward({ startEpoch: startEpochs[i], endEpoch: endEpochs[i], token: tokens[i], // Calculate the reward rate over the epoch period. // Consider the case where endEpochData.endTimestamp == startEpochData.startTimestamp rewardRate: amounts[i].divWadDown(endEpochData.endTimestamp - startEpochData.startTimestamp) }); // Transfer the reward tokens to the BoringSafe. ERC20(tokens[i]).safeTransferFrom(msg.sender, address(boringSafe), amounts[i]); // Emit an event for this reward distribution. emit RewardsDistributed(tokens[i], startEpochs[i], endEpochs[i], amounts[i], maxRewardId++); } } /*////////////////////////////////////////////////////////////// REWARD CLAIMING LOGIC //////////////////////////////////////////////////////////////*/ /// @notice Claims rewards (specified as rewardIds) for the caller. /// @dev Should be permissionless in normal circumstances. /// @param rewardIds The rewardIds to claim rewards for. function claimRewards(uint256[] calldata rewardIds) external requiresAuth { // Claim rewards accrued to the caller _claimRewards(rewardIds, msg.sender); } /// @notice Claims rewards (specified as rewardIds) to the caller on behalf of the specified user. /// @dev Only the claimant for the specified user can call this function /// @param rewardIds The rewardIds to claim rewards for. /// @param user The address of the user to claim rewards on behalf of. function claimRewardsOnBehalfOfUser(uint256[] calldata rewardIds, address user) external requiresAuth { // Check the caller is the designated claimant for the user if (addressToClaimant[user] != msg.sender) revert OnlyClaimant(); // Claim rewards accrued to the user to the claimant _claimRewards(rewardIds, user); } /*////////////////////////////////////////////////////////////// TRANSFER LOGIC //////////////////////////////////////////////////////////////*/ /// @notice Transfer shares /// @dev This function is overridden from the ERC20 implementation to account for incentives. function transfer(address to, uint256 amount) public virtual override(ERC20) returns (bool success) { // Transfer shares from msg.sender to "to" success = super.transfer(to, amount); // Account for the transfer for the sender and forfeit incentives for current epoch for msg.sender _decreaseCurrentAndNextEpochParticipation(msg.sender, uint128(amount)); // Account for the transfer for the recipient and transfer incentives for next epoch onwards to "to" _increaseNextEpochParticipation(to, uint128(amount)); } /// @notice Transfer shares from one user to another /// @dev This function is overridden from the ERC20 implementation to account for incentives. function transferFrom(address from, address to, uint256 amount) public virtual override(ERC20) returns (bool success) { // Transfer shares from "from" to "to" success = super.transferFrom(from, to, amount); // Account for the transfer for the sender and forfeit incentives for current epoch for "from" _decreaseCurrentAndNextEpochParticipation(from, uint128(amount)); // Account for the transfer for the recipient and transfer incentives for next epoch onwards to "to" _increaseNextEpochParticipation(to, uint128(amount)); } /*////////////////////////////////////////////////////////////// VIEW FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Get all balance updates for a user. function getBalanceUpdates(address user) external view returns (BalanceUpdate[] memory) { return balanceUpdates[user]; } /// @notice Get a user's reward balance for a given reward ID. /// @param user The user to get the reward balance for. /// @param rewardId The ID of the reward to get the balance for. function getUserRewardBalance(address user, uint256 rewardId) external view returns (uint256) { // Fetch all balance change updates for the caller. BalanceUpdate[] storage userBalanceUpdates = balanceUpdates[user]; // Retrieve the reward ID, start epoch, and end epoch. Reward storage reward = rewards[rewardId]; // Initialize a local accumulator for the total reward owed. uint256 rewardsOwed = 0; // Scale rate up by 1e18 to avoid precision loss uint256 rateWAD = reward.rewardRate * 1e18; // We want to iterate over the epoch range [startEpoch..endEpoch], // summing up the user's share of tokens from each epoch. for (uint48 epoch = reward.startEpoch; epoch <= reward.endEpoch; epoch++) { // Determine the user's share balance during this epoch. (, uint128 userBalanceAtEpoch) = _findLatestBalanceUpdateForEpoch(epoch, userBalanceUpdates); // If the user is owed rewards for this epoch, remit them if (userBalanceAtEpoch > 0) { Epoch storage epochData = epochs[epoch]; // Compute user fraction = userBalance / totalShares. uint256 userFraction = uint256(userBalanceAtEpoch).divWadDown(epochData.eligibleShares); // Figure out how many tokens were distributed in this epoch // for the specified reward ID: uint256 epochDuration = epochData.endTimestamp - epochData.startTimestamp; uint256 epochReward = rateWAD.mulWadDown(epochDuration); // Multiply epochReward * fraction = userRewardThisEpoch. // Add that to rewardsOwed. rewardsOwed += epochReward.mulWadDown(userFraction); } } // Scale down by 1e18 to get the final reward amount rewardsOwed /= 1e18; return rewardsOwed; } /// @notice Get the user's current eligible balance. /// @dev Returns the user's eligible balance for the current epoch, not the next epoch function getUserEligibleBalance(address user) external view returns (uint128) { // Find the user's balance at the current epoch (, uint128 userBalance) = _findLatestBalanceUpdateForEpoch(currentEpoch, balanceUpdates[user]); return userBalance; } /// @notice Get the array of balance updates for a user. /// @param user The user to get the balance updates for. function getTotalBalanceUpdates(address user) public view returns (uint256) { return balanceUpdates[user].length; } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Mint shares /// @dev This function is overridden from the ERC20 implementation to account for incentives. function _mint(address to, uint256 amount) internal override(ERC20) { // Mint the shares to the depositor super._mint(to, amount); // Mark this deposit eligible for incentives earned from the next epoch onwards _increaseNextEpochParticipation(to, uint128(amount)); } /// @notice Burn shares /// @dev This function is overridden from the ERC20 implementation to account for incentives. function _burn(address from, uint256 amount) internal override(ERC20) { // Burn the shares from the depositor super._burn(from, amount); // Mark this deposit eligible for incentives earned from the next epoch onwards _decreaseCurrentAndNextEpochParticipation(from, uint128(amount)); } /// @dev Roll over to the next epoch. /// @dev Should be called on every boring vault rebalance. function _rollOverEpoch() internal { // Cache current epoch for gas savings uint48 currEpoch = currentEpoch; // Get the current epoch data Epoch storage currentEpochData = epochs[currEpoch]; // Skip rollOver if the current epoch started at this timestamp (multiple rebalances in the same block) if (currentEpochData.startTimestamp == block.timestamp) { return; } // Get the next epoch data Epoch storage nextEpochData = epochs[++currEpoch]; // Update the current epoch's end timestamp and the next epoch's start timestamp. currentEpochData.endTimestamp = uint64(block.timestamp); nextEpochData.startTimestamp = uint64(block.timestamp); // Update the eligible shares for the next epoch by rolling them over. nextEpochData.eligibleShares += currentEpochData.eligibleShares; // Increment the currentEpoch and emit event for epoch start emit EpochStarted(++currentEpoch, nextEpochData.eligibleShares, block.timestamp); } /// @notice Increase the user's share balance for the next epoch function _increaseNextEpochParticipation(address user, uint128 amount) internal { // Skip participation accounting if it has been disabled for this address if (addressToIsDisabled[user]) return; // Cache next epoch for gas op uint48 nextEpoch = currentEpoch + 1; // Handle updating the balance accounting for this user BalanceUpdate[] storage userBalanceUpdates = balanceUpdates[user]; if (userBalanceUpdates.length == 0) { // If there are no balance updates, create a new one userBalanceUpdates.push(BalanceUpdate({epoch: nextEpoch, totalSharesBalance: amount})); } else { // Get the last balance update BalanceUpdate storage lastBalanceUpdate = userBalanceUpdates[userBalanceUpdates.length - 1]; // Ensure no duplicate entries if (lastBalanceUpdate.epoch == nextEpoch) { // Handle case for multiple deposits into an epoch lastBalanceUpdate.totalSharesBalance += amount; } else { // Handle case for the first deposit for an epoch userBalanceUpdates.push( BalanceUpdate({ epoch: nextEpoch, // Add the specified amount to the last balance update's total shares totalSharesBalance: (lastBalanceUpdate.totalSharesBalance + amount) }) ); } } // Get the epoch data for the current epoch and next epoch (epoch to deposit for) Epoch storage epochData = epochs[nextEpoch]; // Account for deposit for the specified epoch epochData.eligibleShares += amount; // Emit event for this deposit emit UserDepositedIntoEpoch(user, nextEpoch, amount); } /// @notice Decrease the user's share balance for the current epoch. /// @dev If the user has a deposit for the next epoch, it will withdraw as much as possible from the next epoch and the rest from the current. function _decreaseCurrentAndNextEpochParticipation(address user, uint128 amount) internal { // Skip participation accounting if it has been disabled for this address. if (addressToIsDisabled[user]) return; // Cache the current epoch, next epoch, and user's balance. uint48 currEpoch = currentEpoch; uint48 nextEpoch = currEpoch + 1; // Cache the user's balance updates and its length. BalanceUpdate[] storage userBalanceUpdates = balanceUpdates[user]; uint256 balanceUpdatesLength = userBalanceUpdates.length; // It is assumed that len > 0 when withdrawing. BalanceUpdate storage lastBalanceUpdate = userBalanceUpdates[balanceUpdatesLength - 1]; // CASE 1: No deposit for the next epoch. if (lastBalanceUpdate.epoch <= currEpoch) { // CASE 1.1: Last balance update is for the current epoch. if (lastBalanceUpdate.epoch == currEpoch) { // If already updated for the current epoch, just update the total shares to reflect the decrease. lastBalanceUpdate.totalSharesBalance -= amount; // CASE 1.2: Last balance update is a previous epoch. Create a new update to preserve order of updates. } else { // Append a new update for the current epoch. userBalanceUpdates.push( BalanceUpdate({ epoch: currEpoch, // Deduct the amount from the last balance update by the shares to decrease by totalSharesBalance: (lastBalanceUpdate.totalSharesBalance - amount) }) ); } // Account for withdrawal in the current epoch. epochs[currEpoch].eligibleShares -= amount; // Emit event for this withdrawal. emit UserWithdrawnFromEpoch(user, currEpoch, amount); return; } // CASE 2: Only deposit made is for the next epoch. if (balanceUpdatesLength == 1) { // If there is only one balance update, it has to be for the next epoch. Update it and adjust the epoch's eligible shares. lastBalanceUpdate.totalSharesBalance -= amount; epochs[nextEpoch].eligibleShares -= amount; // Emit event for this withdrawal emit UserWithdrawnFromEpoch(user, nextEpoch, amount); return; } // Get the second-to-last update. BalanceUpdate storage secondLastBalanceUpdate = userBalanceUpdates[balanceUpdatesLength - 2]; // The amount deposited for the next epoch. uint128 amountDepositedIntoNextEpoch = lastBalanceUpdate.totalSharesBalance - secondLastBalanceUpdate.totalSharesBalance; // CASE 3: Deposit Made for the next epoch and the full withdrawal amount cannot be removed solely from the last update. if (amount > amountDepositedIntoNextEpoch) { // The amount deposited into the next epoch will be withdrawn completely in this case: amountDepositedIntoNextEpoch == amountToWithdrawFromNextEpoch. // The rest of the withdrawal amount will be deducted from the current epoch. uint128 amountToWithdrawFromCurrentEpoch = (amount - amountDepositedIntoNextEpoch); if (secondLastBalanceUpdate.epoch == currEpoch) { // Withdraw the amount left over from withdrawing from the next epoch from the current epoch. secondLastBalanceUpdate.totalSharesBalance -= amountToWithdrawFromCurrentEpoch; epochs[currEpoch].eligibleShares -= amountToWithdrawFromCurrentEpoch; // Withdraw the amount that was deposited into the next epoch completely epochs[nextEpoch].eligibleShares -= amountDepositedIntoNextEpoch; // Since the next epoch's deposit was completely cleared, we can pop the next epoch's (last) update off. userBalanceUpdates.pop(); } else { // Update the last entry to be for the current epoch. lastBalanceUpdate.epoch = currEpoch; lastBalanceUpdate.totalSharesBalance -= amount; // Withdraw the amount to withdraw from the current epoch from total eligible shared. epochs[currEpoch].eligibleShares -= amountToWithdrawFromCurrentEpoch; // Withdraw the full amount deposited into the next epoch. epochs[nextEpoch].eligibleShares -= amountDepositedIntoNextEpoch; } // Emit event for the withdrawals. emit UserWithdrawnFromEpoch(user, nextEpoch, amountDepositedIntoNextEpoch); emit UserWithdrawnFromEpoch(user, currEpoch, amountToWithdrawFromCurrentEpoch); return; // CASE 4: The full amount can be withdrawn from the next epoch. Modify the next epoch (last) update. } else { lastBalanceUpdate.totalSharesBalance -= amount; epochs[nextEpoch].eligibleShares -= amount; // Emit event for this withdrawal. emit UserWithdrawnFromEpoch(user, nextEpoch, amount); return; } } /// @notice Claims rewards (specified as rewardIds) for the caller. /// @dev Should be permissionless in normal circumstances. /// @param rewardIds The rewardIds to claim rewards for. /// @param user The address of the user to claim rewards for. function _claimRewards(uint256[] calldata rewardIds, address user) internal { // Get the epoch range for all rewards to claim and the corresponding Reward structs. (uint48 minEpoch, uint48 maxEpoch, Reward[] memory rewardsToClaim) = _getEpochRangeForRewards(rewardIds, user); // Fetch the caller's balance update history. BalanceUpdate[] storage userBalanceUpdates = balanceUpdates[user]; uint48 firstEpochDeposited = userBalanceUpdates[0].epoch; // Use the later of the minEpoch from rewards or the user's first deposit epoch. if (firstEpochDeposited > minEpoch) { minEpoch = firstEpochDeposited; } // Precompute the user's share ratios and epoch durations from minEpoch to maxEpoch. (uint256[] memory userShareRatios, uint256[] memory epochDurations) = _computeUserShareRatiosAndDurations(minEpoch, maxEpoch, userBalanceUpdates); // Get the rewards owed per unique token in rewardIds. (address[] memory uniqueTokens, uint256[] memory tokenAmounts) = _computeRewardsPerUniqueToken(rewardIds, rewardsToClaim, minEpoch, userShareRatios, epochDurations); // Transfer all reward tokens to the caller boringSafe.transfer(uniqueTokens, tokenAmounts, msg.sender); } /// @dev Retrieves the epoch range for a set of reward campaigns and marks them as claimed. /// @param rewardIds An array of reward campaign identifiers to process. /// @param user The user to mark these rewards as claimed for. /// @return minEpoch The smallest starting epoch among the rewards to claim. /// @return maxEpoch The largest ending epoch among the rewards to claim. /// @return rewardsToClaim An array of Reward structs corresponding to each reward ID provided. function _getEpochRangeForRewards(uint256[] calldata rewardIds, address user) internal returns (uint48 minEpoch, uint48 maxEpoch, Reward[] memory rewardsToClaim) { // Cache array length, rewards, and highest claimable rewardID for gas op. uint256 rewardsLength = rewardIds.length; // Check that the user is claiming at least 1 reward. if (rewardsLength == 0) { revert MustClaimAtLeastOneReward(); } rewardsToClaim = new Reward[](rewardsLength); uint256 highestClaimaibleRewardId = maxRewardId - 1; // Initialize epoch range minEpoch = type(uint48).max; maxEpoch = 0; // Variables to cache reward claim data as a gas optimization. uint256 cachedRewardBucket; uint256 cachedClaimedRewards; // Variables used to preprocess rewardsIds to get a range of epochs for all rewards and mark them as claimed. for (uint256 i = 0; i < rewardsLength; ++i) { // Check if this rewardID exists if (rewardIds[i] > highestClaimaibleRewardId) { revert CannotClaimFutureReward(); } // Cache management (reading and writing) { // Determine the reward bucket that this rewardId belongs in. uint256 rewardBucket = rewardIds[i] / 256; if (i == 0) { // Initialize cache with the reward bucket and bit field cachedRewardBucket = rewardBucket; cachedClaimedRewards = userToRewardBucketToClaimedRewards[user][rewardBucket]; } else if (cachedRewardBucket != rewardBucket) { // Write back the cached claim data to persistent storage userToRewardBucketToClaimedRewards[user][cachedRewardBucket] = cachedClaimedRewards; // Updated cache with the new reward bucket and rewards bit field cachedRewardBucket = rewardBucket; cachedClaimedRewards = userToRewardBucketToClaimedRewards[user][rewardBucket]; } // The bit offset for rewardId within that bucket uint256 bitOffset = rewardIds[i] % 256; // Shift right so that the target bit is in the least significant position, // then check if it's 1 (indicating that it has been claimed) bool claimed = ((cachedClaimedRewards >> bitOffset) & 1) == 1; if (claimed) { // If the user has already claimed this reward, revert. revert RewardClaimedAlready(rewardIds[i]); } else { // If user hasn't claimed this reward // Set the bit corresponding to rewardId to true - indicating it has been claimed cachedClaimedRewards |= (1 << bitOffset); } } // Retrieve and cache the reward ID, start epoch, and end epoch. rewardsToClaim[i] = rewards[rewardIds[i]]; if (rewardsToClaim[i].startEpoch < minEpoch) { minEpoch = rewardsToClaim[i].startEpoch; } if (rewardsToClaim[i].endEpoch > maxEpoch) { maxEpoch = rewardsToClaim[i].endEpoch; } } // Write back the final cache to persistent storage userToRewardBucketToClaimedRewards[user][cachedRewardBucket] = cachedClaimedRewards; } /// @dev Computes the user's share ratios and epoch durations for every epoch between minEpoch and maxEpoch. /// @param minEpoch The starting epoch. /// @param maxEpoch The ending epoch. /// @param userBalanceUpdates The user's balance update history. /// @return userShareRatios An array of the user’s fraction of shares for each epoch. /// @return epochDurations An array of the epoch durations (endTimestamp - startTimestamp) for each epoch. function _computeUserShareRatiosAndDurations( uint48 minEpoch, uint48 maxEpoch, BalanceUpdate[] storage userBalanceUpdates ) internal view returns (uint256[] memory userShareRatios, uint256[] memory epochDurations) { uint256 epochCount = maxEpoch - minEpoch + 1; userShareRatios = new uint256[](epochCount); epochDurations = new uint256[](epochCount); uint256 userBalanceUpdatesLength = userBalanceUpdates.length; // Get the user's share balance at minEpoch. (uint256 balanceIndex, uint256 epochSharesBalance) = _findLatestBalanceUpdateForEpoch(minEpoch, userBalanceUpdates); // Cache the next balance update if it exists. bool hasNextUpdate = balanceIndex < userBalanceUpdatesLength - 1; BalanceUpdate memory nextUserBalanceUpdate; if (hasNextUpdate) { nextUserBalanceUpdate = userBalanceUpdates[balanceIndex + 1]; } // Loop over each epoch from minEpoch to maxEpoch. for (uint48 epoch = minEpoch; epoch <= maxEpoch; ++epoch) { // Update the user's share balance if a new balance update occurs at the current epoch. if (epoch == nextUserBalanceUpdate.epoch && hasNextUpdate) { epochSharesBalance = nextUserBalanceUpdate.totalSharesBalance; hasNextUpdate = ++balanceIndex < userBalanceUpdatesLength - 1; if (hasNextUpdate) { nextUserBalanceUpdate = userBalanceUpdates[balanceIndex + 1]; } } // Retrieve the epoch data. Epoch storage epochData = epochs[epoch]; uint128 eligibleShares = epochData.eligibleShares; // Only calculate ratio and duration if there are eligible shares. Else leave those set to 0. if (eligibleShares != 0) { uint256 epochIndex = epoch - minEpoch; // Calculate the user's fraction of shares for this epoch. userShareRatios[epochIndex] = epochSharesBalance.divWadDown(eligibleShares); // Calculate the epoch duration. epochDurations[epochIndex] = epochData.endTimestamp - epochData.startTimestamp; } } } /// @notice Computes the rewards per unique token for a set of reward campaigns. /// @param rewardIds An array of identifiers for the reward campaigns to process. /// @param rewardsToClaim An array of Reward structs containing the details for each reward campaign. /// @param minEpoch The starting epoch from which rewards are calculated, adjusted to be the /// later of the reward campaign's minimum epoch or the user's first deposit epoch. /// @param userShareRatios An array of precomputed share ratios for the user over a range of epochs, /// used to determine the user's portion of the rewards. /// @param epochDurations An array of epoch durations corresponding to the time intervals over which /// rewards are computed. /// @return uniqueTokens An array of unique token addresses for which rewards have been computed. /// @return tokenAmounts An array of reward amounts corresponding to each unique token in `uniqueTokens`. function _computeRewardsPerUniqueToken( uint256[] calldata rewardIds, Reward[] memory rewardsToClaim, uint48 minEpoch, uint256[] memory userShareRatios, uint256[] memory epochDurations ) internal returns (address[] memory uniqueTokens, uint256[] memory tokenAmounts) { uint256 numRewards = rewardIds.length; uniqueTokens = new address[](numRewards); tokenAmounts = new uint256[](numRewards); uint256 uniqueCount = 0; // For each reward campaign, calculate the rewards owed and accumulate by token. for (uint256 i = 0; i < numRewards; ++i) { uint256 rewardsOwed = _calculateRewardsOwed(rewardsToClaim[i], minEpoch, userShareRatios, epochDurations); if (rewardsOwed > 0) { // Check if this reward token was already encountered. bool found = false; for (uint256 j = 0; j < uniqueCount; ++j) { if (uniqueTokens[j] == rewardsToClaim[i].token) { tokenAmounts[j] += rewardsOwed; found = true; break; } } // If not found, add a new entry. if (!found) { uniqueTokens[uniqueCount] = rewardsToClaim[i].token; tokenAmounts[uniqueCount] = rewardsOwed; uniqueCount++; } // Emit the reward-claim event per reward campaign. emit UserRewardsClaimed(msg.sender, rewardsToClaim[i].token, rewardIds[i], rewardsOwed); } } // Resize the arrays to the actual number of unique tokens if needed if (uniqueCount < numRewards) { assembly ("memory-safe") { mstore(uniqueTokens, uniqueCount) mstore(tokenAmounts, uniqueCount) } } } /// @dev Calculates the total rewards owed for a single reward campaign over its epoch range. /// @param reward The reward campaign data. /// @param minEpoch The minimum epoch to consider (used as the offset for precomputed arrays). /// @param userShareRatios An array of the user's share ratios for each epoch. /// @param epochDurations An array of epoch durations for each epoch. /// @return rewardsOwed The total amount of tokens owed for this reward. function _calculateRewardsOwed( Reward memory reward, uint128 minEpoch, uint256[] memory userShareRatios, uint256[] memory epochDurations ) internal pure returns (uint256 rewardsOwed) { // Scale rate up by 1e18 to avoid precision loss uint256 rateWAD = reward.rewardRate * 1e18; for (uint256 epoch = reward.startEpoch; epoch <= reward.endEpoch; ++epoch) { if (epoch < minEpoch) { // If user didn't have a deposit in this epoch, skip reward calculation continue; } uint256 epochIndex = epoch - minEpoch; uint256 userShareRatioForEpoch = userShareRatios[epochIndex]; // Only process epochs where the user had a positive share ratio. if (userShareRatioForEpoch > 0) { uint256 epochReward = rateWAD.mulWadDown(epochDurations[epochIndex]); rewardsOwed += epochReward.mulWadDown(userShareRatioForEpoch); } } // Scale down by 1e18 to get the final reward amount rewardsOwed /= 1e18; } /// @notice Find the latest balance update index and balance for the specified epoch. /// @dev Assumes `balanceUpdates` is sorted in ascending order by `epoch`. /// @param epoch The epoch for which we want the user's balance. /// @param userBalanceUpdates The historical balance updates for a user, sorted ascending by epoch. /// @return The latest balance update index and balance for the given epoch. function _findLatestBalanceUpdateForEpoch(uint48 epoch, BalanceUpdate[] storage userBalanceUpdates) internal view returns (uint256, uint128) { // No balance changes at all if (userBalanceUpdates.length == 0) { return (0, 0); } // If the requested epoch is before the first recorded epoch, // assume the user had 0 shares. if (epoch < userBalanceUpdates[0].epoch) { return (epoch, 0); } // If the requested epoch is beyond the last recorded epoch, // return the most recent known balance. uint256 lastIndex = userBalanceUpdates.length - 1; if (epoch >= userBalanceUpdates[lastIndex].epoch) { return (lastIndex, userBalanceUpdates[lastIndex].totalSharesBalance); } // Standard binary search: // We want the highest index where balanceUpdates[index].epoch <= epoch uint256 low = 0; uint256 high = lastIndex; // Perform the binary search in the range [low, high] while (low < high) { // Midpoint (biased towards the higher index when (low+high) is even) uint256 mid = (low + high + 1) >> 1; // same as (low + high + 1) / 2 if (userBalanceUpdates[mid].epoch <= epoch) { // If mid's epoch is <= target, we move `low` up to mid low = mid; } else { // If mid's epoch is > target, we move `high` down to mid - 1 high = mid - 1; } } // Now `low == high`, which should be the index where epoch <= balanceUpdates[low].epoch // and balanceUpdates[low].epoch is the largest epoch not exceeding `epoch`. return (low, userBalanceUpdates[low].totalSharesBalance); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) pragma solidity ^0.8.20; /** * @dev Collection of common custom errors used in multiple contracts * * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. * It is recommended to avoid relying on the error API for critical functionality. * * _Available since v5.1._ */ library Errors { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error InsufficientBalance(uint256 balance, uint256 needed); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedCall(); /** * @dev The deployment failed. */ error FailedDeployment(); /** * @dev A necessary precompile is missing. */ error MissingPrecompile(address); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; /** * @title ERC-721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC-721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be * reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` */ abstract contract ERC165 is IERC165 { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol) pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Interface that must be implemented by smart contracts in order to receive * ERC-1155 token transfers. */ interface IERC1155Receiver is IERC165 { /** * @dev Handles the receipt of a single ERC-1155 token type. This function is * called at the end of a `safeTransferFrom` after the balance has been updated. * * NOTE: To accept the transfer, this must return * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` * (i.e. 0xf23a6e61, or its own function selector). * * @param operator The address which initiated the transfer (i.e. msg.sender) * @param from The address which previously owned the token * @param id The ID of the token being transferred * @param value The amount of tokens being transferred * @param data Additional data with no specified format * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed */ function onERC1155Received( address operator, address from, uint256 id, uint256 value, bytes calldata data ) external returns (bytes4); /** * @dev Handles the receipt of a multiple ERC-1155 token types. This function * is called at the end of a `safeBatchTransferFrom` after the balances have * been updated. * * NOTE: To accept the transfer(s), this must return * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` * (i.e. 0xbc197c81, or its own function selector). * * @param operator The address which initiated the batch transfer (i.e. msg.sender) * @param from The address which previously owned the token * @param ids An array containing ids of each token being transferred (order and length must match values array) * @param values An array containing amounts of each token being transferred (order and length must match ids array) * @param data Additional data with no specified format * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed */ function onERC1155BatchReceived( address operator, address from, uint256[] calldata ids, uint256[] calldata values, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {Owned} from "@solmate/auth/Owned.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; /// @title BoringSafe /// @notice Lightweight middleware contract for holding funds that have been committed to reward campaigns in BoringChef. contract BoringSafe is Owned(msg.sender) { using SafeTransferLib for ERC20; error ArrayLengthMismatch(); /// @notice Transfers tokens from this contract. /// @notice Only callable by the owner (BoringChef). /// @param tokens The addresses of the ERC20 tokens to transfer. /// @param amounts The amounts of each token to transfer. /// @param to The recipient address. function transfer(address[] memory tokens, uint256[] memory amounts, address to) external onlyOwner { // Make sure each token has a corresponding amount uint256 numTokens = tokens.length; if (numTokens != amounts.length) { revert ArrayLengthMismatch(); } // Transfer all tokens to the specified address for (uint256 i = 0; i < numTokens; ++i) { // Transfer ERC20 tokens ERC20(tokens[i]).safeTransfer(to, amounts[i]); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC-165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[ERC]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Simple single owner authorization mixin. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) abstract contract Owned { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event OwnershipTransferred(address indexed user, address indexed newOwner); /*////////////////////////////////////////////////////////////// OWNERSHIP STORAGE //////////////////////////////////////////////////////////////*/ address public owner; modifier onlyOwner() virtual { require(msg.sender == owner, "UNAUTHORIZED"); _; } /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(address _owner) { owner = _owner; emit OwnershipTransferred(address(0), _owner); } /*////////////////////////////////////////////////////////////// OWNERSHIP LOGIC //////////////////////////////////////////////////////////////*/ function transferOwnership(address newOwner) public virtual onlyOwner { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } }
{ "remappings": [ "@solmate/=lib/solmate/src/", "@forge-std/=lib/forge-std/src/", "@ds-test/=lib/forge-std/lib/ds-test/src/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@openzeppelin/=lib/openzeppelin-contracts/", "@ccip/=lib/ccip/", "@oapp-auth/=lib/OAppAuth/src/", "@devtools-oapp-evm/=lib/OAppAuth/lib/devtools/packages/oapp-evm/contracts/oapp/", "@layerzerolabs/lz-evm-messagelib-v2/=lib/OAppAuth/node_modules/@layerzerolabs/lz-evm-messagelib-v2/", "@layerzerolabs/lz-evm-protocol-v2/=lib/OAppAuth/lib/LayerZero-V2/packages/layerzero-v2/evm/protocol/", "@layerzerolabs/oapp-evm/=lib/OAppAuth/lib/devtools/packages/oapp-evm/", "@lz-oapp-evm/=lib/OAppAuth/lib/LayerZero-V2/packages/layerzero-v2/evm/oapp/contracts/oapp/", "@sbu/=lib/OAppAuth/lib/solidity-bytes-utils/", "LayerZero-V2/=lib/OAppAuth/lib/", "OAppAuth/=lib/OAppAuth/", "ccip/=lib/ccip/contracts/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "solidity-bytes-utils/=lib/OAppAuth/node_modules/solidity-bytes-utils/", "solmate/=lib/solmate/src/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "viaIR": false, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"payoutAddress","type":"address"},{"internalType":"uint96","name":"startingExchangeRate","type":"uint96"},{"internalType":"address","name":"_base","type":"address"},{"internalType":"uint16","name":"allowedExchangeRateChangeUpper","type":"uint16"},{"internalType":"uint16","name":"allowedExchangeRateChangeLower","type":"uint16"},{"internalType":"uint24","name":"minimumUpdateDelayInSeconds","type":"uint24"},{"internalType":"uint16","name":"platformFee","type":"uint16"},{"internalType":"uint16","name":"performanceFee","type":"uint16"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccountantWithRateProviders__ExchangeRateAboveHighwaterMark","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__LowerBoundTooLarge","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__OnlyCallableByBoringVault","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__Paused","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__PerformanceFeeTooLarge","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__PlatformFeeTooLarge","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__UpdateDelayTooLarge","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__UpperBoundTooSmall","type":"error"},{"inputs":[],"name":"AccountantWithRateProviders__ZeroFeesOwed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"AuthorityUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"oldDelay","type":"uint24"},{"indexed":false,"internalType":"uint24","name":"newDelay","type":"uint24"}],"name":"DelayInSecondsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"oldRate","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"newRate","type":"uint96"},{"indexed":false,"internalType":"uint64","name":"currentTime","type":"uint64"}],"name":"ExchangeRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"feeAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesClaimed","type":"event"},{"anonymous":false,"inputs":[],"name":"HighwaterMarkReset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"oldBound","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"newBound","type":"uint16"}],"name":"LowerBoundUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldPayout","type":"address"},{"indexed":false,"internalType":"address","name":"newPayout","type":"address"}],"name":"PayoutAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"oldFee","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"PerformanceFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"oldFee","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"newFee","type":"uint16"}],"name":"PlatformFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"bool","name":"isPegged","type":"bool"},{"indexed":false,"internalType":"address","name":"rateProvider","type":"address"}],"name":"RateProviderUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"oldBound","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"newBound","type":"uint16"}],"name":"UpperBoundUpdated","type":"event"},{"inputs":[],"name":"accountantState","outputs":[{"internalType":"address","name":"payoutAddress","type":"address"},{"internalType":"uint96","name":"highwaterMark","type":"uint96"},{"internalType":"uint128","name":"feesOwedInBase","type":"uint128"},{"internalType":"uint128","name":"totalSharesLastUpdate","type":"uint128"},{"internalType":"uint96","name":"exchangeRate","type":"uint96"},{"internalType":"uint16","name":"allowedExchangeRateChangeUpper","type":"uint16"},{"internalType":"uint16","name":"allowedExchangeRateChangeLower","type":"uint16"},{"internalType":"uint64","name":"lastUpdateTimestamp","type":"uint64"},{"internalType":"bool","name":"isPaused","type":"bool"},{"internalType":"uint24","name":"minimumUpdateDelayInSeconds","type":"uint24"},{"internalType":"uint16","name":"platformFee","type":"uint16"},{"internalType":"uint16","name":"performanceFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"authority","outputs":[{"internalType":"contract Authority","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"base","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"feeAsset","type":"address"}],"name":"claimFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRate","outputs":[{"internalType":"uint256","name":"rate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"quote","type":"address"}],"name":"getRateInQuote","outputs":[{"internalType":"uint256","name":"rateInQuote","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"quote","type":"address"}],"name":"getRateInQuoteSafe","outputs":[{"internalType":"uint256","name":"rateInQuote","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRateSafe","outputs":[{"internalType":"uint256","name":"rate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"newExchangeRate","type":"uint96"}],"name":"previewUpdateExchangeRate","outputs":[{"internalType":"bool","name":"updateWillPause","type":"bool"},{"internalType":"uint256","name":"newFeesOwedInBase","type":"uint256"},{"internalType":"uint256","name":"totalFeesOwedInBase","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"name":"rateProviderData","outputs":[{"internalType":"bool","name":"isPeggedToBase","type":"bool"},{"internalType":"contract IRateProvider","name":"rateProvider","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resetHighwaterMark","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"setAuthority","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"asset","type":"address"},{"internalType":"bool","name":"isPeggedToBase","type":"bool"},{"internalType":"address","name":"rateProvider","type":"address"}],"name":"setRateProviderData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"minimumUpdateDelayInSeconds","type":"uint24"}],"name":"updateDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"newExchangeRate","type":"uint96"}],"name":"updateExchangeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"allowedExchangeRateChangeLower","type":"uint16"}],"name":"updateLower","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"payoutAddress","type":"address"}],"name":"updatePayoutAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"performanceFee","type":"uint16"}],"name":"updatePerformanceFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"platformFee","type":"uint16"}],"name":"updatePlatformFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"allowedExchangeRateChangeUpper","type":"uint16"}],"name":"updateUpper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"contract BoringVault","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
61010060405234801562000011575f80fd5b50604051620025d0380380620025d083398101604081905262000034916200043f565b5f80546001600160a01b038c166001600160a01b031991821681178355600180549092169091556040518c92919033907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908490a36040516001600160a01b0382169033907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350506001600160a01b03861660808190526040805163313ce56760e01b8152905163313ce567916004808201926020929091908290030181865afa15801562000109573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906200012f91906200051d565b60ff1660a0526001600160a01b03891660c08190526040805163313ce56760e01b8152905163313ce567916004808201926020929091908290030181865afa1580156200017e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620001a491906200051d565b620001b190600a62000655565b60e08181525050604051806101800160405280896001600160a01b03168152602001886001600160601b031681526020015f6001600160801b0316815260200160c0516001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000230573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062000256919062000665565b6001600160801b0390811682526001600160601b03998a1660208084019190915261ffff9889166040808501919091529789166060808501919091526001600160401b034281166080808701919091525f60a08088019190915262ffffff9a8b1660c080890191909152998d1660e080890191909152988d16610100978801528751948801518f16600160a01b026001600160a01b039095169490941760025599860151918601518416600160801b9081029290941691909117600355978401516004805492860151978601519686015194860151610120870151610140880151610160909801518d16600160f01b026001600160f01b03988e16600160e01b02989098166001600160e01b0391909b16600160c81b0262ffffff60c81b19921515600160c01b029290921663ffffffff60c01b1997909c16909502600160801b600160c01b0319988d16600160701b0298909816600160701b600160c01b031999909c166c01000000000000000000000000026001600160701b031990941692909d169190911791909117959095169790971792909217919091169390931795909517929092169190911717909155506200067d92505050565b80516001600160a01b038116811462000428575f80fd5b919050565b805161ffff8116811462000428575f80fd5b5f805f805f805f805f806101408b8d0312156200045a575f80fd5b620004658b62000411565b99506200047560208c0162000411565b98506200048560408c0162000411565b60608c01519098506001600160601b0381168114620004a2575f80fd5b9650620004b260808c0162000411565b9550620004c260a08c016200042d565b9450620004d260c08c016200042d565b935060e08b015162ffffff81168114620004ea575f80fd5b9250620004fb6101008c016200042d565b91506200050c6101208c016200042d565b90509295989b9194979a5092959850565b5f602082840312156200052e575f80fd5b815160ff811681146200053f575f80fd5b9392505050565b634e487b7160e01b5f52601160045260245ffd5b600181815b808511156200059a57815f19048211156200057e576200057e62000546565b808516156200058c57918102915b93841c93908002906200055f565b509250929050565b5f82620005b2575060016200064f565b81620005c057505f6200064f565b8160018114620005d95760028114620005e45762000604565b60019150506200064f565b60ff841115620005f857620005f862000546565b50506001821b6200064f565b5060208310610133831016604e8410600b841016171562000629575081810a6200064f565b6200063583836200055a565b805f19048211156200064b576200064b62000546565b0290505b92915050565b5f6200053f60ff841683620005a2565b5f6020828403121562000676575f80fd5b5051919050565b60805160a05160c05160e051611ee0620006f05f395f8181611a3001528181611a600152611ada01525f818161051801528181610545015281816113aa015261178901525f8181610237015281816106d5015261090401525f81816103cb0152818161061e01526108120152611ee05ff3fe608060405234801561000f575f80fd5b5060043610610187575f3560e01c8063634da58f116100d95780638456cb5911610093578063bf7e214f1161006e578063bf7e214f146104e5578063e059ac07146104f8578063f2fde38b14610500578063fbfa77cf14610513575f80fd5b80638456cb59146104b85780638da5cb5b146104c0578063afb06952146104d2575f80fd5b8063634da58f14610448578063679aefce1461045b5780636a054dc91461046c578063709ac1c31461047f5780637a9e5e4b14610492578063820973da146104a5575f80fd5b80633458113d116101445780634d8be07e1161011f5780634d8be07e146103b35780635001f3b5146103c657806356200819146104055780636183fb9514610418575f80fd5b80633458113d1461026b5780633f4ba83a1461027e578063433255de14610286575f80fd5b806312e2d8f31461018b57806315a0ea6a146101e15780631dcbb110146101f6578063207ec0e714610217578063282a87001461022a578063313ce56714610232575b5f80fd5b6101bd610199366004611b3f565b60056020525f908152604090205460ff81169061010090046001600160a01b031682565b6040805192151583526001600160a01b039091166020830152015b60405180910390f35b6101f46101ef366004611b3f565b61053a565b005b610209610204366004611b3f565b61080f565b6040519081526020016101d8565b6101f4610225366004611b5a565b6109c8565b610209610a8f565b6102597f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016101d8565b6101f4610279366004611b7b565b610acc565b6101f4610c06565b600254600354600454610321926001600160a01b03811692600160a01b9091046001600160601b03908116926001600160801b0380841693600160801b9081900490911692821691600160601b810461ffff90811692600160701b830482169290810467ffffffffffffffff1691600160c01b820460ff1691600160c81b810462ffffff1691600160e01b8204811691600160f01b9004168c565b604080516001600160a01b03909d168d526001600160601b039b8c1660208e01526001600160801b039a8b16908d01529890971660608b015297909416608089015261ffff92831660a089015290821660c088015267ffffffffffffffff1660e087015290151561010086015262ffffff909316610120850152821661014084015216610160820152610180016101d8565b6101f46103c1366004611bae565b610c6e565b6103ed7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016101d8565b6101f4610413366004611b3f565b610d44565b61042b610426366004611b7b565b610dcf565b6040805193151584526020840192909252908201526060016101d8565b6101f4610456366004611b5a565b610ecd565b6004546001600160601b0316610209565b6101f461047a366004611bf6565b610f83565b6101f461048d366004611b5a565b61103d565b6101f46104a0366004611b3f565b6110f4565b6102096104b3366004611b3f565b6111d8565b6101f4611214565b5f546103ed906001600160a01b031681565b6101f46104e0366004611b5a565b611282565b6001546103ed906001600160a01b031681565b6101f4611338565b6101f461050e366004611b3f565b6114dd565b6103ed7f000000000000000000000000000000000000000000000000000000000000000081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461058357604051637e3db46f60e01b815260040160405180910390fd5b600454600290600160c01b900460ff16156105b157604051631d98997b60e11b815260040160405180910390fd5b60018101546001600160801b03165f036105de5760405163115b9d8b60e21b815260040160405180910390fd5b6001600160a01b038083165f81815260056020908152604080832081518083019092525460ff8116151582526101009004851691810191909152909290917f0000000000000000000000000000000000000000000000000000000000000000909116900361065b5760018301546001600160801b03169150610790565b5f846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610698573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106bc9190611c18565b60018501549091505f906106fa906001600160801b03167f000000000000000000000000000000000000000000000000000000000000000084611558565b83519091501561070c5780935061078d565b5f83602001516001600160a01b031663679aefce6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561074d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107719190611c38565b905061078961078184600a611d43565b8390836115c7565b9450505b50505b6001830180546fffffffffffffffffffffffffffffffff1916905582546107c6906001600160a01b0386811691339116856115e2565b836001600160a01b03167f9493e5bbe4e8e0ac67284469a2d677403d0378a85a59e341d3abc433d0d9a2098360405161080191815260200190565b60405180910390a250505050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361085a5750506004546001600160601b031690565b6001600160a01b038083165f81815260056020908152604080832081518083018352905460ff811615158252610100900490951685830152805163313ce56760e01b8152905192939263313ce567926004808401939192918290030181865afa1580156108c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108ed9190611c18565b6004549091505f90610929906001600160601b03167f000000000000000000000000000000000000000000000000000000000000000084611558565b83519091501561093b578093506109c0565b5f83602001516001600160a01b031663679aefce6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109a09190611c38565b90505f6109ae84600a611d43565b90506109bb8184846115c7565b955050505b505050919050565b6109dd335f356001600160e01b031916611686565b610a025760405162461bcd60e51b81526004016109f990611d51565b60405180910390fd5b6127108161ffff161115610a2957604051637375d3bf60e01b815260040160405180910390fd5b6004805461ffff838116600160701b81810261ffff60701b1985161790945560408051949093049091168084526020840191909152917f76fe3c3557dd03afa5caf76f66f4019444ef3999e784ba08f47a33428fcc64d591015b60405180910390a15050565b6004545f90600160c01b900460ff1615610abc57604051631d98997b60e11b815260040160405180910390fd5b506004546001600160601b031690565b610ae1335f356001600160e01b031916611686565b610afd5760405162461bcd60e51b81526004016109f990611d51565b5f805f805f610b0b8661172c565b945094509450945094508415610b355760028401805460ff60c01b1916600160c01b179055610b42565b610b4284878484876118b6565b610b6b868560020180546bffffffffffffffffffffffff19166001600160601b03831617905590565b6001850180546001600160801b03908116600160801b91851682021790915560028601805467ffffffffffffffff60801b191667ffffffffffffffff8716928302179055604080516001600160601b03808716825284166020820152908101919091529096507fa95bc6aba40bbc4d95fc35f118c4cd8b53fc5d5b89ed264002af03503a7a94399060600160405180910390a1505050505050565b610c1b335f356001600160e01b031916611686565b610c375760405162461bcd60e51b81526004016109f990611d51565b6004805460ff60c01b191690556040517fa45f47fdea8a1efdd9029a5691c7f759c32b7c698632b563573e155625d16933905f90a1565b610c83335f356001600160e01b031916611686565b610c9f5760405162461bcd60e51b81526004016109f990611d51565b6040805180820182528315158082526001600160a01b0384811660208085018281528984165f818152600584528890209651875492516001600160a81b0319909316901515610100600160a81b03191617610100929095169190910293909317909455845191825292810191909152918201527f59f9adfe8cf4c9d4b77fb03aa2ae5f373632c97cb8caf6b61f0643d3d170a8fe9060600160405180910390a1505050565b610d59335f356001600160e01b031916611686565b610d755760405162461bcd60e51b81526004016109f990611d51565b600280546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527fba2be5e898fed1646bc0814dee1cc9a2aee98f51fced7d5fc4699c47d99077539101610a83565b5f805f805f805f80610de08961172c565b6001840154949c506001600160801b0390941699508b985091965094509250905084610ec157600184015460028501545f918291610e4d91600160801b908190046001600160801b03169190810467ffffffffffffffff1690600160e01b900461ffff168e88888b6119cf565b875491935091505f906001600160601b03600160a01b9091048116908d161115610ea55786546002880154610ea1918e918591600160a01b90046001600160601b031690600160f01b900461ffff16611abb565b5090505b610eaf8184611d77565b9950610ebb8a8a611d77565b98505050505b50505050509193909250565b610ee2335f356001600160e01b031916611686565b610efe5760405162461bcd60e51b81526004016109f990611d51565b6127108161ffff161015610f255760405163a4ec27a960e01b815260040160405180910390fd5b6004805461ffff838116600160601b81810261ffff60601b1985161790945560408051949093049091168084526020840191909152917f67d3a3f6bebb5b894324217d5224ff719d5d95dfc67f1bb2645dddbfcd43cadb9101610a83565b610f98335f356001600160e01b031916611686565b610fb45760405162461bcd60e51b81526004016109f990611d51565b621275008162ffffff161115610fdd57604051635badbfbb60e01b815260040160405180910390fd5b6004805462ffffff838116600160c81b81810262ffffff60c81b1985161790945560408051949093049091168084526020840191909152917f5f7db254db512f40348d8a7ca15d574c051dfe59c19b47e273d926f2f43186069101610a83565b611052335f356001600160e01b031916611686565b61106e5760405162461bcd60e51b81526004016109f990611d51565b6113888161ffff1611156110955760405163fdaeddbb60e01b815260040160405180910390fd5b6004805461ffff838116600160f01b8181026001600160f01b0385161790945560408051949093049091168084526020840191909152917fba8506b6cb85330fea21cbca8490aafb6a69b166f06201ef755eb511b2709fc19101610a83565b5f546001600160a01b0316331480611185575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061114690339030906001600160e01b03195f351690600401611d8a565b602060405180830381865afa158015611161573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111859190611db7565b61118d575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b6004545f90600160c01b900460ff161561120557604051631d98997b60e11b815260040160405180910390fd5b61120e8261080f565b92915050565b611229335f356001600160e01b031916611686565b6112455760405162461bcd60e51b81526004016109f990611d51565b6004805460ff60c01b1916600160c01b1790556040517f9e87fac88ff661f02d44f95383c817fece4bce600a3dab7a54406878b965e752905f90a1565b611297335f356001600160e01b031916611686565b6112b35760405162461bcd60e51b81526004016109f990611d51565b6107d08161ffff1611156112da5760405163173aacc160e31b815260040160405180910390fd5b6004805461ffff838116600160e01b81810261ffff60e01b1985161790945560408051949093049091168084526020840191909152917f84e4fe32bf74c4011a7e1fde79c63acdffaf92a0112cde153e7b0abee665bc6b9101610a83565b61134d335f356001600160e01b031916611686565b6113695760405162461bcd60e51b81526004016109f990611d51565b600280546004546001600160601b03600160a01b9092048216911611156113a357604051638058acff60e01b815260040160405180910390fd5b5f4290505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611404573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114289190611c38565b60028401549091506114479084906001600160601b03168084866118b6565b6001830180546001600160801b03908116600160801b91841682021790915560045484546001600160a01b03166001600160601b03909116600160a01b0217845560028401805467ffffffffffffffff60801b191667ffffffffffffffff85169092029190911790556040517f98637d475d52bc596e25457cb3385a05269c42e57d4d9f7561dacbbe8583eb89905f90a1505050565b6114f2335f356001600160e01b031916611686565b61150e5760405162461bcd60e51b81526004016109f990611d51565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b5f8160ff168360ff160361156d5750826115c0565b8160ff168360ff1610156115a1576115858383611dd2565b61159090600a611d43565b61159a9085611deb565b90506115c0565b6115ab8284611dd2565b6115b690600a611d43565b61159a9085611e02565b9392505050565b5f825f1904841183021582026115db575f80fd5b5091020490565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af191505080601f3d1160015f51141615161561163b5750833b153d17155b8061167f5760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b60448201526064016109f9565b5050505050565b6001545f906001600160a01b0316801580159061170d575060405163b700961360e01b81526001600160a01b0382169063b7009613906116ce90879030908890600401611d8a565b602060405180830381865afa1580156116e9573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170d9190611db7565b8061172457505f546001600160a01b038581169116145b949350505050565b6004545f90600290829081908190600160c01b900460ff161561176257604051631d98997b60e11b815260040160405180910390fd5b429250836002015f9054906101000a90046001600160601b03166001600160601b031691507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118079190611c38565b600285015490915061183690600160c81b810462ffffff1690600160801b900467ffffffffffffffff16611e21565b67ffffffffffffffff168367ffffffffffffffff16108061187c57506002840154611870908390600160601b900461ffff166127106115c7565b866001600160601b0316115b806118ac575060028401546118a0908390600160701b900461ffff166127106115c7565b866001600160601b0316105b9693955091935091565b600185015460028601545f9182916118fd91600160801b908190046001600160801b03169190810467ffffffffffffffff1690600160e01b900461ffff16898989896119cf565b885491935091506001600160601b03600160a01b90910481169087161115611983575f61195487838a5f0160149054906101000a90046001600160601b03168b600201601e9054906101000a900461ffff16611abb565b5090506119618184611d77565b88546001600160a01b0316600160a01b6001600160601b038a16021789559250505b6001870180548391905f906119a29084906001600160801b0316611e49565b92506101000a8154816001600160801b0302191690836001600160801b0316021790555050505050505050565b5f826001600160801b0389168111156119ee57506001600160801b0388165b61ffff871615611aaf575f611a038985611e69565b67ffffffffffffffff1690505f86886001600160601b031611611a5957611a54836001600160601b038a167f00000000000000000000000000000000000000000000000000000000000000006115c7565b611a84565b611a8483887f00000000000000000000000000000000000000000000000000000000000000006115c7565b90505f611a988261ffff8c166127106115c7565b9050611aa981846301e133806115c7565b94505050505b97509795505050505050565b5f8080611ac88588611e8a565b6001600160601b03169050611afe81877f00000000000000000000000000000000000000000000000000000000000000006115c7565b915061ffff841615611b1e57611b1b8261ffff86166127106115c7565b92505b5094509492505050565b6001600160a01b0381168114611b3c575f80fd5b50565b5f60208284031215611b4f575f80fd5b81356115c081611b28565b5f60208284031215611b6a575f80fd5b813561ffff811681146115c0575f80fd5b5f60208284031215611b8b575f80fd5b81356001600160601b03811681146115c0575f80fd5b8015158114611b3c575f80fd5b5f805f60608486031215611bc0575f80fd5b8335611bcb81611b28565b92506020840135611bdb81611ba1565b91506040840135611beb81611b28565b809150509250925092565b5f60208284031215611c06575f80fd5b813562ffffff811681146115c0575f80fd5b5f60208284031215611c28575f80fd5b815160ff811681146115c0575f80fd5b5f60208284031215611c48575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b600181815b80851115611c9d57815f1904821115611c8357611c83611c4f565b80851615611c9057918102915b93841c9390800290611c68565b509250929050565b5f82611cb35750600161120e565b81611cbf57505f61120e565b8160018114611cd55760028114611cdf57611cfb565b600191505061120e565b60ff841115611cf057611cf0611c4f565b50506001821b61120e565b5060208310610133831016604e8410600b8410161715611d1e575081810a61120e565b611d288383611c63565b805f1904821115611d3b57611d3b611c4f565b029392505050565b5f6115c060ff841683611ca5565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b8082018082111561120e5761120e611c4f565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b5f60208284031215611dc7575f80fd5b81516115c081611ba1565b60ff828116828216039081111561120e5761120e611c4f565b808202811582820484141761120e5761120e611c4f565b5f82611e1c57634e487b7160e01b5f52601260045260245ffd5b500490565b67ffffffffffffffff818116838216019080821115611e4257611e42611c4f565b5092915050565b6001600160801b03818116838216019080821115611e4257611e42611c4f565b67ffffffffffffffff828116828216039080821115611e4257611e42611c4f565b6001600160601b03828116828216039080821115611e4257611e42611c4f56fea26469706673582212209f13400da67c1daa5641e698a5ff4a113bce496c17a9d94343fe4078dde92ee864736f6c634300081500330000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000f424000000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894000000000000000000000000000000000000000000000000000000000000277400000000000000000000000000000000000000000000000000000000000026ac0000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e8
Deployed Bytecode
0x608060405234801561000f575f80fd5b5060043610610187575f3560e01c8063634da58f116100d95780638456cb5911610093578063bf7e214f1161006e578063bf7e214f146104e5578063e059ac07146104f8578063f2fde38b14610500578063fbfa77cf14610513575f80fd5b80638456cb59146104b85780638da5cb5b146104c0578063afb06952146104d2575f80fd5b8063634da58f14610448578063679aefce1461045b5780636a054dc91461046c578063709ac1c31461047f5780637a9e5e4b14610492578063820973da146104a5575f80fd5b80633458113d116101445780634d8be07e1161011f5780634d8be07e146103b35780635001f3b5146103c657806356200819146104055780636183fb9514610418575f80fd5b80633458113d1461026b5780633f4ba83a1461027e578063433255de14610286575f80fd5b806312e2d8f31461018b57806315a0ea6a146101e15780631dcbb110146101f6578063207ec0e714610217578063282a87001461022a578063313ce56714610232575b5f80fd5b6101bd610199366004611b3f565b60056020525f908152604090205460ff81169061010090046001600160a01b031682565b6040805192151583526001600160a01b039091166020830152015b60405180910390f35b6101f46101ef366004611b3f565b61053a565b005b610209610204366004611b3f565b61080f565b6040519081526020016101d8565b6101f4610225366004611b5a565b6109c8565b610209610a8f565b6102597f000000000000000000000000000000000000000000000000000000000000000681565b60405160ff90911681526020016101d8565b6101f4610279366004611b7b565b610acc565b6101f4610c06565b600254600354600454610321926001600160a01b03811692600160a01b9091046001600160601b03908116926001600160801b0380841693600160801b9081900490911692821691600160601b810461ffff90811692600160701b830482169290810467ffffffffffffffff1691600160c01b820460ff1691600160c81b810462ffffff1691600160e01b8204811691600160f01b9004168c565b604080516001600160a01b03909d168d526001600160601b039b8c1660208e01526001600160801b039a8b16908d01529890971660608b015297909416608089015261ffff92831660a089015290821660c088015267ffffffffffffffff1660e087015290151561010086015262ffffff909316610120850152821661014084015216610160820152610180016101d8565b6101f46103c1366004611bae565b610c6e565b6103ed7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d403889481565b6040516001600160a01b0390911681526020016101d8565b6101f4610413366004611b3f565b610d44565b61042b610426366004611b7b565b610dcf565b6040805193151584526020840192909252908201526060016101d8565b6101f4610456366004611b5a565b610ecd565b6004546001600160601b0316610209565b6101f461047a366004611bf6565b610f83565b6101f461048d366004611b5a565b61103d565b6101f46104a0366004611b3f565b6110f4565b6102096104b3366004611b3f565b6111d8565b6101f4611214565b5f546103ed906001600160a01b031681565b6101f46104e0366004611b5a565b611282565b6001546103ed906001600160a01b031681565b6101f4611338565b6101f461050e366004611b3f565b6114dd565b6103ed7f00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc81565b336001600160a01b037f00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc161461058357604051637e3db46f60e01b815260040160405180910390fd5b600454600290600160c01b900460ff16156105b157604051631d98997b60e11b815260040160405180910390fd5b60018101546001600160801b03165f036105de5760405163115b9d8b60e21b815260040160405180910390fd5b6001600160a01b038083165f81815260056020908152604080832081518083019092525460ff8116151582526101009004851691810191909152909290917f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894909116900361065b5760018301546001600160801b03169150610790565b5f846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610698573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106bc9190611c18565b60018501549091505f906106fa906001600160801b03167f000000000000000000000000000000000000000000000000000000000000000684611558565b83519091501561070c5780935061078d565b5f83602001516001600160a01b031663679aefce6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561074d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107719190611c38565b905061078961078184600a611d43565b8390836115c7565b9450505b50505b6001830180546fffffffffffffffffffffffffffffffff1916905582546107c6906001600160a01b0386811691339116856115e2565b836001600160a01b03167f9493e5bbe4e8e0ac67284469a2d677403d0378a85a59e341d3abc433d0d9a2098360405161080191815260200190565b60405180910390a250505050565b5f7f00000000000000000000000029219dd400f2bf60e5a23d13be72b486d40388946001600160a01b0316826001600160a01b03160361085a5750506004546001600160601b031690565b6001600160a01b038083165f81815260056020908152604080832081518083018352905460ff811615158252610100900490951685830152805163313ce56760e01b8152905192939263313ce567926004808401939192918290030181865afa1580156108c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108ed9190611c18565b6004549091505f90610929906001600160601b03167f000000000000000000000000000000000000000000000000000000000000000684611558565b83519091501561093b578093506109c0565b5f83602001516001600160a01b031663679aefce6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109a09190611c38565b90505f6109ae84600a611d43565b90506109bb8184846115c7565b955050505b505050919050565b6109dd335f356001600160e01b031916611686565b610a025760405162461bcd60e51b81526004016109f990611d51565b60405180910390fd5b6127108161ffff161115610a2957604051637375d3bf60e01b815260040160405180910390fd5b6004805461ffff838116600160701b81810261ffff60701b1985161790945560408051949093049091168084526020840191909152917f76fe3c3557dd03afa5caf76f66f4019444ef3999e784ba08f47a33428fcc64d591015b60405180910390a15050565b6004545f90600160c01b900460ff1615610abc57604051631d98997b60e11b815260040160405180910390fd5b506004546001600160601b031690565b610ae1335f356001600160e01b031916611686565b610afd5760405162461bcd60e51b81526004016109f990611d51565b5f805f805f610b0b8661172c565b945094509450945094508415610b355760028401805460ff60c01b1916600160c01b179055610b42565b610b4284878484876118b6565b610b6b868560020180546bffffffffffffffffffffffff19166001600160601b03831617905590565b6001850180546001600160801b03908116600160801b91851682021790915560028601805467ffffffffffffffff60801b191667ffffffffffffffff8716928302179055604080516001600160601b03808716825284166020820152908101919091529096507fa95bc6aba40bbc4d95fc35f118c4cd8b53fc5d5b89ed264002af03503a7a94399060600160405180910390a1505050505050565b610c1b335f356001600160e01b031916611686565b610c375760405162461bcd60e51b81526004016109f990611d51565b6004805460ff60c01b191690556040517fa45f47fdea8a1efdd9029a5691c7f759c32b7c698632b563573e155625d16933905f90a1565b610c83335f356001600160e01b031916611686565b610c9f5760405162461bcd60e51b81526004016109f990611d51565b6040805180820182528315158082526001600160a01b0384811660208085018281528984165f818152600584528890209651875492516001600160a81b0319909316901515610100600160a81b03191617610100929095169190910293909317909455845191825292810191909152918201527f59f9adfe8cf4c9d4b77fb03aa2ae5f373632c97cb8caf6b61f0643d3d170a8fe9060600160405180910390a1505050565b610d59335f356001600160e01b031916611686565b610d755760405162461bcd60e51b81526004016109f990611d51565b600280546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527fba2be5e898fed1646bc0814dee1cc9a2aee98f51fced7d5fc4699c47d99077539101610a83565b5f805f805f805f80610de08961172c565b6001840154949c506001600160801b0390941699508b985091965094509250905084610ec157600184015460028501545f918291610e4d91600160801b908190046001600160801b03169190810467ffffffffffffffff1690600160e01b900461ffff168e88888b6119cf565b875491935091505f906001600160601b03600160a01b9091048116908d161115610ea55786546002880154610ea1918e918591600160a01b90046001600160601b031690600160f01b900461ffff16611abb565b5090505b610eaf8184611d77565b9950610ebb8a8a611d77565b98505050505b50505050509193909250565b610ee2335f356001600160e01b031916611686565b610efe5760405162461bcd60e51b81526004016109f990611d51565b6127108161ffff161015610f255760405163a4ec27a960e01b815260040160405180910390fd5b6004805461ffff838116600160601b81810261ffff60601b1985161790945560408051949093049091168084526020840191909152917f67d3a3f6bebb5b894324217d5224ff719d5d95dfc67f1bb2645dddbfcd43cadb9101610a83565b610f98335f356001600160e01b031916611686565b610fb45760405162461bcd60e51b81526004016109f990611d51565b621275008162ffffff161115610fdd57604051635badbfbb60e01b815260040160405180910390fd5b6004805462ffffff838116600160c81b81810262ffffff60c81b1985161790945560408051949093049091168084526020840191909152917f5f7db254db512f40348d8a7ca15d574c051dfe59c19b47e273d926f2f43186069101610a83565b611052335f356001600160e01b031916611686565b61106e5760405162461bcd60e51b81526004016109f990611d51565b6113888161ffff1611156110955760405163fdaeddbb60e01b815260040160405180910390fd5b6004805461ffff838116600160f01b8181026001600160f01b0385161790945560408051949093049091168084526020840191909152917fba8506b6cb85330fea21cbca8490aafb6a69b166f06201ef755eb511b2709fc19101610a83565b5f546001600160a01b0316331480611185575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061114690339030906001600160e01b03195f351690600401611d8a565b602060405180830381865afa158015611161573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111859190611db7565b61118d575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b6004545f90600160c01b900460ff161561120557604051631d98997b60e11b815260040160405180910390fd5b61120e8261080f565b92915050565b611229335f356001600160e01b031916611686565b6112455760405162461bcd60e51b81526004016109f990611d51565b6004805460ff60c01b1916600160c01b1790556040517f9e87fac88ff661f02d44f95383c817fece4bce600a3dab7a54406878b965e752905f90a1565b611297335f356001600160e01b031916611686565b6112b35760405162461bcd60e51b81526004016109f990611d51565b6107d08161ffff1611156112da5760405163173aacc160e31b815260040160405180910390fd5b6004805461ffff838116600160e01b81810261ffff60e01b1985161790945560408051949093049091168084526020840191909152917f84e4fe32bf74c4011a7e1fde79c63acdffaf92a0112cde153e7b0abee665bc6b9101610a83565b61134d335f356001600160e01b031916611686565b6113695760405162461bcd60e51b81526004016109f990611d51565b600280546004546001600160601b03600160a01b9092048216911611156113a357604051638058acff60e01b815260040160405180910390fd5b5f4290505f7f00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611404573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114289190611c38565b60028401549091506114479084906001600160601b03168084866118b6565b6001830180546001600160801b03908116600160801b91841682021790915560045484546001600160a01b03166001600160601b03909116600160a01b0217845560028401805467ffffffffffffffff60801b191667ffffffffffffffff85169092029190911790556040517f98637d475d52bc596e25457cb3385a05269c42e57d4d9f7561dacbbe8583eb89905f90a1505050565b6114f2335f356001600160e01b031916611686565b61150e5760405162461bcd60e51b81526004016109f990611d51565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b5f8160ff168360ff160361156d5750826115c0565b8160ff168360ff1610156115a1576115858383611dd2565b61159090600a611d43565b61159a9085611deb565b90506115c0565b6115ab8284611dd2565b6115b690600a611d43565b61159a9085611e02565b9392505050565b5f825f1904841183021582026115db575f80fd5b5091020490565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af191505080601f3d1160015f51141615161561163b5750833b153d17155b8061167f5760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b60448201526064016109f9565b5050505050565b6001545f906001600160a01b0316801580159061170d575060405163b700961360e01b81526001600160a01b0382169063b7009613906116ce90879030908890600401611d8a565b602060405180830381865afa1580156116e9573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170d9190611db7565b8061172457505f546001600160a01b038581169116145b949350505050565b6004545f90600290829081908190600160c01b900460ff161561176257604051631d98997b60e11b815260040160405180910390fd5b429250836002015f9054906101000a90046001600160601b03166001600160601b031691507f00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118079190611c38565b600285015490915061183690600160c81b810462ffffff1690600160801b900467ffffffffffffffff16611e21565b67ffffffffffffffff168367ffffffffffffffff16108061187c57506002840154611870908390600160601b900461ffff166127106115c7565b866001600160601b0316115b806118ac575060028401546118a0908390600160701b900461ffff166127106115c7565b866001600160601b0316105b9693955091935091565b600185015460028601545f9182916118fd91600160801b908190046001600160801b03169190810467ffffffffffffffff1690600160e01b900461ffff16898989896119cf565b885491935091506001600160601b03600160a01b90910481169087161115611983575f61195487838a5f0160149054906101000a90046001600160601b03168b600201601e9054906101000a900461ffff16611abb565b5090506119618184611d77565b88546001600160a01b0316600160a01b6001600160601b038a16021789559250505b6001870180548391905f906119a29084906001600160801b0316611e49565b92506101000a8154816001600160801b0302191690836001600160801b0316021790555050505050505050565b5f826001600160801b0389168111156119ee57506001600160801b0388165b61ffff871615611aaf575f611a038985611e69565b67ffffffffffffffff1690505f86886001600160601b031611611a5957611a54836001600160601b038a167f00000000000000000000000000000000000000000000000000000000000f42406115c7565b611a84565b611a8483887f00000000000000000000000000000000000000000000000000000000000f42406115c7565b90505f611a988261ffff8c166127106115c7565b9050611aa981846301e133806115c7565b94505050505b97509795505050505050565b5f8080611ac88588611e8a565b6001600160601b03169050611afe81877f00000000000000000000000000000000000000000000000000000000000f42406115c7565b915061ffff841615611b1e57611b1b8261ffff86166127106115c7565b92505b5094509492505050565b6001600160a01b0381168114611b3c575f80fd5b50565b5f60208284031215611b4f575f80fd5b81356115c081611b28565b5f60208284031215611b6a575f80fd5b813561ffff811681146115c0575f80fd5b5f60208284031215611b8b575f80fd5b81356001600160601b03811681146115c0575f80fd5b8015158114611b3c575f80fd5b5f805f60608486031215611bc0575f80fd5b8335611bcb81611b28565b92506020840135611bdb81611ba1565b91506040840135611beb81611b28565b809150509250925092565b5f60208284031215611c06575f80fd5b813562ffffff811681146115c0575f80fd5b5f60208284031215611c28575f80fd5b815160ff811681146115c0575f80fd5b5f60208284031215611c48575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b600181815b80851115611c9d57815f1904821115611c8357611c83611c4f565b80851615611c9057918102915b93841c9390800290611c68565b509250929050565b5f82611cb35750600161120e565b81611cbf57505f61120e565b8160018114611cd55760028114611cdf57611cfb565b600191505061120e565b60ff841115611cf057611cf0611c4f565b50506001821b61120e565b5060208310610133831016604e8410600b8410161715611d1e575081810a61120e565b611d288383611c63565b805f1904821115611d3b57611d3b611c4f565b029392505050565b5f6115c060ff841683611ca5565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b8082018082111561120e5761120e611c4f565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b5f60208284031215611dc7575f80fd5b81516115c081611ba1565b60ff828116828216039081111561120e5761120e611c4f565b808202811582820484141761120e5761120e611c4f565b5f82611e1c57634e487b7160e01b5f52601260045260245ffd5b500490565b67ffffffffffffffff818116838216019080821115611e4257611e42611c4f565b5092915050565b6001600160801b03818116838216019080821115611e4257611e42611c4f565b67ffffffffffffffff828116828216039080821115611e4257611e42611c4f565b6001600160601b03828116828216039080821115611e4257611e42611c4f56fea26469706673582212209f13400da67c1daa5641e698a5ff4a113bce496c17a9d94343fe4078dde92ee864736f6c63430008150033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000f424000000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894000000000000000000000000000000000000000000000000000000000000277400000000000000000000000000000000000000000000000000000000000026ac0000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e8
-----Decoded View---------------
Arg [0] : _owner (address): 0x5F2F11ad8656439d5C14d9B351f8b09cDaC2A02d
Arg [1] : _vault (address): 0x74D1fAfa4e0163b2f1035F1b052137F3f9baD5cC
Arg [2] : payoutAddress (address): 0x0000000000000000000000000000000000000001
Arg [3] : startingExchangeRate (uint96): 1000000
Arg [4] : _base (address): 0x29219dd400f2Bf60E5a23d13Be72B486D4038894
Arg [5] : allowedExchangeRateChangeUpper (uint16): 10100
Arg [6] : allowedExchangeRateChangeLower (uint16): 9900
Arg [7] : minimumUpdateDelayInSeconds (uint24): 21600
Arg [8] : platformFee (uint16): 0
Arg [9] : performanceFee (uint16): 1000
-----Encoded View---------------
10 Constructor Arguments found :
Arg [0] : 0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d
Arg [1] : 00000000000000000000000074d1fafa4e0163b2f1035f1b052137f3f9bad5cc
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [3] : 00000000000000000000000000000000000000000000000000000000000f4240
Arg [4] : 00000000000000000000000029219dd400f2bf60e5a23d13be72b486d4038894
Arg [5] : 0000000000000000000000000000000000000000000000000000000000002774
Arg [6] : 00000000000000000000000000000000000000000000000000000000000026ac
Arg [7] : 0000000000000000000000000000000000000000000000000000000000005460
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [9] : 00000000000000000000000000000000000000000000000000000000000003e8
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.