Source Code
Latest 25 from a total of 288 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Withdraw | 40140941 | 26 days ago | IN | 0 ETH | 0.00000072 | ||||
| Collect And Conv... | 40140931 | 26 days ago | IN | 0 ETH | 0.00000079 | ||||
| Withdraw | 36809026 | 104 days ago | IN | 0 ETH | 0.00000263 | ||||
| Withdraw | 36808987 | 104 days ago | IN | 0 ETH | 0.00000313 | ||||
| Deposit ETH | 36808948 | 104 days ago | IN | 0.00003937 ETH | 0.0000015 | ||||
| Collect And Conv... | 36569231 | 109 days ago | IN | 0 ETH | 0.00000347 | ||||
| Withdraw | 36552130 | 110 days ago | IN | 0 ETH | 0.00000204 | ||||
| Withdraw | 36552124 | 110 days ago | IN | 0 ETH | 0.0000027 | ||||
| Deposit ETH | 36551995 | 110 days ago | IN | 0.00029803 ETH | 0.00000127 | ||||
| Collect And Conv... | 36430332 | 112 days ago | IN | 0 ETH | 0.00000114 | ||||
| Withdraw | 36428589 | 112 days ago | IN | 0 ETH | 0.00000097 | ||||
| Withdraw | 36428580 | 112 days ago | IN | 0 ETH | 0.00000129 | ||||
| Deposit ETH | 36428573 | 112 days ago | IN | 0.00010061 ETH | 0.00000066 | ||||
| Collect And Conv... | 36421028 | 113 days ago | IN | 0 ETH | 0.00000081 | ||||
| Withdraw | 36416990 | 113 days ago | IN | 0 ETH | 0.00000161 | ||||
| Collect And Conv... | 36394420 | 113 days ago | IN | 0 ETH | 0.00000147 | ||||
| Collect And Conv... | 36392962 | 113 days ago | IN | 0 ETH | 0.00000121 | ||||
| Collect And Conv... | 36291193 | 116 days ago | IN | 0 ETH | 0.00000192 | ||||
| Withdraw | 36260230 | 116 days ago | IN | 0 ETH | 0.00000437 | ||||
| Collect And Conv... | 36260196 | 116 days ago | IN | 0 ETH | 0.00000501 | ||||
| Collect And Conv... | 36144755 | 119 days ago | IN | 0 ETH | 0.00000111 | ||||
| Withdraw | 36142423 | 119 days ago | IN | 0 ETH | 0.00000085 | ||||
| Withdraw | 36142130 | 119 days ago | IN | 0 ETH | 0.00000072 | ||||
| Update Global Fe... | 36141966 | 119 days ago | IN | 0 ETH | 0.00000016 | ||||
| Withdraw | 36141946 | 119 days ago | IN | 0 ETH | 0.00000095 |
Latest 25 internal transactions (View All)
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 40140941 | 26 days ago | 0.00970794 ETH | ||||
| 40140941 | 26 days ago | 0.00970794 ETH | ||||
| 36809026 | 104 days ago | 0.1483767 ETH | ||||
| 36809026 | 104 days ago | 0.1483767 ETH | ||||
| 36808987 | 104 days ago | 0.00003936 ETH | ||||
| 36808987 | 104 days ago | 0.00003936 ETH | ||||
| 36808948 | 104 days ago | 0 ETH | ||||
| 36808948 | 104 days ago | 0 ETH | ||||
| 36808948 | 104 days ago | 0.00003937 ETH | ||||
| 36552130 | 110 days ago | 0.0001443 ETH | ||||
| 36552130 | 110 days ago | 0.0001443 ETH | ||||
| 36552124 | 110 days ago | 0.00029803 ETH | ||||
| 36552124 | 110 days ago | 0.00029803 ETH | ||||
| 36551995 | 110 days ago | 0.00029803 ETH | ||||
| 36428589 | 112 days ago | 0.00128795 ETH | ||||
| 36428589 | 112 days ago | 0.00128795 ETH | ||||
| 36428580 | 112 days ago | 0.00010061 ETH | ||||
| 36428580 | 112 days ago | 0.00010061 ETH | ||||
| 36428573 | 112 days ago | 0 ETH | ||||
| 36428573 | 112 days ago | 0 ETH | ||||
| 36428573 | 112 days ago | 0.00010061 ETH | ||||
| 36416990 | 113 days ago | 0.0087705 ETH | ||||
| 36416990 | 113 days ago | 0.0087705 ETH | ||||
| 36260230 | 116 days ago | 0.01025863 ETH | ||||
| 36260230 | 116 days ago | 0.01025863 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
DackieVault
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "./interfaces/IDackieVault.sol";
import "./interfaces/IUniswapV3.sol";
import "./interfaces/IWETH.sol";
/**
* @title DackieVault
* @notice A vault for managing full-range V3 liquidity positions on WETH/DACKIE pair
* with automatic fee collection and conversion to cbBTC
*/
contract DackieVault is IDackieVault, Ownable, ReentrancyGuard, Pausable, IERC721Receiver {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice WETH token address on Base
address public constant WETH = 0x4200000000000000000000000000000000000006;
/// @notice DACKIE token address on Base
address public constant DACKIE = 0x73326b4d0225c429bed050c11C4422d91470AaF4;
/// @notice cbBTC token address on Base
address public constant cbBTC = 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf;
/// @notice V3 fee tier (1%)
uint24 public constant FEE_TIER = 10000;
/// @notice Precision for fee accumulator calculations
uint256 public constant PRECISION = 1e18;
/// @notice Maximum treasury fee in basis points (50%)
uint16 public constant MAX_TREASURY_FEE_BPS = 5000;
/// @notice Maximum lock duration (~2 years)
uint32 public constant MAX_LOCK_DURATION = 730 days;
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice V3 Position Manager
INonfungiblePositionManager public immutable positionManager;
/// @notice V3 Swap Router
ISwapRouter public swapRouter;
/// @notice V3 Factory
IUniswapV3Factory public immutable factory;
/// @notice Token ID of the shared V3 position (0 if no position exists)
uint256 public tokenId;
/// @notice Total liquidity in the shared position
uint128 public totalLiquidity;
/// @notice User position information including normal and locked liquidity
struct UserPosition {
uint128 normalLiquidity; // withdrawable anytime
uint128 lockedLiquidity; // withdrawable only after lockedUntil
uint64 lockedUntil; // unix timestamp when locked liquidity becomes withdrawable
uint256 feeDebt0; // for per-user fee accounting
uint256 feeDebt1; // for per-user fee accounting
uint256 rewardDebt; // for DACKIE reward accounting
uint256 lockMultiplier; // Q1e18 multiplier applied to locked liquidity (default 1e18)
}
/// @notice Track user positions
mapping(address => UserPosition) private userPositions;
/// @notice Fee accumulator for token0 (per unit of liquidity)
uint256 public accFee0PerLiquidity;
/// @notice Fee accumulator for token1 (per unit of liquidity)
uint256 public accFee1PerLiquidity;
/// @notice Swap path for WETH to cbBTC
bytes public pathWETHToCbBTC;
/// @notice Swap path for DACKIE to cbBTC
bytes public pathDACKIEToCbBTC;
/// @notice Tick spacing for the fee tier
int24 public immutable tickSpacing;
/// @notice Lower tick for full-range position
int24 public immutable tickLower;
/// @notice Upper tick for full-range position
int24 public immutable tickUpper;
/// @notice Whether token0 is WETH (true) or DACKIE (false)
bool public immutable isWETHToken0;
/// @notice Treasury address to receive fee split
address public treasuryAddress;
/// @notice Treasury fee in basis points (e.g., 2000 = 20%)
uint16 public treasuryFeeBps;
/*//////////////////////////////////////////////////////////////
DACKIE REWARD STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice Latest reward period number
uint256 public latestPeriodNumber;
/// @notice Latest reward period start time
uint256 public latestPeriodStartTime;
/// @notice Latest reward period end time
uint256 public latestPeriodEndTime;
/// @notice DACKIE emission rate per second for current period
uint256 public latestPeriodDACKIEPerSecond;
/// @notice Accumulated DACKIE rewards per unit of reward weight
uint256 public accDACKIERewardPerLiquidity;
/// @notice Total reward weight across all users (normal + locked with multiplier)
uint256 public totalRewardWeight;
/// @notice Receiver contract for DACKIE harvesting
address public receiver;
/// @notice Precision for reward calculations
uint256 public constant REWARD_PRECISION = 1e18;
/// @notice Max lock multiplier (5.0x)
uint256 public constant MAX_LOCK_MULTIPLIER = 5e18;
/// @notice Minimum reward duration
uint256 public constant MIN_REWARD_DURATION = 1 days;
/// @notice Maximum reward duration
uint256 public constant MAX_REWARD_DURATION = 30 days;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Initialize the DackieVault
* @param _positionManager Address of V3 Position Manager
* @param _swapRouter Address of V3 Swap Router
* @param _treasuryAddress Address to receive treasury fee split
* @param _receiver Address of the receiver contract for DACKIE rewards
*/
constructor(
address _positionManager,
address _swapRouter,
address _treasuryAddress,
address _receiver
) Ownable(msg.sender) {
if (_positionManager == address(0) || _swapRouter == address(0)) {
revert InvalidRouter();
}
positionManager = INonfungiblePositionManager(_positionManager);
swapRouter = ISwapRouter(_swapRouter);
factory = IUniswapV3Factory(positionManager.factory());
// Get tick spacing for the fee tier
tickSpacing = factory.feeAmountTickSpacing(FEE_TIER);
// Ensure we have a valid tick spacing
if (tickSpacing == 0) {
tickSpacing = 200; // Default for 1% fee tier
}
// Calculate full-range ticks
tickLower = (TickMath.MIN_TICK / tickSpacing) * tickSpacing;
tickUpper = (TickMath.MAX_TICK / tickSpacing) * tickSpacing;
// Determine token order
isWETHToken0 = WETH < DACKIE;
// Set default swap paths (direct routes for Base)
// WETH -> cbBTC (assuming 0.3% fee pool exists)
pathWETHToCbBTC = abi.encodePacked(WETH, uint24(500), cbBTC);
// DACKIE -> WETH -> cbBTC (via 1% and 0.3% pools)
pathDACKIEToCbBTC = abi.encodePacked(DACKIE, FEE_TIER, WETH, uint24(500), cbBTC);
// Set treasury configuration
if (_treasuryAddress == address(0)) {
revert InvalidRouter(); // Reuse existing error for simplicity
}
treasuryAddress = _treasuryAddress;
treasuryFeeBps = 2000; // Default 20%
// Set receiver address
if (_receiver == address(0)) {
revert InvalidReceiver();
}
receiver = _receiver;
// Approve tokens for position manager and swap router
IERC20(WETH).approve(_positionManager, type(uint256).max);
IERC20(DACKIE).approve(_positionManager, type(uint256).max);
IERC20(WETH).approve(_swapRouter, type(uint256).max);
IERC20(DACKIE).approve(_swapRouter, type(uint256).max);
}
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyReceiver() {
if (msg.sender != receiver) revert InvalidReceiver();
_;
}
/*//////////////////////////////////////////////////////////////
MAIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IDackieVault
*/
function deposit(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
// Transfer tokens from user
IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
(uint256 actualWETH, uint256 actualDACKIE) = _depositInternalNoRefund(
amountWETHDesired,
amountDACKIEDesired,
amountWETHMin,
amountDACKIEMin,
deadline,
false
);
// Refund unused tokens for regular deposit
uint256 refundWETH = amountWETHDesired - actualWETH;
uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;
if (refundWETH > 0) {
IERC20(WETH).safeTransfer(msg.sender, refundWETH);
}
if (refundDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
}
}
/**
* @notice Deposit ETH and DACKIE tokens to the vault (ETH version)
* @param amountDACKIEDesired Amount of DACKIE tokens desired
* @param amountETHMin Minimum ETH amount to use
* @param amountDACKIEMin Minimum DACKIE amount to use
* @param deadline Transaction deadline
*/
function depositETH(
uint256 amountDACKIEDesired,
uint256 amountETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external payable nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (msg.value == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
// Wrap ETH to WETH
_wrapETH(msg.value);
// Transfer DACKIE from user
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
// Call the internal deposit function (no refunds in internal)
(uint256 actualWETH, uint256 actualDACKIE) = _depositInternalNoRefund(
msg.value,
amountDACKIEDesired,
amountETHMin,
amountDACKIEMin,
deadline,
false
);
// Refund excess ETH if any
uint256 excessETH = msg.value - actualWETH;
if (excessETH > 0) {
// Unwrap excess WETH to ETH and send to user
IWETH(WETH).withdraw(excessETH);
(bool success, ) = payable(msg.sender).call{ value: excessETH }("");
require(success, "ETH refund failed");
}
// Refund excess DACKIE if any
uint256 excessDACKIE = amountDACKIEDesired - actualDACKIE;
if (excessDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, excessDACKIE);
}
}
/**
* @notice Internal deposit function shared by deposit and depositETH
* @param amountWETHDesired Amount of WETH tokens desired
* @param amountDACKIEDesired Amount of DACKIE tokens desired
* @param amountWETHMin Minimum WETH amount to use
* @param amountDACKIEMin Minimum DACKIE amount to use
* @param deadline Transaction deadline
* @param isLocked Whether this is a locked deposit
*/
function _depositInternalNoRefund(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
bool isLocked
) internal returns (uint256 actualWETH, uint256 actualDACKIE) {
// Update global fees and rewards before any state changes
_updateGlobalFees();
_updateDACKIERewards();
uint128 liquidityAdded;
uint256 amount0;
uint256 amount1;
if (tokenId == 0) {
// First deposit - mint new position
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(tokenId, liquidityAdded, amount0, amount1) = positionManager.mint(
INonfungiblePositionManager.MintParams({
token0: isWETHToken0 ? WETH : DACKIE,
token1: isWETHToken0 ? DACKIE : WETH,
fee: FEE_TIER,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
recipient: address(this),
deadline: deadline
})
);
} else {
// Add to existing position
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(liquidityAdded, amount0, amount1) = positionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
}
// Update user and total liquidity
totalLiquidity += liquidityAdded;
if (isLocked) {
userPositions[msg.sender].lockedLiquidity += liquidityAdded;
} else {
userPositions[msg.sender].normalLiquidity += liquidityAdded;
}
// Update user fee debt to prevent fee crowding
userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;
// Update reward weights and debt based on locked or normal
if (isLocked) {
// For locked path, weight is updated in _depositLockedInternal; do nothing here
} else {
// Normal deposit: add 1x weight
uint256 weightDelta = liquidityAdded;
totalRewardWeight += weightDelta;
_updateUserRewardDebt(msg.sender, int256(weightDelta));
}
// Calculate actual amounts used
(actualWETH, actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);
emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
}
/**
* @inheritdoc IDackieVault
*/
function withdraw(
uint256 liquidityToRemove,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (liquidityToRemove == 0) revert ZeroAmount();
// Auto-unlock expired liquidity
_autoUnlockExpired(msg.sender);
if (liquidityToRemove > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();
// Update global fees and rewards before any state changes
_updateGlobalFees();
_updateDACKIERewards();
// Auto-collect and convert pending fees to cbBTC before withdrawal
(uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);
if (pendingWETH > 0 || pendingDACKIE > 0) {
_collectAndConvertFeesInternal(msg.sender, pendingWETH, pendingDACKIE, deadline);
}
// Decrease liquidity from position
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(uint256 amount0, uint256 amount1) = positionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: uint128(liquidityToRemove),
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
// Collect the principal amounts
(uint256 collected0, uint256 collected1) = positionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: uint128(amount0),
amount1Max: uint128(amount1)
})
);
// Update state
totalLiquidity -= uint128(liquidityToRemove);
userPositions[msg.sender].normalLiquidity -= uint128(liquidityToRemove);
// Update user fee debt
uint256 feeDebtReduction0 = (liquidityToRemove * accFee0PerLiquidity) / PRECISION;
uint256 feeDebtReduction1 = (liquidityToRemove * accFee1PerLiquidity) / PRECISION;
// Handle underflow protection
if (userPositions[msg.sender].feeDebt0 > feeDebtReduction0) {
userPositions[msg.sender].feeDebt0 -= feeDebtReduction0;
} else {
userPositions[msg.sender].feeDebt0 = 0;
}
if (userPositions[msg.sender].feeDebt1 > feeDebtReduction1) {
userPositions[msg.sender].feeDebt1 -= feeDebtReduction1;
} else {
userPositions[msg.sender].feeDebt1 = 0;
}
// Update reward weight and reward debt after liquidity removal (normal = 1x)
if (liquidityToRemove > 0) {
uint256 weightDeltaNeg = liquidityToRemove; // 1x
if (totalRewardWeight >= weightDeltaNeg) totalRewardWeight -= weightDeltaNeg;
else totalRewardWeight = 0;
_updateUserRewardDebt(msg.sender, -int256(weightDeltaNeg));
}
// Transfer principal back to user
(uint256 withdrawnWETH, uint256 withdrawnDACKIE) = isWETHToken0
? (collected0, collected1)
: (collected1, collected0);
// Unwrap WETH to ETH and send to user
_unwrapAndSendETH(msg.sender, withdrawnWETH);
IERC20(DACKIE).safeTransfer(msg.sender, withdrawnDACKIE);
emit Withdraw(msg.sender, uint128(liquidityToRemove), withdrawnWETH, withdrawnDACKIE);
}
/**
* @notice Emergency withdraw without fee collection (faster, gas-efficient)
* @param liquidityToRemove Amount of liquidity to remove
* @param amountWETHMin Minimum WETH to receive
* @param amountDACKIEMin Minimum DACKIE to receive
* @param deadline Transaction deadline
*/
function withdrawEmergency(
uint256 liquidityToRemove,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external nonReentrant {
if (block.timestamp > deadline) revert DeadlineExpired();
if (liquidityToRemove == 0) revert ZeroAmount();
// Auto-unlock expired liquidity
_autoUnlockExpired(msg.sender);
if (liquidityToRemove > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();
// Update global fees before any state changes (but don't collect)
_updateGlobalFees();
_updateDACKIERewards();
// Decrease liquidity from position
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(uint256 amount0, uint256 amount1) = positionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: uint128(liquidityToRemove),
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
// Collect the principal amounts
(uint256 collected0, uint256 collected1) = positionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: uint128(amount0),
amount1Max: uint128(amount1)
})
);
// Update state
totalLiquidity -= uint128(liquidityToRemove);
userPositions[msg.sender].normalLiquidity -= uint128(liquidityToRemove);
// Update user fee debt
uint256 feeDebtReduction0 = (liquidityToRemove * accFee0PerLiquidity) / PRECISION;
uint256 feeDebtReduction1 = (liquidityToRemove * accFee1PerLiquidity) / PRECISION;
// Handle underflow protection
if (userPositions[msg.sender].feeDebt0 > feeDebtReduction0) {
userPositions[msg.sender].feeDebt0 -= feeDebtReduction0;
} else {
userPositions[msg.sender].feeDebt0 = 0;
}
if (userPositions[msg.sender].feeDebt1 > feeDebtReduction1) {
userPositions[msg.sender].feeDebt1 -= feeDebtReduction1;
} else {
userPositions[msg.sender].feeDebt1 = 0;
}
// Update reward weight and reward debt after liquidity removal (normal = 1x)
if (liquidityToRemove > 0) {
uint256 weightDeltaNeg = liquidityToRemove; // 1x
if (totalRewardWeight >= weightDeltaNeg) totalRewardWeight -= weightDeltaNeg;
else totalRewardWeight = 0;
_updateUserRewardDebt(msg.sender, -int256(weightDeltaNeg));
}
// Transfer principal back to user
(uint256 withdrawnWETH, uint256 withdrawnDACKIE) = isWETHToken0
? (collected0, collected1)
: (collected1, collected0);
// Unwrap WETH to ETH and send to user
_unwrapAndSendETH(msg.sender, withdrawnWETH);
IERC20(DACKIE).safeTransfer(msg.sender, withdrawnDACKIE);
emit Withdraw(msg.sender, uint128(liquidityToRemove), withdrawnWETH, withdrawnDACKIE);
}
/**
* @inheritdoc IDackieVault
*/
function collectAndConvertFees(
uint256 minWETH,
uint256 minDACKIE,
uint256 minCbBTC,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 cbBTCSentToUser, uint256 cbBTCSentToTreasury) {
if (block.timestamp > deadline) revert DeadlineExpired();
// Ensure expired locks are unlocked before reward/fee calculations
_autoUnlockExpired(msg.sender);
// Update global fees and rewards to get latest
_updateGlobalFees();
_updateDACKIERewards();
// Try to distribute DACKIE rewards (with rollback protection)
_distributeDACKIEReward(msg.sender);
// Calculate user's pending fees
(uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);
// Check minimum fee requirements
if (minWETH > 0 && pendingWETH < minWETH) revert InsufficientFees();
if (minDACKIE > 0 && pendingDACKIE < minDACKIE) revert InsufficientFees();
if (pendingWETH == 0 && pendingDACKIE == 0) {
return (0, 0);
}
// Update user fee debt
uint128 userLiq = userPositions[msg.sender].normalLiquidity + userPositions[msg.sender].lockedLiquidity;
userPositions[msg.sender].feeDebt0 = (userLiq * accFee0PerLiquidity) / PRECISION;
userPositions[msg.sender].feeDebt1 = (userLiq * accFee1PerLiquidity) / PRECISION;
// Get actual fee amounts based on token order
(uint256 feeWETH, uint256 feeDACKIE) = isWETHToken0
? (pendingWETH, pendingDACKIE)
: (pendingDACKIE, pendingWETH);
uint256 totalCbBTC = 0;
// Swap WETH fees to cbBTC
if (feeWETH > 0) {
totalCbBTC += _swapToCbBTC(WETH, feeWETH, pathWETHToCbBTC, deadline);
}
// Swap DACKIE fees to cbBTC
if (feeDACKIE > 0) {
totalCbBTC += _swapToCbBTC(DACKIE, feeDACKIE, pathDACKIEToCbBTC, deadline);
}
if (totalCbBTC < minCbBTC) revert SlippageExceeded();
// Calculate treasury split
cbBTCSentToTreasury = (treasuryAddress != address(0) && treasuryFeeBps > 0)
? (totalCbBTC * treasuryFeeBps) / 10000
: 0;
cbBTCSentToUser = totalCbBTC - cbBTCSentToTreasury;
// Transfer cbBTC to treasury and user
if (cbBTCSentToTreasury > 0) {
IERC20(cbBTC).safeTransfer(treasuryAddress, cbBTCSentToTreasury);
}
if (cbBTCSentToUser > 0) {
IERC20(cbBTC).safeTransfer(msg.sender, cbBTCSentToUser);
}
emit FeesCollected(msg.sender, feeWETH, feeDACKIE);
emit FeesConverted(msg.sender, feeWETH, feeDACKIE, cbBTCSentToUser, cbBTCSentToTreasury);
return (cbBTCSentToUser, cbBTCSentToTreasury);
}
/**
* @inheritdoc IDackieVault
*/
function depositLocked(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint32 lockDuration
) external nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
// Calculate lock expiry
uint64 newLockedUntil = _calculateLockExpiry(userPositions[msg.sender].lockedUntil, lockDuration);
// Transfer tokens from user
IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
(uint256 actualWETH, uint256 actualDACKIE) = _depositLockedInternal(
amountWETHDesired,
amountDACKIEDesired,
amountWETHMin,
amountDACKIEMin,
deadline,
newLockedUntil
);
// Refund unused tokens for regular locked deposit
uint256 refundWETH = amountWETHDesired - actualWETH;
uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;
if (refundWETH > 0) {
IERC20(WETH).safeTransfer(msg.sender, refundWETH);
}
if (refundDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
}
}
/**
* @notice Deposit ETH and DACKIE tokens to the vault with lock (ETH version)
* @param amountDACKIEDesired Amount of DACKIE tokens desired
* @param amountETHMin Minimum ETH amount to use
* @param amountDACKIEMin Minimum DACKIE amount to use
* @param deadline Transaction deadline
* @param lockDuration Lock duration in seconds
*/
function depositLockedETH(
uint256 amountDACKIEDesired,
uint256 amountETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint32 lockDuration
) external payable nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (msg.value == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
// Calculate lock expiry
uint64 newLockedUntil = _calculateLockExpiry(userPositions[msg.sender].lockedUntil, lockDuration);
// Wrap ETH to WETH
_wrapETH(msg.value);
// Transfer DACKIE from user
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
(uint256 actualWETH, uint256 actualDACKIE) = _depositLockedInternal(
msg.value,
amountDACKIEDesired,
amountETHMin,
amountDACKIEMin,
deadline,
newLockedUntil
);
// Refund excess ETH if any
uint256 excessETH = msg.value - actualWETH;
if (excessETH > 0) {
// Unwrap excess WETH to ETH and send to user
IWETH(WETH).withdraw(excessETH);
(bool success, ) = payable(msg.sender).call{ value: excessETH }("");
require(success, "ETH refund failed");
}
// Refund excess DACKIE if any
uint256 excessDACKIE = amountDACKIEDesired - actualDACKIE;
if (excessDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, excessDACKIE);
}
}
/**
* @notice Internal locked deposit function
* @param amountWETHDesired Amount of WETH tokens desired
* @param amountDACKIEDesired Amount of DACKIE tokens desired
* @param amountWETHMin Minimum WETH amount to use
* @param amountDACKIEMin Minimum DACKIE amount to use
* @param deadline Transaction deadline
* @param newLockedUntil New lock expiry timestamp
*/
function _depositLockedInternal(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint64 newLockedUntil
) internal returns (uint256 actualWETH, uint256 actualDACKIE) {
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
uint128 liquidityAdded;
uint256 amount0;
uint256 amount1;
// Snapshot existing locked state and multiplier BEFORE mutation
UserPosition storage u = userPositions[msg.sender];
uint128 prevLocked = u.lockedLiquidity;
uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
if (tokenId == 0) {
// First deposit - mint new position
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(tokenId, liquidityAdded, amount0, amount1) = positionManager.mint(
INonfungiblePositionManager.MintParams({
token0: isWETHToken0 ? WETH : DACKIE,
token1: isWETHToken0 ? DACKIE : WETH,
fee: FEE_TIER,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
recipient: address(this),
deadline: deadline
})
);
} else {
// Add to existing position
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(liquidityAdded, amount0, amount1) = positionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
}
// Update user and total liquidity
totalLiquidity += liquidityAdded;
u.lockedLiquidity = uint128(uint256(u.lockedLiquidity) + liquidityAdded);
u.lockedUntil = newLockedUntil;
// Update user fee debt to prevent fee crowding
userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;
// Calculate multiplier and update totalRewardWeight/rewardDebt by weight delta
uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
// Increase weight for EXISTING locked position (exclude newly added) if multiplier grows
if (prevLocked > 0 && newMultiplier > oldMultiplier) {
uint256 bonusDelta = (uint256(prevLocked) * (newMultiplier - oldMultiplier)) / REWARD_PRECISION;
if (bonusDelta > 0) {
totalRewardWeight += bonusDelta;
_updateUserRewardDebt(msg.sender, int256(bonusDelta));
}
}
// Add weight for newly added liquidity with newMultiplier
uint256 weightDeltaNew = (uint256(liquidityAdded) * newMultiplier) / REWARD_PRECISION;
if (weightDeltaNew > 0) {
totalRewardWeight += weightDeltaNew;
_updateUserRewardDebt(msg.sender, int256(weightDeltaNew));
}
u.lockMultiplier = newMultiplier;
// Calculate actual amounts used
(actualWETH, actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);
emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
emit LiquidityLocked(msg.sender, liquidityAdded, newLockedUntil);
}
/**
* @inheritdoc IDackieVault
*/
function convertToLocked(uint128 liquidityToConvert, uint32 lockDuration) external nonReentrant whenNotPaused {
if (liquidityToConvert == 0) revert ZeroAmount();
if (liquidityToConvert > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
// Calculate new lock expiry (use current state before mutation)
uint64 oldLockedUntilSnap = userPositions[msg.sender].lockedUntil;
uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntilSnap, lockDuration);
// Move liquidity from normal to locked
userPositions[msg.sender].normalLiquidity -= liquidityToConvert;
userPositions[msg.sender].lockedLiquidity += liquidityToConvert;
userPositions[msg.sender].lockedUntil = newLockedUntil;
// Adjust weights: remove normal weight, add locked weight with multiplier
UserPosition storage u = userPositions[msg.sender];
uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
uint256 multiplier = newMultiplier;
uint256 removeWeight = uint256(liquidityToConvert); // normal = 1x
uint256 addWeight = (uint256(liquidityToConvert) * multiplier) / REWARD_PRECISION;
if (addWeight >= removeWeight) {
uint256 delta = addWeight - removeWeight;
if (delta > 0) {
totalRewardWeight += delta;
_updateUserRewardDebt(msg.sender, int256(delta));
}
} else {
uint256 deltaNeg = removeWeight - addWeight;
if (deltaNeg > 0) {
if (totalRewardWeight >= deltaNeg) totalRewardWeight -= deltaNeg;
else totalRewardWeight = 0;
_updateUserRewardDebt(msg.sender, -int256(deltaNeg));
}
}
u.lockMultiplier = multiplier;
emit LiquidityConvertedToLocked(msg.sender, liquidityToConvert, newLockedUntil);
}
/**
* @inheritdoc IDackieVault
*/
function increaseLockedLiquidity(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
// Check if user has locked liquidity and lock is not expired
UserPosition storage u = userPositions[msg.sender];
if (u.lockedLiquidity == 0) revert ZeroAmount();
if (block.timestamp >= u.lockedUntil) revert LockExpired();
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
// Transfer tokens from user
IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
// Add to existing position (similar to regular deposit)
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(uint128 liquidityAdded, uint256 amount0, uint256 amount1) = positionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
// Update user and total liquidity (do not change lockedUntil)
totalLiquidity += liquidityAdded;
userPositions[msg.sender].lockedLiquidity += liquidityAdded;
// Update user fee debt to prevent fee crowding
userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;
// Update totalRewardWeight and user reward debt by weight delta at current multiplier
uint256 multiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
uint256 weightDelta = (uint256(liquidityAdded) * multiplier) / REWARD_PRECISION;
if (weightDelta > 0) {
totalRewardWeight += weightDelta;
_updateUserRewardDebt(msg.sender, int256(weightDelta));
}
// Calculate actual amounts used
(uint256 actualWETH, uint256 actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);
// Refund unused tokens
uint256 refundWETH = amountWETHDesired - actualWETH;
uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;
if (refundWETH > 0) {
IERC20(WETH).safeTransfer(msg.sender, refundWETH);
}
if (refundDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
}
emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
emit LockedLiquidityIncreased(msg.sender, liquidityAdded, userPositions[msg.sender].lockedUntil);
}
/**
* @inheritdoc IDackieVault
*/
function extendLockDuration(uint32 lockDuration) external nonReentrant whenNotPaused {
_extendLockDurationMerged(lockDuration);
}
function _extendLockDurationMerged(uint32 lockDuration) internal {
UserPosition storage u = userPositions[msg.sender];
if (u.lockedLiquidity == 0) revert ZeroAmount();
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
uint64 oldLockedUntil = u.lockedUntil;
if (block.timestamp >= oldLockedUntil) {
// Expired path: behave like previous extendExpiredLock
if (lockDuration == 0 || lockDuration > MAX_LOCK_DURATION) {
revert InvalidLockDuration();
}
uint64 newLockedUntil = uint64(block.timestamp) + uint64(lockDuration);
if (newLockedUntil > uint64(block.timestamp) + uint64(MAX_LOCK_DURATION)) {
newLockedUntil = uint64(block.timestamp) + uint64(MAX_LOCK_DURATION);
}
// Remove any old bonus weight that might remain after expiry (multiplier > 1e18)
uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
if (oldMultiplier > REWARD_PRECISION && u.lockedLiquidity > 0) {
uint256 oldBonus = (uint256(u.lockedLiquidity) * (oldMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
if (oldBonus > 0) {
if (totalRewardWeight >= oldBonus) totalRewardWeight -= oldBonus;
else totalRewardWeight = 0;
_updateUserRewardDebt(msg.sender, -int256(oldBonus));
}
}
// Compute and apply new multiplier/bonus for the renewed lock
uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
if (u.lockedLiquidity > 0) {
uint256 newBonus = (uint256(u.lockedLiquidity) * (newMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
if (newBonus > 0) {
totalRewardWeight += newBonus;
_updateUserRewardDebt(msg.sender, int256(newBonus));
}
}
u.lockMultiplier = newMultiplier;
u.lockedUntil = newLockedUntil;
emit ExpiredLockExtended(msg.sender, newLockedUntil);
} else {
// Active lock path: behave like previous extendLockDuration
uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntil, lockDuration);
uint256 oldMultiplier2 = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
uint256 newMultiplier2 = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
if (u.lockedLiquidity > 0 && newMultiplier2 > oldMultiplier2) {
uint256 bonusDelta = (uint256(u.lockedLiquidity) * (newMultiplier2 - oldMultiplier2)) /
REWARD_PRECISION;
if (bonusDelta > 0) {
totalRewardWeight += bonusDelta;
_updateUserRewardDebt(msg.sender, int256(bonusDelta));
}
}
u.lockMultiplier = newMultiplier2;
u.lockedUntil = newLockedUntil;
emit LockDurationExtended(msg.sender, oldLockedUntil, newLockedUntil);
}
}
/**
* @inheritdoc IDackieVault
*/
function increaseLockedLiquidityAndExtend(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint32 lockDuration
) external nonReentrant whenNotPaused {
if (block.timestamp > deadline) revert DeadlineExpired();
if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
if (userPositions[msg.sender].lockedLiquidity == 0) revert ZeroAmount();
// Calculate new lock expiry (use current state before mutation)
uint64 oldLockedUntilSnap = userPositions[msg.sender].lockedUntil;
uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntilSnap, lockDuration);
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
// Transfer tokens from user
IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);
// Add to existing position
(uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
? (amountWETHDesired, amountDACKIEDesired)
: (amountDACKIEDesired, amountWETHDesired);
(uint256 amount0Min, uint256 amount1Min) = isWETHToken0
? (amountWETHMin, amountDACKIEMin)
: (amountDACKIEMin, amountWETHMin);
(uint128 liquidityAdded, uint256 amount0, uint256 amount1) = positionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: amount0Min,
amount1Min: amount1Min,
deadline: deadline
})
);
// Snapshot existing locked state and multiplier BEFORE mutation
UserPosition storage u = userPositions[msg.sender];
uint128 prevLocked = u.lockedLiquidity;
uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
// Update user and total liquidity
totalLiquidity += liquidityAdded;
u.lockedLiquidity += liquidityAdded;
// Update user fee debt to prevent fee crowding
userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;
// Compute new multiplier
uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
// Handle expired vs active lock for EXISTING locked portion
if (block.timestamp >= oldLockedUntilSnap) {
// Expired: remove old bonus entirely then add new bonus from base 1e18
if (oldMultiplier > REWARD_PRECISION && prevLocked > 0) {
uint256 oldBonus = (uint256(prevLocked) * (oldMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
if (oldBonus > 0) {
if (totalRewardWeight >= oldBonus) totalRewardWeight -= oldBonus;
else totalRewardWeight = 0;
_updateUserRewardDebt(msg.sender, -int256(oldBonus));
}
}
if (prevLocked > 0 && newMultiplier > REWARD_PRECISION) {
uint256 newBonus = (uint256(prevLocked) * (newMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
if (newBonus > 0) {
totalRewardWeight += newBonus;
_updateUserRewardDebt(msg.sender, int256(newBonus));
}
}
} else {
// Active: only add delta when multiplier increases
if (prevLocked > 0 && newMultiplier > oldMultiplier) {
uint256 bonusDelta = (uint256(prevLocked) * (newMultiplier - oldMultiplier)) / REWARD_PRECISION;
if (bonusDelta > 0) {
totalRewardWeight += bonusDelta;
_updateUserRewardDebt(msg.sender, int256(bonusDelta));
}
}
}
// Add weight for newly added liquidity with new multiplier
uint256 weightDeltaNew = (uint256(liquidityAdded) * newMultiplier) / REWARD_PRECISION;
if (weightDeltaNew > 0) {
totalRewardWeight += weightDeltaNew;
_updateUserRewardDebt(msg.sender, int256(weightDeltaNew));
}
// Update lock params at the end
u.lockMultiplier = newMultiplier;
u.lockedUntil = newLockedUntil;
// Calculate actual amounts used
(uint256 actualWETH, uint256 actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);
// Refund unused tokens
uint256 refundWETH = amountWETHDesired - actualWETH;
uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;
if (refundWETH > 0) {
IERC20(WETH).safeTransfer(msg.sender, refundWETH);
}
if (refundDACKIE > 0) {
IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
}
emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
emit LockedLiquidityIncreasedAndExtended(msg.sender, liquidityAdded, newLockedUntil);
}
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IDackieVault
*/
function pendingFees(address user) public view returns (uint256 pendingWETH, uint256 pendingDACKIE) {
UserPosition storage userPos = userPositions[user];
uint256 userLiq = uint256(userPos.normalLiquidity) + uint256(userPos.lockedLiquidity);
if (userLiq == 0) return (0, 0);
uint256 accrued0 = (userLiq * accFee0PerLiquidity) / PRECISION;
uint256 accrued1 = (userLiq * accFee1PerLiquidity) / PRECISION;
uint256 pending0 = accrued0 >= userPos.feeDebt0 ? accrued0 - userPos.feeDebt0 : 0;
uint256 pending1 = accrued1 >= userPos.feeDebt1 ? accrued1 - userPos.feeDebt1 : 0;
(pendingWETH, pendingDACKIE) = isWETHToken0 ? (pending0, pending1) : (pending1, pending0);
}
/**
* @inheritdoc IDackieVault
*/
function position() external view returns (uint256, uint128) {
return (tokenId, totalLiquidity);
}
/**
* @inheritdoc IDackieVault
*/
function userInfo(
address user
)
external
view
returns (
uint128 normalLiquidity,
uint128 lockedLiquidity,
uint64 lockedUntil,
uint256 pendingWETH,
uint256 pendingDACKIE
)
{
UserPosition storage userPos = userPositions[user];
(pendingWETH, pendingDACKIE) = pendingFees(user);
return (userPos.normalLiquidity, userPos.lockedLiquidity, userPos.lockedUntil, pendingWETH, pendingDACKIE);
}
/*//////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IDackieVault
*/
function setSwapPaths(bytes calldata pathWETHToCbBTC_, bytes calldata pathDACKIEToCbBTC_) external onlyOwner {
if (pathWETHToCbBTC_.length == 0 || pathDACKIEToCbBTC_.length == 0) {
revert InvalidPath();
}
pathWETHToCbBTC = pathWETHToCbBTC_;
pathDACKIEToCbBTC = pathDACKIEToCbBTC_;
emit SwapPathsUpdated(pathWETHToCbBTC_, pathDACKIEToCbBTC_);
}
/**
* @inheritdoc IDackieVault
*/
function setSwapRouter(address swapRouter_) external onlyOwner {
if (swapRouter_ == address(0)) revert InvalidRouter();
// Revoke old approvals
IERC20(WETH).approve(address(swapRouter), 0);
IERC20(DACKIE).approve(address(swapRouter), 0);
swapRouter = ISwapRouter(swapRouter_);
// Approve new router
IERC20(WETH).approve(swapRouter_, type(uint256).max);
IERC20(DACKIE).approve(swapRouter_, type(uint256).max);
emit SwapRouterUpdated(swapRouter_);
}
/**
* @notice Pause the contract (emergency function)
*/
function pause() external onlyOwner {
_pause();
}
/**
* @notice Unpause the contract
*/
function unpause() external onlyOwner {
_unpause();
}
/**
* @notice Set treasury address for fee splitting
* @param _treasury New treasury address
*/
function setTreasuryAddress(address _treasury) external onlyOwner {
if (_treasury == address(0)) {
revert InvalidRouter(); // Reuse existing error
}
treasuryAddress = _treasury;
emit TreasuryUpdated(treasuryAddress, treasuryFeeBps);
}
/**
* @notice Set treasury fee in basis points
* @param _bps New treasury fee in basis points
*/
function setTreasuryFeeBps(uint16 _bps) external onlyOwner {
if (_bps > MAX_TREASURY_FEE_BPS) {
revert InvalidRouter(); // Reuse existing error
}
treasuryFeeBps = _bps;
emit TreasuryUpdated(treasuryAddress, treasuryFeeBps);
}
/**
* @notice Emergency function to rescue tokens (not core tokens if user shares exist)
* @param token Token address to rescue
* @param recipient Address to send tokens to
*/
function rescueTokens(address token, address recipient) external onlyOwner {
// Don't allow rescuing core tokens if users have shares
if (totalLiquidity > 0) {
require(token != WETH && token != DACKIE && token != cbBTC, "Cannot rescue core tokens");
}
IERC20(token).safeTransfer(recipient, IERC20(token).balanceOf(address(this)));
}
/*//////////////////////////////////////////////////////////////
LOCK EXPIRY FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IDackieVault
*/
function unlockExpired() external {
_autoUnlockExpired(msg.sender);
}
/**
* @notice Update global fee accumulators by syncing with V3 position
* @dev Can be called by anyone to ensure fees are up-to-date
*/
function updateGlobalFees() external {
_updateGlobalFees();
}
/**
* @inheritdoc IDackieVault
*/
function convertExpiredLockedToNormal() external nonReentrant {
UserPosition storage userPos = userPositions[msg.sender];
if (userPos.lockedLiquidity == 0) revert ZeroAmount();
if (block.timestamp < userPos.lockedUntil) revert LiquidityStillLocked();
// Update global fees before any state changes
_updateGlobalFees();
_updateDACKIERewards();
// Auto-collect and convert pending fees to cbBTC before conversion
(uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);
if (pendingWETH > 0 || pendingDACKIE > 0) {
_collectAndConvertFeesInternal(msg.sender, pendingWETH, pendingDACKIE, block.timestamp + 600);
}
uint128 liquidityToConvert = userPos.lockedLiquidity;
// Adjust totalRewardWeight by removing bonus weight of locked portion
uint256 multiplier = userPos.lockMultiplier == 0 ? REWARD_PRECISION : userPos.lockMultiplier;
if (multiplier > REWARD_PRECISION) {
uint256 bonusWeight = (uint256(liquidityToConvert) * (multiplier - REWARD_PRECISION)) / REWARD_PRECISION;
if (bonusWeight > 0) {
if (totalRewardWeight >= bonusWeight) {
totalRewardWeight -= bonusWeight;
} else {
totalRewardWeight = 0;
}
_updateUserRewardDebt(msg.sender, -int256(bonusWeight));
}
}
userPos.normalLiquidity += liquidityToConvert;
userPos.lockedLiquidity = 0;
userPos.lockedUntil = 0;
userPos.lockMultiplier = REWARD_PRECISION;
emit ExpiredLockedConvertedToNormal(msg.sender, liquidityToConvert);
}
/**
* @inheritdoc IDackieVault
*/
// extendExpiredLock removed. Use extendLockDuration for both expired and active locks.
/*//////////////////////////////////////////////////////////////
DACKIE REWARD FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IDackieVault
*/
function upkeep(uint256 amount, uint256 duration) external onlyReceiver {
if (amount == 0) revert ZeroAmount();
if (duration < MIN_REWARD_DURATION || duration > MAX_REWARD_DURATION) {
revert InvalidRewardDuration();
}
// Pull DACKIE from the receiver (receiver must approve the vault beforehand)
IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amount);
// Update reward state before any calculations
_updateDACKIERewards();
uint256 currentTime = block.timestamp;
uint256 endTime = currentTime + duration;
uint256 dackiePerSecond;
uint256 totalAmount = amount;
// Handle remaining rewards from previous period
if (latestPeriodEndTime > currentTime) {
uint256 remainingTime = latestPeriodEndTime - currentTime;
uint256 remainingRewards = (remainingTime * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
totalAmount += remainingRewards;
}
// Calculate new emission rate
dackiePerSecond = (totalAmount * REWARD_PRECISION) / duration;
// Update period state
latestPeriodNumber++;
latestPeriodStartTime = currentTime;
latestPeriodEndTime = endTime;
latestPeriodDACKIEPerSecond = dackiePerSecond;
emit DACKIERewardPeriodStarted(latestPeriodNumber, currentTime, endTime, dackiePerSecond, totalAmount);
}
/**
* @inheritdoc IDackieVault
*/
function pendingDACKIEReward(address user) external view returns (uint256 pending) {
UserPosition storage userPos = userPositions[user];
uint256 userWeight = _userRewardWeight(user);
if (userWeight == 0) return 0;
// Calculate what the accumulated reward per liquidity would be if updated now
uint256 currentAccDACKIERewardPerLiquidity = accDACKIERewardPerLiquidity;
// Add rewards that would be accumulated if _updateDACKIERewards was called
if (totalRewardWeight > 0 && latestPeriodStartTime < block.timestamp && latestPeriodStartTime > 0) {
uint256 effectiveEndTime = block.timestamp > latestPeriodEndTime ? latestPeriodEndTime : block.timestamp;
uint256 timeElapsed = effectiveEndTime - latestPeriodStartTime;
uint256 rewardToDistribute = (timeElapsed * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
currentAccDACKIERewardPerLiquidity += (rewardToDistribute * REWARD_PRECISION) / totalRewardWeight;
}
// Calculate pending rewards based on liquidity proportion
uint256 accumulatedReward = (userWeight * currentAccDACKIERewardPerLiquidity) / REWARD_PRECISION;
if (accumulatedReward >= userPos.rewardDebt) {
pending = accumulatedReward - userPos.rewardDebt;
} else {
pending = 0;
}
}
/**
* @inheritdoc IDackieVault
*/
function getLatestRewardPeriod()
external
view
returns (uint256 periodNumber, uint256 startTime, uint256 endTime, uint256 dackiePerSecond)
{
return (latestPeriodNumber, latestPeriodStartTime, latestPeriodEndTime, latestPeriodDACKIEPerSecond);
}
/**
* @inheritdoc IDackieVault
*/
function setReceiver(address _receiver) external onlyOwner {
if (_receiver == address(0)) revert InvalidReceiver();
receiver = _receiver;
emit ReceiverUpdated(_receiver);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Update global fee accumulators by collecting all fees from the position
*/
function _updateGlobalFees() internal {
if (tokenId == 0 || totalLiquidity == 0) return;
// Collect all fees from the position
(uint256 collected0, uint256 collected1) = positionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
// Update accumulators with newly collected fees
if (collected0 > 0) {
accFee0PerLiquidity += (collected0 * PRECISION) / totalLiquidity;
}
if (collected1 > 0) {
accFee1PerLiquidity += (collected1 * PRECISION) / totalLiquidity;
}
}
/**
* @notice Swap tokens to cbBTC using the configured path
* @param amountIn Amount of input token
* @param path Swap path
* @return amountOut Amount of cbBTC received
*/
function _swapToCbBTC(
address /* tokenIn */,
uint256 amountIn,
bytes memory path,
uint256 /* deadline */
) internal returns (uint256 amountOut) {
try
swapRouter.exactInput(
ISwapRouter.ExactInputParams({
path: path,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: 0 // We handle slippage at the total level
})
)
returns (uint256 output) {
return output;
} catch {
revert SwapFailed();
}
}
/**
* @notice Auto-unlock expired locked liquidity for a user
* @param user Address of the user
*/
function _autoUnlockExpired(address user) internal {
UserPosition storage userPos = userPositions[user];
if (userPos.lockedLiquidity > 0 && block.timestamp >= userPos.lockedUntil) {
// Before changing weights, update rewards
_updateDACKIERewards();
// Adjust totalRewardWeight by removing bonus weight of locked portion
uint256 multiplier = userPos.lockMultiplier == 0 ? REWARD_PRECISION : userPos.lockMultiplier;
if (multiplier > REWARD_PRECISION) {
uint256 bonusWeight = (uint256(userPos.lockedLiquidity) * (multiplier - REWARD_PRECISION)) /
REWARD_PRECISION;
if (bonusWeight > 0) {
// Reduce totalRewardWeight and rewardDebt accordingly
if (totalRewardWeight >= bonusWeight) {
totalRewardWeight -= bonusWeight;
} else {
totalRewardWeight = 0;
}
_updateUserRewardDebt(user, -int256(bonusWeight));
}
}
uint128 liquidityToUnlock = userPos.lockedLiquidity;
userPos.normalLiquidity += liquidityToUnlock;
userPos.lockedLiquidity = 0;
userPos.lockedUntil = 0;
userPos.lockMultiplier = REWARD_PRECISION;
emit LockedLiquidityUnlocked(user, liquidityToUnlock);
}
}
/**
* @notice Calculate new lock expiry timestamp
* @param currentExpiry Current lock expiry (0 if no lock)
* @param lockDuration Additional lock duration in seconds
* @return newExpiry New lock expiry timestamp
*/
function _calculateLockExpiry(uint64 currentExpiry, uint32 lockDuration) internal view returns (uint64 newExpiry) {
if (lockDuration == 0 || lockDuration > MAX_LOCK_DURATION) {
revert InvalidLockDuration();
}
uint64 newFromNow = uint64(block.timestamp) + uint64(lockDuration);
if (newFromNow > uint64(block.timestamp) + uint64(MAX_LOCK_DURATION)) {
newFromNow = uint64(block.timestamp) + uint64(MAX_LOCK_DURATION);
}
return currentExpiry > newFromNow ? currentExpiry : newFromNow;
}
/**
* @notice Calculate lock multiplier based on duration (linear 1.0x → 5.0x)
*/
function _calcLockMultiplier(uint32 lockDuration) internal pure returns (uint256) {
uint256 base = REWARD_PRECISION; // 1e18
if (lockDuration == 0) return base;
if (lockDuration >= MAX_LOCK_DURATION) return MAX_LOCK_MULTIPLIER;
uint256 bonus = ((MAX_LOCK_MULTIPLIER - base) * lockDuration) / MAX_LOCK_DURATION;
return base + bonus;
}
/**
* @notice Compute user's reward weight (normal 1x + locked * multiplier)
*/
function _userRewardWeight(address user) internal view returns (uint256) {
UserPosition storage u = userPositions[user];
uint256 multiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
return uint256(u.normalLiquidity) + (uint256(u.lockedLiquidity) * multiplier) / REWARD_PRECISION;
}
/**
* @notice Update DACKIE reward accumulators based on time elapsed
*/
function _updateDACKIERewards() internal {
if (latestPeriodStartTime == 0) return;
uint256 currentTime = block.timestamp;
if (currentTime <= latestPeriodStartTime) return;
uint256 effectiveEndTime = currentTime > latestPeriodEndTime ? latestPeriodEndTime : currentTime;
uint256 effectiveTimeElapsed = effectiveEndTime - latestPeriodStartTime;
if (effectiveTimeElapsed > 0) {
// Only update accumulator if there's reward weight to distribute to
if (totalRewardWeight > 0) {
uint256 rewardToDistribute = (effectiveTimeElapsed * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
accDACKIERewardPerLiquidity += (rewardToDistribute * REWARD_PRECISION) / totalRewardWeight;
}
// Always update start time to track elapsed time, even if no liquidity
latestPeriodStartTime = effectiveEndTime;
}
}
/**
* @notice Distribute DACKIE rewards to user with rollback protection
* @param user User address
* @return success Whether the reward distribution was successful
*/
function _distributeDACKIEReward(address user) internal returns (bool success) {
UserPosition storage userPos = userPositions[user];
uint256 userWeight = _userRewardWeight(user);
if (userWeight == 0) {
return true;
}
// Calculate pending rewards with underflow clamp
uint256 accumulatedReward = (userWeight * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
uint256 pending;
if (accumulatedReward >= userPos.rewardDebt) {
pending = accumulatedReward - userPos.rewardDebt;
} else {
pending = 0;
}
if (pending > 0) {
// Update reward debt first
userPos.rewardDebt = accumulatedReward;
IERC20(DACKIE).safeTransfer(user, pending);
emit DACKIERewardDistributed(user, pending);
return true;
}
// Update reward debt even if no pending rewards
userPos.rewardDebt = accumulatedReward;
return true;
}
/**
* @notice Update user reward debt after liquidity changes
* @param user User address
* @param liquidityDelta Change in liquidity (positive for increase, negative for decrease)
*/
function _updateUserRewardDebt(address user, int256 liquidityDelta) internal {
UserPosition storage userPos = userPositions[user];
if (liquidityDelta > 0) {
// Increase weight - add to reward debt to prevent reward farming
uint256 increase = (uint256(liquidityDelta) * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
userPos.rewardDebt += increase;
} else if (liquidityDelta < 0) {
// Decrease weight - reduce reward debt proportionally
uint256 rewardDebtReduction = (uint256(-liquidityDelta) * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
if (userPos.rewardDebt > rewardDebtReduction) {
userPos.rewardDebt -= rewardDebtReduction;
} else {
userPos.rewardDebt = 0;
}
}
}
/**
* @notice Internal function to collect and convert fees to cbBTC
* @param user User address
* @param pendingWETH Amount of pending WETH fees
* @param pendingDACKIE Amount of pending DACKIE fees
* @param deadline Transaction deadline
*/
function _collectAndConvertFeesInternal(
address user,
uint256 pendingWETH,
uint256 pendingDACKIE,
uint256 deadline
) internal {
if (pendingWETH == 0 && pendingDACKIE == 0) return;
// Try to distribute DACKIE rewards (with rollback protection)
_distributeDACKIEReward(user);
// Update user fee debt
uint128 userLiq = userPositions[user].normalLiquidity + userPositions[user].lockedLiquidity;
userPositions[user].feeDebt0 = (userLiq * accFee0PerLiquidity) / PRECISION;
userPositions[user].feeDebt1 = (userLiq * accFee1PerLiquidity) / PRECISION;
// Get actual fee amounts based on token order
(uint256 feeWETH, uint256 feeDACKIE) = isWETHToken0
? (pendingWETH, pendingDACKIE)
: (pendingDACKIE, pendingWETH);
uint256 totalCbBTC = 0;
// Swap WETH fees to cbBTC
if (feeWETH > 0) {
totalCbBTC += _swapToCbBTC(WETH, feeWETH, pathWETHToCbBTC, deadline);
}
// Swap DACKIE fees to cbBTC
if (feeDACKIE > 0) {
totalCbBTC += _swapToCbBTC(DACKIE, feeDACKIE, pathDACKIEToCbBTC, deadline);
}
if (totalCbBTC > 0) {
// Calculate treasury split
uint256 cbBTCSentToTreasury = (treasuryAddress != address(0) && treasuryFeeBps > 0)
? (totalCbBTC * treasuryFeeBps) / 10000
: 0;
uint256 cbBTCSentToUser = totalCbBTC - cbBTCSentToTreasury;
// Transfer cbBTC to treasury and user
if (cbBTCSentToTreasury > 0) {
IERC20(cbBTC).safeTransfer(treasuryAddress, cbBTCSentToTreasury);
}
if (cbBTCSentToUser > 0) {
IERC20(cbBTC).safeTransfer(user, cbBTCSentToUser);
}
emit FeesCollected(user, feeWETH, feeDACKIE);
emit FeesConverted(user, feeWETH, feeDACKIE, cbBTCSentToUser, cbBTCSentToTreasury);
}
}
/**
* @notice Handle receipt of NFT from position manager
*/
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
/**
* @notice Receive ETH function to accept ETH deposits
*/
receive() external payable {
// Allow receiving ETH for wrapping purposes
}
/**
* @notice Wrap ETH to WETH
* @param amount Amount of ETH to wrap
*/
function _wrapETH(uint256 amount) internal {
if (amount > 0) {
IWETH(WETH).deposit{ value: amount }();
}
}
/**
* @notice Unwrap WETH to ETH and send to recipient
* @param recipient Address to receive ETH
* @param amount Amount of WETH to unwrap
*/
function _unwrapAndSendETH(address recipient, uint256 amount) internal {
if (amount > 0) {
IWETH(WETH).withdraw(amount);
(bool success, ) = payable(recipient).call{ value: amount }("");
require(success, "ETH transfer failed");
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity >=0.5.0;
/**
* @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
pragma solidity ^0.8.26;
/**
* @title IDackieVault
* @notice Interface for DackieVault - a vault for managing full-range Uniswap V3 liquidity
* positions on WETH/DACKIE pair with automatic fee collection and conversion to cbBTC
*/
interface IDackieVault {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a user deposits liquidity into the vault
* @param user The address of the user making the deposit
* @param liquidity The amount of liquidity added
* @param amountWETH The amount of WETH deposited
* @param amountDACKIE The amount of DACKIE deposited
*/
event Deposit(address indexed user, uint128 liquidity, uint256 amountWETH, uint256 amountDACKIE);
/**
* @notice Emitted when a user withdraws liquidity from the vault
* @param user The address of the user making the withdrawal
* @param liquidity The amount of liquidity removed
* @param amountWETH The amount of WETH withdrawn
* @param amountDACKIE The amount of DACKIE withdrawn
*/
event Withdraw(address indexed user, uint128 liquidity, uint256 amountWETH, uint256 amountDACKIE);
/**
* @notice Emitted when fees are collected for a user
* @param user The address of the user collecting fees
* @param amountWETH The amount of WETH fees collected
* @param amountDACKIE The amount of DACKIE fees collected
*/
event FeesCollected(address indexed user, uint256 amountWETH, uint256 amountDACKIE);
/**
* @notice Emitted when fees are converted to cbBTC and split
* @param user The address of the user for whom fees were converted
* @param amountWETHIn The amount of WETH converted
* @param amountDACKIEIn The amount of DACKIE converted
* @param amountCbBTCUser The amount of cbBTC sent to user
* @param amountCbBTCTreasury The amount of cbBTC sent to treasury
*/
event FeesConverted(
address indexed user,
uint256 amountWETHIn,
uint256 amountDACKIEIn,
uint256 amountCbBTCUser,
uint256 amountCbBTCTreasury
);
/**
* @notice Emitted when treasury configuration is updated
* @param treasury The new treasury address
* @param feeBps The new treasury fee in basis points
*/
event TreasuryUpdated(address indexed treasury, uint16 feeBps);
/**
* @notice Emitted when swap paths are updated
* @param pathWETH New path for WETH to cbBTC swaps
* @param pathDACKIE New path for DACKIE to cbBTC swaps
*/
event SwapPathsUpdated(bytes pathWETH, bytes pathDACKIE);
/**
* @notice Emitted when the swap router is updated
* @param router New swap router address
*/
event SwapRouterUpdated(address indexed router);
/**
* @notice Emitted when liquidity is locked
* @param user The address of the user locking liquidity
* @param liquidityAdded The amount of liquidity locked
* @param lockedUntil The timestamp when the liquidity can be unlocked
*/
event LiquidityLocked(address indexed user, uint128 liquidityAdded, uint64 lockedUntil);
/**
* @notice Emitted when normal liquidity is converted to locked
* @param user The address of the user converting liquidity
* @param liquidityConverted The amount of liquidity converted
* @param lockedUntil The timestamp when the liquidity can be unlocked
*/
event LiquidityConvertedToLocked(address indexed user, uint128 liquidityConverted, uint64 lockedUntil);
/**
* @notice Emitted when locked liquidity is unlocked
* @param user The address of the user whose liquidity was unlocked
* @param liquidityUnlocked The amount of liquidity unlocked
*/
event LockedLiquidityUnlocked(address indexed user, uint128 liquidityUnlocked);
/**
* @notice Emitted when locked liquidity is increased
* @param user The address of the user increasing locked liquidity
* @param liquidityAdded The amount of liquidity added
* @param lockedUntil The timestamp when the liquidity can be unlocked
*/
event LockedLiquidityIncreased(address indexed user, uint128 liquidityAdded, uint64 lockedUntil);
/**
* @notice Emitted when lock duration is extended
* @param user The address of the user extending the lock
* @param oldLockedUntil The previous lock expiry timestamp
* @param newLockedUntil The new lock expiry timestamp
*/
event LockDurationExtended(address indexed user, uint64 oldLockedUntil, uint64 newLockedUntil);
/**
* @notice Emitted when locked liquidity is increased and duration extended
* @param user The address of the user
* @param liquidityAdded The amount of liquidity added
* @param newLockedUntil The new lock expiry timestamp
*/
event LockedLiquidityIncreasedAndExtended(address indexed user, uint128 liquidityAdded, uint64 newLockedUntil);
/**
* @notice Emitted when expired locked liquidity is converted to normal
* @param user The address of the user
* @param liquidityConverted The amount of liquidity converted
*/
event ExpiredLockedConvertedToNormal(address indexed user, uint128 liquidityConverted);
/**
* @notice Emitted when expired lock is extended
* @param user The address of the user
* @param newLockedUntil The new lock expiry timestamp
*/
event ExpiredLockExtended(address indexed user, uint64 newLockedUntil);
/**
* @notice Emitted when a new DACKIE reward period starts
* @param periodNumber The period number
* @param startTime Period start timestamp
* @param endTime Period end timestamp
* @param dackiePerSecond DACKIE emission rate per second
* @param totalAmount Total DACKIE amount for the period
*/
event DACKIERewardPeriodStarted(
uint256 indexed periodNumber,
uint256 startTime,
uint256 endTime,
uint256 dackiePerSecond,
uint256 totalAmount
);
/**
* @notice Emitted when DACKIE rewards are distributed to a user
* @param user The address of the user receiving rewards
* @param amount The amount of DACKIE rewards distributed
*/
event DACKIERewardDistributed(address indexed user, uint256 amount);
/**
* @notice Emitted when the receiver address is updated
* @param receiver The new receiver address
*/
event ReceiverUpdated(address indexed receiver);
/**
* @notice Emitted during emergency DACKIE withdrawal
* @param recipient The address receiving the emergency withdrawal
* @param amount The amount of DACKIE withdrawn
*/
event DACKIEEmergencyWithdraw(address indexed recipient, uint256 amount);
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Information about a user's position in the vault
* @param normalLiquidity The user's normal liquidity (withdrawable anytime)
* @param lockedLiquidity The user's locked liquidity (withdrawable after lockedUntil)
* @param lockedUntil Timestamp when locked liquidity becomes withdrawable
* @param pendingWETH Pending WETH fees to be collected
* @param pendingDACKIE Pending DACKIE fees to be collected
*/
struct UserInfo {
uint128 normalLiquidity;
uint128 lockedLiquidity;
uint64 lockedUntil;
uint256 pendingWETH;
uint256 pendingDACKIE;
}
/**
* @notice Information about the vault's shared position
* @param tokenId The Uniswap V3 NFT token ID (0 if no position exists)
* @param liquidity Total liquidity in the shared position
*/
struct PositionInfo {
uint256 tokenId;
uint128 liquidity;
}
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error InsufficientLiquidity();
error SlippageExceeded();
error DeadlineExpired();
error InsufficientFees();
error SwapFailed();
error InvalidPath();
error ZeroAmount();
error InvalidRouter();
error InvalidLockDuration();
error InsufficientNormalLiquidity();
error LiquidityStillLocked();
error LockExpired();
error InvalidRewardDuration();
error InvalidReceiver();
error NoRewardsAvailable();
error RewardPeriodExpired();
/*//////////////////////////////////////////////////////////////
MAIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Deposit tokens to add liquidity to the vault
* @param amountWETHDesired Desired amount of WETH to deposit
* @param amountDACKIEDesired Desired amount of DACKIE to deposit
* @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
* @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
* @param deadline Transaction deadline
*/
function deposit(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external;
/**
* @notice Deposit tokens as locked liquidity to the vault
* @param amountWETHDesired Desired amount of WETH to deposit
* @param amountDACKIEDesired Desired amount of DACKIE to deposit
* @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
* @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
* @param deadline Transaction deadline
* @param lockDuration Duration to lock the liquidity in seconds
*/
function depositLocked(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint32 lockDuration
) external;
/**
* @notice Convert normal liquidity to locked liquidity
* @param liquidityToConvert Amount of normal liquidity to convert
* @param lockDuration Duration to lock the liquidity in seconds
*/
function convertToLocked(uint128 liquidityToConvert, uint32 lockDuration) external;
/**
* @notice Increase locked liquidity without changing lock duration
* @param amountWETHDesired Desired amount of WETH to deposit
* @param amountDACKIEDesired Desired amount of DACKIE to deposit
* @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
* @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
* @param deadline Transaction deadline
*/
function increaseLockedLiquidity(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external;
/**
* @notice Extend lock duration without changing liquidity
* @param lockDuration Additional duration to extend the lock in seconds
*/
function extendLockDuration(uint32 lockDuration) external;
/**
* @notice Increase locked liquidity and extend lock duration
* @param amountWETHDesired Desired amount of WETH to deposit
* @param amountDACKIEDesired Desired amount of DACKIE to deposit
* @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
* @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
* @param deadline Transaction deadline
* @param lockDuration Duration to extend the lock in seconds
*/
function increaseLockedLiquidityAndExtend(
uint256 amountWETHDesired,
uint256 amountDACKIEDesired,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline,
uint32 lockDuration
) external;
/**
* @notice Unlock expired locked liquidity
*/
function unlockExpired() external;
/**
* @notice Convert expired locked liquidity to normal liquidity
*/
function convertExpiredLockedToNormal() external;
/**
* @notice Withdraw liquidity from the vault
* @param liquidityToRemove Amount of liquidity to remove
* @param amountWETHMin Minimum amount of WETH to receive (slippage protection)
* @param amountDACKIEMin Minimum amount of DACKIE to receive (slippage protection)
* @param deadline Transaction deadline
*/
function withdraw(
uint256 liquidityToRemove,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external;
/**
* @notice Emergency withdraw without fee collection (faster, gas-efficient)
* @param liquidityToRemove Amount of liquidity to remove
* @param amountWETHMin Minimum WETH to receive
* @param amountDACKIEMin Minimum DACKIE to receive
* @param deadline Transaction deadline
*/
function withdrawEmergency(
uint256 liquidityToRemove,
uint256 amountWETHMin,
uint256 amountDACKIEMin,
uint256 deadline
) external;
/**
* @notice Collect accrued fees and convert them to cbBTC with treasury split
* @param minWETH Minimum WETH fees expected (0 to skip check)
* @param minDACKIE Minimum DACKIE fees expected (0 to skip check)
* @param minCbBTC Minimum cbBTC to receive from conversion (before treasury split)
* @param deadline Transaction deadline
* @return cbBTCSentToUser Amount of cbBTC sent to the user
* @return cbBTCSentToTreasury Amount of cbBTC sent to treasury
*/
function collectAndConvertFees(
uint256 minWETH,
uint256 minDACKIE,
uint256 minCbBTC,
uint256 deadline
) external returns (uint256 cbBTCSentToUser, uint256 cbBTCSentToTreasury);
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Get pending fees for a user
* @param user Address of the user
* @return pendingWETH Amount of pending WETH fees
* @return pendingDACKIE Amount of pending DACKIE fees
*/
function pendingFees(address user) external view returns (uint256 pendingWETH, uint256 pendingDACKIE);
/**
* @notice Get pending DACKIE rewards for a user
* @param user Address of the user
* @return pending Amount of pending DACKIE rewards
*/
function pendingDACKIEReward(address user) external view returns (uint256 pending);
/**
* @notice Get information about the vault's shared position
* @return tokenId The Uniswap V3 NFT token ID (0 if no position)
* @return liquidity Total liquidity in the position
*/
function position() external view returns (uint256 tokenId, uint128 liquidity);
/**
* @notice Get comprehensive information about a user's position
* @param user Address of the user
* @return normalLiquidity User's normal liquidity
* @return lockedLiquidity User's locked liquidity
* @return lockedUntil Timestamp when locked liquidity becomes withdrawable
* @return pendingWETH Pending WETH fees
* @return pendingDACKIE Pending DACKIE fees
*/
function userInfo(
address user
)
external
view
returns (
uint128 normalLiquidity,
uint128 lockedLiquidity,
uint64 lockedUntil,
uint256 pendingWETH,
uint256 pendingDACKIE
);
/*//////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Set the swap paths for converting fees to cbBTC
* @param pathWETHToCbBTC Path for WETH to cbBTC swaps
* @param pathDACKIEToCbBTC Path for DACKIE to cbBTC swaps
*/
function setSwapPaths(bytes calldata pathWETHToCbBTC, bytes calldata pathDACKIEToCbBTC) external;
/**
* @notice Set the swap router address
* @param swapRouter Address of the new swap router
*/
function setSwapRouter(address swapRouter) external;
/**
* @notice Set treasury address for fee splitting
* @param treasury New treasury address
*/
function setTreasuryAddress(address treasury) external;
/**
* @notice Set treasury fee in basis points
* @param bps New treasury fee in basis points
*/
function setTreasuryFeeBps(uint16 bps) external;
/**
* @notice Update global fee accumulators by syncing with Uniswap V3 position
* @dev Can be called by anyone to ensure fees are up-to-date
*/
function updateGlobalFees() external;
/**
* @notice Update DACKIE reward period (called by receiver)
* @param amount Amount of DACKIE to distribute
* @param duration Period duration in seconds
*/
function upkeep(uint256 amount, uint256 duration) external;
/**
* @notice Set receiver contract address
* @param receiver Receiver contract address
*/
function setReceiver(address receiver) external;
/**
* @notice Get latest reward period information
* @return periodNumber Current period number
* @return startTime Period start timestamp
* @return endTime Period end timestamp
* @return dackiePerSecond DACKIE emission rate per second
*/
function getLatestRewardPeriod()
external
view
returns (uint256 periodNumber, uint256 startTime, uint256 endTime, uint256 dackiePerSecond);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
/**
* @title Uniswap V3 Interfaces
* @notice Minimal interfaces for Uniswap V3 core and periphery contracts
*/
interface INonfungiblePositionManager is IERC721Receiver {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function mint(
MintParams calldata params
) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function increaseLiquidity(
IncreaseLiquidityParams calldata params
) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1);
function decreaseLiquidity(
DecreaseLiquidityParams calldata params
) external payable returns (uint256 amount0, uint256 amount1);
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
function positions(
uint256 tokenId
)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
function factory() external view returns (address);
function WETH9() external view returns (address);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
struct ExactInputParams {
bytes path;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}
interface IUniswapV3Pool {
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
function liquidity() external view returns (uint128);
function tickSpacing() external view returns (int24);
function fee() external view returns (uint24);
function token0() external view returns (address);
function token1() external view returns (address);
}
interface IUniswapV3Factory {
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
function feeAmountTickSpacing(uint24 fee) external view returns (int24);
}
/**
* @title TickMath
* @notice Computes sqrt price for ticks and vice versa
*/
library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**127
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(int256(MAX_TICK)), "T");
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 wad) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @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);
}{
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@uniswap/v3-core/=lib/v3-core/",
"@uniswap/v3-periphery/=lib/v3-periphery/",
"@forge-std/=lib/forge-std/src/",
"@ds-test/=lib/ds-test/src/",
"@dackievault/=src/",
"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/",
"v3-core/=lib/v3-core/",
"v3-periphery/=lib/v3-periphery/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_swapRouter","type":"address"},{"internalType":"address","name":"_treasuryAddress","type":"address"},{"internalType":"address","name":"_receiver","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"InsufficientFees","type":"error"},{"inputs":[],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InsufficientNormalLiquidity","type":"error"},{"inputs":[],"name":"InvalidLockDuration","type":"error"},{"inputs":[],"name":"InvalidPath","type":"error"},{"inputs":[],"name":"InvalidReceiver","type":"error"},{"inputs":[],"name":"InvalidRewardDuration","type":"error"},{"inputs":[],"name":"InvalidRouter","type":"error"},{"inputs":[],"name":"LiquidityStillLocked","type":"error"},{"inputs":[],"name":"LockExpired","type":"error"},{"inputs":[],"name":"NoRewardsAvailable","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"RewardPeriodExpired","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SlippageExceeded","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DACKIEEmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DACKIERewardDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"periodNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"startTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dackiePerSecond","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"DACKIERewardPeriodStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"ExpiredLockExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityConverted","type":"uint128"}],"name":"ExpiredLockedConvertedToNormal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"FeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWETHIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIEIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCbBTCUser","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCbBTCTreasury","type":"uint256"}],"name":"FeesConverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityConverted","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LiquidityConvertedToLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LiquidityLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint64","name":"oldLockedUntil","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"LockDurationExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LockedLiquidityIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"LockedLiquidityIncreasedAndExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityUnlocked","type":"uint128"}],"name":"LockedLiquidityUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"}],"name":"ReceiverUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pathWETH","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"pathDACKIE","type":"bytes"}],"name":"SwapPathsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"router","type":"address"}],"name":"SwapRouterUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"treasury","type":"address"},{"indexed":false,"internalType":"uint16","name":"feeBps","type":"uint16"}],"name":"TreasuryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DACKIE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_TIER","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_LOCK_DURATION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_LOCK_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_TREASURY_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accDACKIERewardPerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accFee0PerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accFee1PerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cbBTC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"minWETH","type":"uint256"},{"internalType":"uint256","name":"minDACKIE","type":"uint256"},{"internalType":"uint256","name":"minCbBTC","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"collectAndConvertFees","outputs":[{"internalType":"uint256","name":"cbBTCSentToUser","type":"uint256"},{"internalType":"uint256","name":"cbBTCSentToTreasury","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"convertExpiredLockedToNormal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"liquidityToConvert","type":"uint128"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"convertToLocked","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"depositETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"depositLocked","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"depositLockedETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"extendLockDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract IUniswapV3Factory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatestRewardPeriod","outputs":[{"internalType":"uint256","name":"periodNumber","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"dackiePerSecond","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"increaseLockedLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"increaseLockedLiquidityAndExtend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isWETHToken0","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodDACKIEPerSecond","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodEndTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pathDACKIEToCbBTC","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pathWETHToCbBTC","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"pendingDACKIEReward","outputs":[{"internalType":"uint256","name":"pending","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"pendingFees","outputs":[{"internalType":"uint256","name":"pendingWETH","type":"uint256"},{"internalType":"uint256","name":"pendingDACKIE","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"position","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"contract INonfungiblePositionManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"receiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"}],"name":"setReceiver","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"pathWETHToCbBTC_","type":"bytes"},{"internalType":"bytes","name":"pathDACKIEToCbBTC_","type":"bytes"}],"name":"setSwapPaths","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"swapRouter_","type":"address"}],"name":"setSwapRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_treasury","type":"address"}],"name":"setTreasuryAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_bps","type":"uint16"}],"name":"setTreasuryFeeBps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapRouter","outputs":[{"internalType":"contract ISwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickLower","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickUpper","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLiquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRewardWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasuryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasuryFeeBps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockExpired","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateGlobalFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"upkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userInfo","outputs":[{"internalType":"uint128","name":"normalLiquidity","type":"uint128"},{"internalType":"uint128","name":"lockedLiquidity","type":"uint128"},{"internalType":"uint64","name":"lockedUntil","type":"uint64"},{"internalType":"uint256","name":"pendingWETH","type":"uint256"},{"internalType":"uint256","name":"pendingDACKIE","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidityToRemove","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidityToRemove","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdrawEmergency","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
61014080604052346108c957608081615b1780380380916100208285610938565b8339810103126108c9576100338161095b565b61003f6020830161095b565b9161005860606100516040840161095b565b920161095b565b903315610925575f8054336001600160a01b0319821681178355604051959290916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3600180556001600160a01b03169283158015610914575b61066e57608084905260028054610100600160a81b031916600887901b610100600160a81b031617905563c45a015560e01b81526001600160a01b039490941693602081600481875afa9081156105fd575f916108d5575b506001600160a01b031660a08190526040516322afcccb60e01b8152612710600482015290602090829060249082905afa9081156105fd575f91610895575b508060c05260020b1561088b575b60c05160020b80156108775761018590620d89e7190560c05160020b9061096f565b60e05260c05160020b8015610877576101a990620d89e80560c05160020b9061096f565b610100526001610120526040517f42000000000000000000000000000000000000060000000000000000000000006020820152607d60ea1b60348201527fcbb7c0000ab88b473b1f5afd9ef808440eed33bf0000000000000000000000006037820152602b815261021b604b82610938565b8051906001600160401b03821161077a5760085490600182811c9216801561086d575b602083101461075c5781601f8493116107ff575b50602090601f8311600114610799575f9261078e575b50508160011b915f199060031b1c1916176008555b6040517f73326b4d0225c429bed050c11c4422d91470aaf4000000000000000000000000602082015261027160ec1b60348201527f42000000000000000000000000000000000000060000000000000000000000006037820152607d60ea1b604b8201527fcbb7c0000ab88b473b1f5afd9ef808440eed33bf000000000000000000000000604e82015260428152610316606282610938565b8051906001600160401b03821161077a5760095490600182811c92168015610770575b602083101461075c5781601f8493116106ee575b50602090601f8311600114610688575f9261067d575b50508160011b915f199060031b1c1916176009555b6001600160a01b0316801561066e57600a80546001600160b01b031916909117607d60a41b1790556001600160a01b0316801561065f57601180546001600160a01b03191691909117905560405163095ea7b360e01b8152600481018290525f1960248201526020816044815f7342000000000000000000000000000000000000065af180156105fd57610642575b5060405163095ea7b360e01b815260048101919091525f1960248201526020816044815f7373326b4d0225c429bed050c11c4422d91470aaf45af180156105fd57610625575b5060405163095ea7b360e01b8152600481018290525f1960248201526020816044815f7342000000000000000000000000000000000000065af180156105fd57610608575b5060405163095ea7b360e01b815260048101919091525f1960248201526020816044815f7373326b4d0225c429bed050c11c4422d91470aaf45af180156105fd576105ce575b60405161516490816109b38239608051818181610f350152818161115b015281816116fb01528181611d8c0152818161208f01528181613f2d015281816142650152818161451401528181614777015261484c015260a0518161068f015260c0518161062a015260e051818181611a0a01528181613e9d0152614484015261010051818181611ae501528181613ec601526144ad01526101205181818161106d015281816111190152818161167d01528181611d4c015281816134e5015281816137f601528181613e1101528181614092015281816141ed015281816143f801528181614632015281816146ff0152614d550152f35b6105ef9060203d6020116105f6575b6105e78183610938565b81019061099a565b505f6104d8565b503d6105dd565b6040513d5f823e3d90fd5b6106209060203d6020116105f6576105e78183610938565b610492565b61063d9060203d6020116105f6576105e78183610938565b61044d565b61065a9060203d6020116105f6576105e78183610938565b610407565b631e4ec46b60e01b5f5260045ffd5b63466d7fef60e01b5f5260045ffd5b015190505f80610363565b60095f9081528281209350601f198516905b8181106106d657509084600195949392106106be575b505050811b01600955610378565b01515f1960f88460031b161c191690555f80806106b0565b9293602060018192878601518155019501930161069a565b60095f529091507f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510610752575b90601f859493920160051c01905b818110610744575061034d565b5f8155849350600101610737565b9091508190610729565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610339565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610268565b60085f9081528281209350601f198516905b8181106107e757509084600195949392106107cf575b505050811b0160085561027d565b01515f1960f88460031b161c191690555f80806107c1565b929360206001819287860151815501950193016107ab565b60085f529091507ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3601f840160051c81019160208510610863575b90601f859493920160051c01905b8181106108555750610252565b5f8155849350600101610848565b909150819061083a565b91607f169161023e565b634e487b7160e01b5f52601260045260245ffd5b60c860c052610163565b90506020813d6020116108cd575b816108b060209383610938565b810103126108c957518060020b81036108c9575f610155565b5f80fd5b3d91506108a3565b90506020813d60201161090c575b816108f060209383610938565b810103126108c957602061090560249261095b565b9150610116565b3d91506108e3565b506001600160a01b038516156100be565b631e4fbdf760e01b5f525f60045260245ffd5b601f909101601f19168101906001600160401b0382119082101761077a57604052565b51906001600160a01b03821682036108c957565b9060020b9060020b02908160020b91820361098657565b634e487b7160e01b5f52601160045260245ffd5b908160209103126108c9575180151581036108c9579056fe6080604052600436101561001a575b3615610018575f80fd5b005b5f5f3560e01c806305fd570614612e6e57806309218e9114612e415780630cf058b414612e16578063150b7a0214612dc057806315770f9214612d9a5780631608bbbd14612d7f57806317d70f7c14612d6257806318dbf54514612cc35780631959a00214612c5257806325d2a3f314612c235780632d7d8eed14612ace5780632ed66478146129d057806330ec24cc146129b257806336db87351461297857806338bc362c1461260e5780633b558ec8146125df5780633d6aa5e114610a835780633f4520f5146125c15780633f4ba83a146125595780633fb26f221461200c5780634127365714611de057806347d32ab014611dc25780634c22b3ed14611ceb5780634c69a6c914611cce5780634f1bfc9e14611caf5780635431c94e14611b0b57806355b812a814611acd57806355cafdea14611a3057806359c4f905146119f25780635ad182d3146119c35780635be7dc55146115ea5780635c662ddc146115d05780635c975abb146115ad5780635cc27d6f1461158f5780636605bfda1461150157806366975ef9146114e4578063674fb1b4146110925780636ba9c71b146110555780636d4cec7814611037578063715018a614610fdd578063718da7ee14610f64578063791b98bc14610f1f57806380a60f0c14610eeb57806383bc112414610ecd5780638456cb5914610e725780638c3853b014610e565780638da5cb5b14610e2f578063a3af179814610ac5578063a772fd2d14610a88578063aaf5eb6814610a83578063ad5c464814610a60578063b0b9151714610a3d578063b0fe2ae2146107cc578063b8b37980146107ae578063bf02b5ea146106eb578063c31c9c07146106be578063c45a015514610679578063c5f956af14610650578063d0c93a7c14610612578063d64b84b5146105f4578063df227bb2146105d6578063e80cfa5e146105b1578063f072fe20146103ca578063f2fde38b14610344578063f7260d3e1461031b5763fcf0b9d8146102fb575061000e565b346103185780600319360112610318576020600c54604051908152f35b80fd5b50346103185780600319360112610318576011546040516001600160a01b039091168152602090f35b50346103185760203660031901126103185761035e612fe7565b610366613b6a565b6001600160a01b031680156103b65781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b50346103185780600319360112610318576103e3613b90565b33815260056020526040812090815460801c156105a257600182016001600160401b038154164210610593576104176147ec565b61041f613a59565b6104283361345c565b811580159061058a575b61056a575b5050825460801c906005840190815480155f146105645750670de0b6b3a7640000945b670de0b6b3a764000086116104dc575b90816001600160801b0361048b8682670de0b6b3a76400009796541661363e565b166001600160801b03169055805467ffffffffffffffff191690555560405190815233907f3b1b8505ee84a56aa36376056ce2813b1f931924953865e6baf68bc218b961a290602090a26001805580f35b670de0b6b3a763ffff19860195861161055057670de0b6b3a76400006105058596978296613313565b0480610516575b508594935061046a565b6010546105419161053b9181116105475761053381601054613306565b60105561367e565b336149d1565b5f61050c565b8860105561367e565b634e487b7160e01b85526011600452602485fd5b9461045a565b6102584201918242116105505790610583929133614cb5565b5f80610437565b50801515610432565b635ee7b8d760e11b8252600482fd5b631f2a200560e01b8152600490fd5b5034610318578060031936011261031857602061ffff600a5460a01c16604051908152f35b50346103185780600319360112610318576020600f54604051908152f35b50346103185780600319360112610318576020601054604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b5034610318578060031936011261031857600a546040516001600160a01b039091168152602090f35b50346103185780600319360112610318576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461031857806003193601126103185760025460405160089190911c6001600160a01b03168152602090f35b5034610318576106fa366132d7565b91610706949394613b90565b61070e613bb0565b82421161079f5783158015610797575b61078857839261074c9261074c9261073a610752973033613a0a565b6107458830336139ae565b87866143ce565b92613306565b9080610778575b5080610768575b506001805580f35b61077290336142ff565b5f610760565b6107829033614342565b5f610759565b631f2a200560e01b8652600486fd5b50841561071e565b631ab7da6b60e01b8652600486fd5b50346103185780600319360112610318576020600754604051908152f35b5034610318576040366003190112610318576004356001600160801b038116808203610a395760243563ffffffff81168103610a355761080a613b90565b610812613bb0565b8115610a265733845260056020526001600160801b036040852054168211610a17577fbf0227234580f604a05ef4fbcecdaaaebc4596dbe3bf5fe3e3f2d5b27cade13e9161088b6109ad926108656147ec565b61086d613a59565b33875260056020526001600160401b03600160408920015416613c0c565b903386526005602052604086206001600160801b036108ad87828454166136f9565b166001600160801b031982541617905533865260056020526108fa604087206108da87825460801c61363e565b81546001600160801b031660809190911b6001600160801b031916179055565b3386526005602052600160408720016001600160401b0383166001600160401b0319825416179055338652600560205260056040872061095163ffffffff61094b6001600160401b0342168761365e565b16614960565b92670de0b6b3a76400006109658583613313565b048181106109d6579061097791613306565b806109b7575b505b01556040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b0390a26001805580f35b806109c76109d0926010546132f9565b601055336149d1565b5f61097d565b6109df91613306565b806109eb575b5061097f565b601054610a089161053b918111610a0e5761053381601054613306565b5f6109e5565b8a60105561367e565b635bb93cd760e01b8452600484fd5b631f2a200560e01b8452600484fd5b8380fd5b8280fd5b50346103185780600319360112610318576020604051674563918244f400008152f35b50346103185780600319360112610318576040516006602160991b018152602090f35b613282565b5034610318576040610ab5610a9c36613264565b92610aa8929192613b90565b610ab0613bb0565b613719565b6001805582519182526020820152f35b50346103185760203660031901126103185760043563ffffffff811690818103610a3957610af1613b90565b610af9613bb0565b338352600560205260408320805460801c15610a2657610b176147ec565b610b1f613a59565b60018101916001600160401b0383541690814210155f14610d4457505082158015610d37575b610d2857610b5d6001600160401b0342169384613bec565b926001600160401b03610b6f82613bcb565b166001600160401b03851611610d17575b6005820190815480155f14610d125750670de0b6b3a76400005b670de0b6b3a7640000811180610d05575b610c7e575b5063ffffffff61094b610bc3928761365e565b915460801c80610c26575b50556001600160401b0382166001600160401b03198254161790556001600160401b03604051911681527fba658ab540503bde77f18ef3e57928a936d504ef5c57bdd3f56f69b96a78dcfc60203392a25b6001805580f35b670de0b6b3a763ffff19830190838211610c6a57670de0b6b3a764000091610c4d91613313565b048015610bce57806109c7610c64926010546132f9565b5f610bce565b634e487b7160e01b87526011600452602487fd5b835460801c670de0b6b3a763ffff198201918211610cf157610bc392670de0b6b3a7640000610cb563ffffffff9461094b94613313565b0480610cc5575b50925050610bb0565b601054610ce29161053b918111610ce85761053381601054613306565b5f610cbc565b8b60105561367e565b634e487b7160e01b88526011600452602488fd5b50835460801c1515610bab565b610b9a565b9250610d2283613bcb565b92610b80565b630f962f3d60e31b8452600484fd5b506303c267008311610b45565b819450610d5a906001600160401b039492613c0c565b9160058101805480155f14610e2a5750670de0b6b3a76400005b610d8863ffffffff61094b8842168861365e565b925460801c9081151580610e21575b610de1575b50505582821683198254161790556040519283521660208201527f6af432668cf14bfd72598906f14d15dd09552b679e589c26b753ff547bc5a60860403392a2610c1f565b670de0b6b3a764000091610df8610dfe9286613306565b90613313565b0480610e0b575b80610d9c565b806109c7610e1b926010546132f9565b5f610e05565b50808411610d97565b610d74565b5034610318578060031936011261031857546040516001600160a01b039091168152602090f35b5034610318578060031936011261031857610e6f6147ec565b80f35b5034610318578060031936011261031857610e8b613b6a565b610e93613bb0565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103185780600319360112610318576020600654604051908152f35b5034610318578060031936011261031857610f1b610f076131be565b604051918291602083526020830190613240565b0390f35b50346103185780600319360112610318576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461031857602036600319011261031857610f7e612fe7565b610f86613b6a565b6001600160a01b03168015610fce57601180546001600160a01b031916821790557f75fd3aa5d9b6e2a8a9d8894008c9263200713f4b1fa9113665e09ceac00277468280a280f35b631e4ec46b60e01b8252600482fd5b5034610318578060031936011261031857610ff6613b6a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346103185780600319360112610318576020600d54604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b5034610318576110a136613264565b6110ad93929193613b90565b6110b5613bb0565b8042116114d55782156114c6576110cb33614a64565b33855260056020526001600160801b0360408620541683116114b75784906110f16147ec565b6110f9613a59565b806111033361345c565b819291158015906114ae575b61149c575b5050507f000000000000000000000000000000000000000000000000000000000000000092835f1461148f579060406111f592965b60035482516001600160801b038916997f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169591939192611191856130a7565b8452602084018b81528585019190915260608401918252608084019283528451630624e65f60e11b81528451600482015290516001600160801b031660248201526040909301516044840152516064830152516084820152928390819060a4820190565b038186855af19081156114845760409261125b928591869161145c575b506001600160801b0390816003549387519461122d866130c2565b85523060208601521686840152166060820152835194858094819363fc6f786560e01b8352600483016136bc565b03925af1918215611451577f5874005e1be6e01dcd0a13611d0d006a60f97986eaf93aa45b4e3c0e62f76a909386928794611413575b5061053b61139d916004546001600160801b036112b08a8284166136f9565b16906001600160801b031916176004553389526005602052604089206001600160801b036112e18a828454166136f9565b166001600160801b0319825416179055670de0b6b3a764000061130660065483613313565b04670de0b6b3a764000061131c60075484613313565b0490338b52600560205280600260408d200154115f146113fc57338b526005602052611350600260408d2001918254613306565b90555b338a52600560205280600360408c200154115f146113e557338a526005602052611385600360408c2001918254613306565b90555b60105481116105475761053381601054613306565b156113df575b6113ad8133614bf8565b6113b782336142ff565b604080516001600160801b0390951685526020850191909152830152339180606081016109ad565b906113a3565b503389526005602052886003604082200155611388565b50338a526005602052896002604082200155611353565b61139d91945061053b935061143f9060403d60401161144a575b61143781836130f9565b8101906136a6565b939093949150611291565b503d61142d565b6040513d87823e3d90fd5b6001600160801b03925061147d9150853d871161144a5761143781836130f9565b9091611212565b6040513d85823e3d90fd5b6111f59190604090611149565b6114a69233614cb5565b5f8181611114565b5080151561110f565b635bb93cd760e01b8552600485fd5b631f2a200560e01b8552600485fd5b631ab7da6b60e01b8552600485fd5b503461031857806003193601126103185760206040516113888152f35b50346103185760203660031901126103185761151b612fe7565b611523613b6a565b6001600160a01b03168015611580577f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff96020826bffffffffffffffffffffffff60a01b600a54161780600a5561ffff6040519160a01c168152a280f35b63466d7fef60e01b8252600482fd5b50346103185780600319360112610318576020604051620151808152f35b5034610318578060031936011261031857602060ff600254166040519015158152f35b5034610318578060031936011261031857610e6f33614a64565b5034610318576115f9366132d7565b9390611606939293613b90565b61160e613bb0565b84421161079f57811580156119bb575b6107885733865260056020526040862094855460801c156119ac576001600160401b0360018701541642101561199d576116f69160609161165d6147ec565b611665613a59565b611670853033613a0a565b61167b8630336139ae565b7f000000000000000000000000000000000000000000000000000000000000000096871561199457859287915b891561198e5790915b60035494604051956116c287613078565b86526020860152604085015284840152608083015260a08201526040518093819263219f5d1760e01b835260048301613600565b0381897f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19384156119835786958780938197611914575b50925f5160206150cf5f395f51905f529592670de0b6b3a76400006118128a6005611863999661074c9c996004546001600160801b0361177a8682841661363e565b16906001600160801b03191617600455338152826020526117a6604082206108da86825460801c61363e565b856117be6001600160801b0360065496169586613313565b04338252836020526117d8600260408420019182546132f9565b905561180160036040886117ee60075489613313565b04933381528660205220019182546132f9565b9055015480610df857508290613313565b04806118fe575b50156118ef579061182d9196878592613306565b90806118df575b50806118cf575b50604080516001600160801b0387168152602081019590955284015233929081906060820190565b0390a233825260056020527f1632c94c5e5cdb04939cdca9be282eb8ebf86e6d8dacdbc60ce14b7fdf9f49696109ad6001600160401b036001604086200154166040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b6118d990336142ff565b5f61183b565b6118e99033614342565b5f611834565b929061182d9196878592613306565b806109c761190e926010546132f9565b5f611819565b6118639592985061074c9750600591945092670de0b6b3a76400006118126119625f5160206150cf5f395f51905f52999660603d60601161197c575b61195a81836130f9565b8101906135de565b9590919c8d92969c50969950505050509295939093611738565b503d611950565b6040513d88823e3d90fd5b916116b1565b869286916116a8565b6307b7d7dd60e51b8752600487fd5b631f2a200560e01b8752600487fd5b50821561161e565b5034610318578060031936011261031857602060405173cbb7c0000ab88b473b1f5afd9ef808440eed33bf8152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b503461031857611a3f366132a4565b611a4d959495929192613b90565b611a55613bb0565b824211611abe5784158015611ab6575b6119ac579261074c9261074c92611a986107529796338b5260056020526001600160401b03600160408d20015416613c0c565b92611aa4873033613a0a565b611aaf8930336139ae565b8887613db8565b508515611a65565b631ab7da6b60e01b8752600487fd5b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b503461031857604036600319011261031857611b25612fe7565b611b2d612ffd565b90611b36613b6a565b6001600160801b0360045416611c05575b6040516370a0823160e01b8152306004820152906001600160a01b0316602082602481845afa918215611bfa578492611bbe575b5060405163a9059cbb60e01b60208201526001600160a01b03939093166024840152604480840192909252908252610e6f9190611bb96064836130f9565b615076565b9291506020833d602011611bf2575b81611bda602093836130f9565b81010312611bee5791519091611bb9611b7b565b5f80fd5b3d9150611bcd565b6040513d86823e3d90fd5b6001600160a01b0381166006602160991b018114159081611c90575b81611c71575b50611b475760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072657363756520636f726520746f6b656e73000000000000006044820152606490fd5b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf915014155f611c27565b7373326b4d0225c429bed050c11c4422d91470aaf48114159150611c21565b503461031857806003193601126103185760206040516303c267008152f35b503461031857806003193601126103185760206040516127108152f35b503461031857611cfa36613264565b611d0693929193613b90565b8042116114d55782156114c657611d1c33614a64565b33855260056020526001600160801b0360408620541683116114b7578490611d426147ec565b611d4a613a59565b7f000000000000000000000000000000000000000000000000000000000000000092831561148f579060406111f5929660035482516001600160801b038916997f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169591939192611191856130a7565b50346103185780600319360112610318576020600e54604051908152f35b503461031857602036600319011261031857611dfa612fe7565b611e02613b6a565b6001600160a01b038116908115611ffd5760025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481876006602160991b015af18015611bfa57611fe0575b5060025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481877373326b4d0225c429bed050c11c4422d91470aaf45af18015611bfa57611fc3575b5060028054610100600160a81b03191660089290921b610100600160a81b031691909117905560405163095ea7b360e01b8152600481018290525f196024820152602081604481866006602160991b015af1801561148457611fa6575b5060405163095ea7b360e01b8152600481018290525f196024820152602081604481867373326b4d0225c429bed050c11c4422d91470aaf45af1801561148457611f79575b507f36db479a3b4d3672bd6f5fca4484283f60b5ac70647b1ceec13ecbb1d030a2df8280a280f35b611f9a9060203d602011611f9f575b611f9281836130f9565b81019061368e565b611f51565b503d611f88565b611fbe9060203d602011611f9f57611f9281836130f9565b611f0c565b611fdb9060203d602011611f9f57611f9281836130f9565b611eaf565b611ff89060203d602011611f9f57611f9281836130f9565b611e5a565b63466d7fef60e01b8352600483fd5b50346103185761201b366132a4565b94909391612027613b90565b61202f613bb0565b844211611abe5781158015612551575b6119ac573387526005602052604087205460801c156119ac57606061208a9133895260056020526120816001600160401b03600160408c200154169889613c0c565b9661165d6147ec565b03818a7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19384156125465787968892899661251d575b50338952600560205260408920908789835460801c8c6121de63ffffffff61094b6005890196875480155f146125175750670de0b6b3a7640000945b6004546001600160801b0361211e8a82841661363e565b16906001600160801b0319161760045561215f61213f898d5460801c61363e565b8c546001600160801b031660809190911b6001600160801b031916178c55565b670de0b6b3a764000061217f6001600160801b036006549a16998a613313565b04338252600560205261219a600260408420019182546132f9565b90556121cc60036040670de0b6b3a76400006121b86007548d613313565b0493338152600560205220019182546132f9565b90556001600160401b0342169061365e565b94421061247357670de0b6b3a764000081118061246a575b61240e575b50801515806123fd575b612343575b50946122cf9460016109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d613313565b048061232d575b505501805467ffffffffffffffff19166001600160401b038b161790551561231e57906122999196878592613306565b908061230e575b50806122fe575b50604080516001600160801b038a168152602081019590955284015233929081906060820190565b0390a26040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b61230890336142ff565b5f6122a7565b6123189033614342565b5f6122a0565b92906122999196878592613306565b806109c761233d926010546132f9565b5f612269565b670de0b6b3a763ffff198401908482116123e9576109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98836123b46122cf9f9a60019a613313565b04806123d3575b50989d50505050959a50959a9c50959a50509461220a565b806109c76123e3926010546132f9565b5f6123bb565b634e487b7160e01b8e52601160045260248efd5b50670de0b6b3a76400008411612205565b670de0b6b3a763ffff1981019081116123e957612434670de0b6b3a76400009183613313565b048d81612442575b506121fb565b8161053b916124639360105410155f14610533575061053381601054613306565b5f8d61243c565b508115156121f6565b6109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98886001986122cf9f9a15158061250e575b6124dc575b5050613313565b8591610df86124eb9286613306565b04806124f8575b806124d5565b806109c7612508926010546132f9565b5f6124f2565b508084116124d0565b94612107565b919750945061253b915060603d60601161197c5761195a81836130f9565b91969091945f6120cb565b6040513d89823e3d90fd5b50821561203f565b5034610318578060031936011261031857612572613b6a565b60025460ff8116156125b25760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b638dfc202b60e01b8252600482fd5b5034610318578060031936011261031857602060405162278d008152f35b503461031857806003193601126103185760206040517373326b4d0225c429bed050c11c4422d91470aaf48152f35b5034610318576040366003190112610318576004356001600160401b0381116129745761263f903690600401613013565b91906024356001600160401b038111610a3957612660903690600401613013565b61266b949194613b6a565b8115801561296c575b61295d576001600160401b03821161286857612691600854613040565b601f81116128f5575b508184601f821160011461288757859161287c575b508260011b905f198460031b1c1916176008555b6001600160401b038111612868576126dc600954613040565b601f8111612800575b5083601f821160011461276c57948161275b9286977f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b9791612761575b508160011b905f198360031b1c1916176009555b61274d6040519586956040875260408701916135aa565b9184830360208601526135aa565b0390a180f35b90508201355f612722565b600985525f51602061510f5f395f51905f5290601f198316865b8181106127e8575091839161275b947f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b989994106127cf575b5050600181811b01600955612736565b8301355f19600384901b60f8161c191690555f806127bf565b9192602060018192868c013581550194019201612786565b60098552601f820160051c5f51602061510f5f395f51905f52019060208310612853575b601f0160051c5f51602061510f5f395f51905f5201905b81811061284857506126e5565b85815560010161283b565b5f51602061510f5f395f51905f529150612824565b634e487b7160e01b84526041600452602484fd5b90508301355f6126af565b600886525f5160206150ef5f395f51905f52915083601f198116875b8181106128da5750106128c1575b5050600182811b016008556126c3565b8401355f19600385901b60f8161c191690555f806128b1565b878401358555600190940193602093840193879350016128a3565b60088552601f830160051c5f5160206150ef5f395f51905f52019060208410612948575b601f0160051c5f5160206150ef5f395f51905f5201905b81811061293d575061269a565b858155600101612930565b5f5160206150ef5f395f51905f529150612919565b6320db826760e01b8452600484fd5b508015612674565b5080fd5b5034610318578060031936011261031857600b54600c54600d54600e54604080519485526020850193909352918301526060820152608090f35b50346103185780600319360112610318576020600b54604051908152f35b506129da36613264565b916129e3613b90565b6129eb613bb0565b8242116114d55734158015612ac6575b6114c657612a2d92859492612a2592612a1334613ca8565b612a1e8530336139ae565b84346143ce565b929034613306565b80612a4b575b50612a3e9250613306565b8061076857506001805580f35b6006602160991b013b15612ac157604051632e1a7d4d60e01b8152600481018290528481602481836006602160991b015af1801561145157612aac575b50838080612a3e96612aa594335af1612a9f61352c565b5061356a565b8392612a33565b612ab78580926130f9565b612ac1575f612a88565b505050fd5b5083156129fb565b5060a0366003190112611bee576004359060643560843563ffffffff81168103611bee57612afa613b90565b612b02613bb0565b814211612c145734158015612c0c575b612bfd57612b6991612b3f612b6192335f5260056020526001600160401b03600160405f20015416613c0c565b90612b4934613ca8565b612b548630336139ae565b6044356024358734613db8565b919034613306565b9283612b7b575b612a3e929350613306565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018590525f81602481836006602160991b015af18015612bf257612bd8575b50612bd083808080612a3e9798335af1612a9f61352c565b839250612b70565b612a3e93505f612be7916130f9565b612bd05f9350612bb8565b6040513d5f823e3d90fd5b631f2a200560e01b5f5260045ffd5b508315612b12565b631ab7da6b60e01b5f5260045ffd5b34611bee576020366003190112611bee576040612c46612c41612fe7565b61345c565b82519182526020820152f35b34611bee576020366003190112611bee5760a0612c6d612fe7565b600180831b0381165f526005602052612c8960405f209161345c565b906001600160401b036001845494015416604051936001600160801b038116855260801c6020850152604084015260608301526080820152f35b34611bee576020366003190112611bee5760043561ffff8116808203611bee5761138890612cef613b6a565b11612d5357600a805461ffff60a01b19811660a093841b61ffff60a01b1617918290556040519190921c61ffff1681526001600160a01b03909116907f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff990602090a2005b63466d7fef60e01b5f5260045ffd5b34611bee575f366003190112611bee576020600354604051908152f35b34611bee575f366003190112611bee57610f1b610f0761311a565b34611bee575f366003190112611bee5760206001600160801b0360045416604051908152f35b34611bee576080366003190112611bee57612dd9612fe7565b50612de2612ffd565b506064356001600160401b038111611bee57612e02903690600401613013565b5050604051630a85bd0160e11b8152602090f35b34611bee576020366003190112611bee576020612e39612e34612fe7565b613344565b604051908152f35b34611bee575f366003190112611bee5760406003546001600160801b036004541682519182526020820152f35b34611bee576040366003190112611bee5760115460043590602435906001600160a01b03163303612fd8578115612bfd576201518081108015612fcc575b612fbd57612ebb8230336139ae565b612ec3613a59565b612ecd81426132f9565b9082600d54428111612f88575b5050670de0b6b3a7640000830290838204670de0b6b3a76400001484151715612f7457612f0691613326565b600b54915f198314612f7457612f6f60017f966e984f09b14de9e3af2a3e121a423498feeec6949fc36854f284ea269214d494019485600b5542600c5582600d5583600e5560405193849342859094939260609260808301968352602083015260408201520152565b0390a2005b634e487b7160e01b5f52601160045260245ffd5b612fb5929450612fae612fa5670de0b6b3a7640000924290613306565b600e5490613313565b04906132f9565b918380612eda565b63e7726b7960e01b5f5260045ffd5b5062278d008111612eac565b631e4ec46b60e01b5f5260045ffd5b600435906001600160a01b0382168203611bee57565b602435906001600160a01b0382168203611bee57565b9181601f84011215611bee578235916001600160401b038311611bee5760208381860195010111611bee57565b90600182811c9216801561306e575b602083101461305a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161304f565b60c081019081106001600160401b0382111761309357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761309357604052565b608081019081106001600160401b0382111761309357604052565b61016081019081106001600160401b0382111761309357604052565b90601f801991011681019081106001600160401b0382111761309357604052565b604051905f826008549161312d83613040565b808352926001811690811561319f5750600114613153575b613151925003836130f9565b565b5060085f90815290915f5160206150ef5f395f51905f525b81831061318357505090602061315192820101613145565b602091935080600191548385890101520191019091849261316b565b6020925061315194915060ff191682840152151560051b820101613145565b604051905f82600954916131d183613040565b808352926001811690811561319f57506001146131f457613151925003836130f9565b5060095f90815290915f51602061510f5f395f51905f525b81831061322457505090602061315192820101613145565b602091935080600191548385890101520191019091849261320c565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6080906003190112611bee5760043590602435906044359060643590565b34611bee575f366003190112611bee576020604051670de0b6b3a76400008152f35b60c0906003190112611bee57600435906024359060443590606435906084359060a43563ffffffff81168103611bee5790565b60a0906003190112611bee576004359060243590604435906064359060843590565b91908201809211612f7457565b91908203918211612f7457565b81810292918115918404141715612f7457565b8115613330570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b0381165f9081526005602052604090209061336590613b01565b80156133b557600f5460105480151580613451575b80613446575b6133bb575b5061339b670de0b6b3a764000091600493613313565b04910154808210155f146133b5576133b291613306565b90565b50505f90565b91600d548042115f14613430576133e2612fa5670de0b6b3a7640000925b600c5490613306565b0490670de0b6b3a7640000820291808304670de0b6b3a76400001490151715612f7457613427670de0b6b3a76400009361342160049661339b95613326565b906132f9565b92509250613385565b50670de0b6b3a76400006133e2612fa5426133d9565b50600c541515613380565b50600c54421161337a565b60018060a01b03165f52600560205260405f2061348781546001600160801b038160801c91166132f9565b801561352457670de0b6b3a76400006134b1816134a660065485613313565b049260075490613313565b600284015491900491818110613519576003916134cd91613306565b925b0154808210613510576134e191613306565b905b7f00000000000000000000000000000000000000000000000000000000000000001561350c5791565b9091565b50505f906134e3565b505060035f926134cf565b50505f905f90565b3d15613565573d906001600160401b038211613093576040519161355a601f8201601f1916602001846130f9565b82523d5f602084013e565b606090565b1561357157565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b908060209392818452848401375f828201840152601f01601f1916010190565b51906001600160801b0382168203611bee57565b90816060910312611bee576135f2816135ca565b916040602083015192015190565b91909160a08060c083019480518452602081015160208501526040810151604085015260608101516060850152608081015160808501520151910152565b906001600160801b03809116911601906001600160801b038211612f7457565b906001600160401b03809116911603906001600160401b038211612f7457565b600160ff1b8114612f74575f0390565b90816020910312611bee57518015158103611bee5790565b9190826040910312611bee576020825192015190565b91909160606001600160801b038160808401958051855260018060a01b036020820151166020860152826040820151166040860152015116910152565b906001600160801b03809116911603906001600160801b038211612f7457565b909193924211612c145761372c33614a64565b6137346147ec565b61373c613a59565b61374533614ee5565b5061374f3361345c565b92909180151590816139a4575b5061398b57801515908161399a575b5061398b57801580613983575b61397957335f90815260056020526040902054670de0b6b3a7640000906137e3906137b190608081901c906001600160801b031661363e565b826137c96001600160801b0360065493169283613313565b04335f526005602052600260405f20015560075490613313565b04335f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000005f14613973575b5f938161395a575b8261393e575b841061392f57600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b906001600160a01b038116908115158061391f575b156139135761271061388561ffff61388d9360a01c1689613313565b048097613306565b9384918780613903575b5050816138f4575b6040518481528160208201527f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960403392a2604080519485526020850191909152830152606082018590523391608090a29190565b6138fe823361437b565b61389f565b61390c9161437b565b5f87613897565b5061388d5f8097613306565b5061ffff8160a01c161515613869565b638199f5f360e01b5f5260045ffd5b936139549061342161394e6131be565b85614f9d565b9361382a565b935061396d61396761311a565b82614f9d565b93613824565b9061381c565b505090505f905f90565b508115613778565b638d53e55360e01b5f5260045ffd5b905082105f61376b565b905082105f61375c565b6040516323b872dd60e01b60208201526001600160a01b03918216602482015291166044820152606480820192909252908152613151906139f06084826130f9565b7373326b4d0225c429bed050c11c4422d91470aaf4615076565b6040516323b872dd60e01b60208201526001600160a01b0391821660248201529116604482015260648082019290925290815261315190613a4c6084826130f9565b6006602160991b01615076565b600c548015613afe5780421115613afe57600d548042115f14613af357613a8290915b82613306565b80613a8b575050565b6010549081613a9c575b5050600c55565b613ab2670de0b6b3a764000091600e5490613313565b04670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612f7457613ae991613ae191613326565b600f546132f9565b600f555f80613a95565b50613a824291613a7c565b50565b60018060a01b03165f5260056020526133b260405f20600581015480155f14613b4f57506001600160801b03670de0b6b3a7640000613b4781935b54938460801c613313565b0491166132f9565b670de0b6b3a7640000613b476001600160801b039293613b3c565b5f546001600160a01b03163303613b7d57565b63118cdaa760e01b5f523360045260245ffd5b600260015414613ba1576002600155565b633ee5aeb560e01b5f5260045ffd5b60ff60025416613bbc57565b63d93c066560e01b5f5260045ffd5b6001600160401b036303c26700911601906001600160401b038211612f7457565b906001600160401b03809116911601906001600160401b038211612f7457565b9063ffffffff1680158015613c9b575b613c8c57613c346001600160401b0342169182613bec565b906001600160401b03613c4682613bcb565b166001600160401b03831611613c7c575b506001600160401b0381166001600160401b038316115f14613c77575090565b905090565b613c869150613bcb565b5f613c57565b630f962f3d60e31b5f5260045ffd5b506303c267008111613c1c565b80613cb05750565b6006602160991b013b15611bee57604051630d0e30db60e41b8152905f90829060049082906006602160991b015af18015612bf257613cec5750565b5f613151916130f9565b9190826080910312611bee57815191613d11602082016135ca565b916060604083015192015190565b9190916101408061016083019460018060a01b03815116845260018060a01b03602082015116602085015262ffffff6040820151166040850152606081015160020b6060850152608081015160020b608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015260018060a01b03610120820151166101208501520151910152565b929593909495613dc66147ec565b613dce613a59565b335f52600560205260405f2093845460801c926005860192835480155f146142f95750670de0b6b3a7640000955b600354806141e15750986080939291613f289a7f000000000000000000000000000000000000000000000000000000000000000094855f146141db5792905b85156141d55791925b85156141ba576006602160991b01955b156141ad577373326b4d0225c429bed050c11c4422d91470aaf45b60405196613e7c886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527f000000000000000000000000000000000000000000000000000000000000000060020b60608701527f000000000000000000000000000000000000000000000000000000000000000060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180988192634418b22b60e11b835260048301613d1f565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1928315612bf2575f945f945f985f91614160575b509161408791670de0b6b3a7640000939996976003555b6004546001600160801b03613f998a82841661363e565b16906001600160801b031916176004556001815460801c91613fe86001600160801b03613fc9818d1680966132f9565b83546001600160801b0316911660801b6001600160801b031916178255565b016001600160401b038c166001600160401b03198254161790558361400f60065483613313565b04335f52600560205261402a600260405f20019182546132f9565b90558361403960075483613313565b04335f526005602052614054600360405f20019182546132f9565b905561407163ffffffff61094b426001600160401b03168e61365e565b9586928115158061250e576124dc575050613313565b048061414a575b50557f00000000000000000000000000000000000000000000000000000000000000001561411e576141197f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f643619194955b604080516001600160801b03861681526020810188905290810188905295969533905f5160206150cf5f395f51905f529080606081016122cf565b0390a2565b937f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f6436190614119906140de565b806109c761415a926010546132f9565b5f61408e565b90506140879850670de0b6b3a764000092965061419691955060803d6080116141a6575b61418e81836130f9565b810190613cf6565b9099929791969193509091613f6b565b503d614184565b6006602160991b01613e6f565b7373326b4d0225c429bed050c11c4422d91470aaf495613e54565b92613e44565b90613e3b565b92916060946142609b927f000000000000000000000000000000000000000000000000000000000000000091825f146142f35793915b156142ed5791925b6040519561422c87613078565b86526020860152604085015284840152608083015260a08201526040518098819263219f5d1760e01b835260048301613600565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1928315612bf2575f935f975f916142b8575b50670de0b6b3a76400009161408791989596613f82565b6140879850670de0b6b3a76400009295506142e2915060603d60601161197c5761195a81836130f9565b9098509094916142a1565b9261421f565b91614217565b95613dfc565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906139f081606481015b03601f1981018352826130f9565b60405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481019190915261315190613a4c8160648101614334565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906143b48160648101614334565b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf615076565b92909493916143db6147ec565b6143e3613a59565b600354806146f3575094608093929161450f967f000000000000000000000000000000000000000000000000000000000000000094855f146146ed5792905b85156146e75791925b85156146cc576006602160991b01955b156146bf577373326b4d0225c429bed050c11c4422d91470aaf45b60405196614463886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527f000000000000000000000000000000000000000000000000000000000000000060020b60608701527f000000000000000000000000000000000000000000000000000000000000000060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180948192634418b22b60e11b835260048301613d1f565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f915f905f945f91614697575b509390926003555b6004546001600160801b036145718582841661363e565b16906001600160801b03191617600455335f52600560205260405f206001600160801b036145a2858284541661363e565b166001600160801b0319825416179055614630600654670de0b6b3a76400006145d56001600160801b0387169283613313565b04335f5260056020526145f0600260405f20019182546132f9565b9055670de0b6b3a764000061460760075483613313565b04335f526005602052614622600360405f20019182546132f9565b90556109c7816010546132f9565b7f0000000000000000000000000000000000000000000000000000000000000000156146915792915b604080516001600160801b03909216825260208201859052810183905233905f5160206150cf5f395f51905f52908060608101614119565b91614659565b92945050506146b5915060803d6080116141a65761418e81836130f9565b939190935f614552565b6006602160991b01614456565b7373326b4d0225c429bed050c11c4422d91470aaf49561443b565b9261442b565b90614422565b929160609461477297927f000000000000000000000000000000000000000000000000000000000000000091825f146147e65793915b156147e05791925b6040519561473e87613078565b86526020860152604085015284840152608083015260a08201526040518094819263219f5d1760e01b835260048301613600565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f915f935f916147ba575b509261455a565b919350506147d7915060603d60601161197c5761195a81836130f9565b9290925f6147b3565b92614731565b91614729565b6003548015801561494d575b613afe57604080516148479261480d826130c2565b81523060208201526001600160801b03828201526001600160801b03606082015281518093819263fc6f786560e01b8352600483016136bc565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f905f9261492b575b50801580156148e2575b50508015801561489e575050565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f74576148d56148dd916001600160801b036004541690613326565b6007546132f9565b600755565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f7457614919614921916001600160801b036004541690613326565b6006546132f9565b6006555f80614890565b9050614946915060403d60401161144a5761143781836130f9565b905f614886565b506001600160801b0360045416156147f8565b63ffffffff1680156149c4576303c267008110156149b75780673782dace9d9000000290673782dace9d900000820403612f74576303c267009004670de0b6b3a76400000180670de0b6b3a764000011612f745790565b50674563918244f4000090565b50670de0b6b3a764000090565b6001600160a01b03165f9081526005602052604081209190811315614a1a576004670de0b6b3a7640000614a0b614a1693600f5490613313565b0492019182546132f9565b9055565b5f8112614a25575050565b670de0b6b3a7640000614a45614a3c60049361367e565b600f5490613313565b04910190808254115f14614a5e57614a16908254613306565b505f9055565b6001600160a01b0381165f818152600560205260409020805491929160801c151580614be1575b614a9457505050565b614a9c613a59565b6005810191825480155f14614bdc5750670de0b6b3a76400005b670de0b6b3a76400008111614b39575b50506001600160801b03670de0b6b3a76400007f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369360018484614b13602097548060801c9788911661363e565b166001600160801b0316815501805467ffffffffffffffff1916905555604051908152a2565b825460801c93670de0b6b3a763ffff198201918211612f745760016020946001600160801b0394670de0b6b3a7640000614b9581967f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369a613313565b049081614ba9575b50509450509350614ac6565b601054614bcc92614bc6918111614bd35761053381601054613306565b906149d1565b5f80614b9d565b5f60105561367e565b614ab6565b506001600160401b03600182015416421015614a8b565b5f9082614c0457505050565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018490525f81602481836006602160991b015af18015612bf257614ca0575b5090918291829182916001600160a01b03165af1614c5d61352c565b5015614c6557565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b614cad9192505f906130f9565b5f905f614c41565b91909250821580614edd575b614ed857614cce82614ee5565b506001600160a01b0382165f81815260056020526040902054909390670de0b6b3a764000090614d4290614d1090608081901c906001600160801b031661363e565b82614d286001600160801b0360065493169283613313565b04875f526005602052600260405f20015560075490613313565b04845f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000005f14614ed2575b5f9281614ebf575b82614ea9575b83614d96575b5050505050565b600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b94614e609290916001600160a01b038116919082151580614e99575b15614e8d57612710614df261ffff614dfa9360a01c1686613313565b048094613306565b918380614e7d575b50508180614e6d575b5050867f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960408051878152886020820152a2604051948594859094939260609260808301968352602083015260408201520152565b0390a25f80808080614d8f565b614e769161437b565b5f81614e0b565b614e869161437b565b5f83614e02565b50614dfa5f8094613306565b5061ffff8160a01c161515614dd6565b92614eb99061342161394e6131be565b92614d89565b9250614ecc61396761311a565b92614d83565b90614d7b565b505050565b508015614cc1565b6001600160a01b0381165f8181526005602052604090209190614f0782613b01565b8015614f9457670de0b6b3a7640000614f25600492600f5490613313565b049301918254808510155f14614f8c57614f3f9085613306565b905b81614f4f5750505055600190565b81614f8091602094967f1ea97d707845967623ce740ccc94ddadf7dea761b865a44d0ff09da7404d066e96556142ff565b604051908152a2600190565b505f90614f41565b50505050600190565b600254604051926020929160081c6001600160a01b031690614fbe856130c2565b84526150065f848601953087526040810194855260608101968288526040519788968795869463b858183f60e01b86528a6004870152516080602487015260a4860190613240565b92516001600160a01b0316604485015251606484015251608483015203925af15f9181615042575b506133b25763081ceff360e41b5f5260045ffd5b9091506020813d60201161506e575b8161505e602093836130f9565b81010312611bee5751905f61502e565b3d9150615051565b905f602091828151910182855af115612bf2575f513d6150c557506001600160a01b0381163b155b6150a55750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b6001141561509e56fe2c057cf23622b35e0ed9529292a5f7e4cc561dc18ad64550b35e47ff7f287dcbf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee36e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7afa26469706673582212204d266e960103008a4a99e35fade2f03575378edf2c747c47539702a08a5eab1c64736f6c634300081c0033000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b770000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2
Deployed Bytecode
0x6080604052600436101561001a575b3615610018575f80fd5b005b5f5f3560e01c806305fd570614612e6e57806309218e9114612e415780630cf058b414612e16578063150b7a0214612dc057806315770f9214612d9a5780631608bbbd14612d7f57806317d70f7c14612d6257806318dbf54514612cc35780631959a00214612c5257806325d2a3f314612c235780632d7d8eed14612ace5780632ed66478146129d057806330ec24cc146129b257806336db87351461297857806338bc362c1461260e5780633b558ec8146125df5780633d6aa5e114610a835780633f4520f5146125c15780633f4ba83a146125595780633fb26f221461200c5780634127365714611de057806347d32ab014611dc25780634c22b3ed14611ceb5780634c69a6c914611cce5780634f1bfc9e14611caf5780635431c94e14611b0b57806355b812a814611acd57806355cafdea14611a3057806359c4f905146119f25780635ad182d3146119c35780635be7dc55146115ea5780635c662ddc146115d05780635c975abb146115ad5780635cc27d6f1461158f5780636605bfda1461150157806366975ef9146114e4578063674fb1b4146110925780636ba9c71b146110555780636d4cec7814611037578063715018a614610fdd578063718da7ee14610f64578063791b98bc14610f1f57806380a60f0c14610eeb57806383bc112414610ecd5780638456cb5914610e725780638c3853b014610e565780638da5cb5b14610e2f578063a3af179814610ac5578063a772fd2d14610a88578063aaf5eb6814610a83578063ad5c464814610a60578063b0b9151714610a3d578063b0fe2ae2146107cc578063b8b37980146107ae578063bf02b5ea146106eb578063c31c9c07146106be578063c45a015514610679578063c5f956af14610650578063d0c93a7c14610612578063d64b84b5146105f4578063df227bb2146105d6578063e80cfa5e146105b1578063f072fe20146103ca578063f2fde38b14610344578063f7260d3e1461031b5763fcf0b9d8146102fb575061000e565b346103185780600319360112610318576020600c54604051908152f35b80fd5b50346103185780600319360112610318576011546040516001600160a01b039091168152602090f35b50346103185760203660031901126103185761035e612fe7565b610366613b6a565b6001600160a01b031680156103b65781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b50346103185780600319360112610318576103e3613b90565b33815260056020526040812090815460801c156105a257600182016001600160401b038154164210610593576104176147ec565b61041f613a59565b6104283361345c565b811580159061058a575b61056a575b5050825460801c906005840190815480155f146105645750670de0b6b3a7640000945b670de0b6b3a764000086116104dc575b90816001600160801b0361048b8682670de0b6b3a76400009796541661363e565b166001600160801b03169055805467ffffffffffffffff191690555560405190815233907f3b1b8505ee84a56aa36376056ce2813b1f931924953865e6baf68bc218b961a290602090a26001805580f35b670de0b6b3a763ffff19860195861161055057670de0b6b3a76400006105058596978296613313565b0480610516575b508594935061046a565b6010546105419161053b9181116105475761053381601054613306565b60105561367e565b336149d1565b5f61050c565b8860105561367e565b634e487b7160e01b85526011600452602485fd5b9461045a565b6102584201918242116105505790610583929133614cb5565b5f80610437565b50801515610432565b635ee7b8d760e11b8252600482fd5b631f2a200560e01b8152600490fd5b5034610318578060031936011261031857602061ffff600a5460a01c16604051908152f35b50346103185780600319360112610318576020600f54604051908152f35b50346103185780600319360112610318576020601054604051908152f35b503461031857806003193601126103185760206040517f00000000000000000000000000000000000000000000000000000000000000c860020b8152f35b5034610318578060031936011261031857600a546040516001600160a01b039091168152602090f35b50346103185780600319360112610318576040517f0000000000000000000000003d237ac6d2f425d2e890cc99198818cc1fa488706001600160a01b03168152602090f35b503461031857806003193601126103185760025460405160089190911c6001600160a01b03168152602090f35b5034610318576106fa366132d7565b91610706949394613b90565b61070e613bb0565b82421161079f5783158015610797575b61078857839261074c9261074c9261073a610752973033613a0a565b6107458830336139ae565b87866143ce565b92613306565b9080610778575b5080610768575b506001805580f35b61077290336142ff565b5f610760565b6107829033614342565b5f610759565b631f2a200560e01b8652600486fd5b50841561071e565b631ab7da6b60e01b8652600486fd5b50346103185780600319360112610318576020600754604051908152f35b5034610318576040366003190112610318576004356001600160801b038116808203610a395760243563ffffffff81168103610a355761080a613b90565b610812613bb0565b8115610a265733845260056020526001600160801b036040852054168211610a17577fbf0227234580f604a05ef4fbcecdaaaebc4596dbe3bf5fe3e3f2d5b27cade13e9161088b6109ad926108656147ec565b61086d613a59565b33875260056020526001600160401b03600160408920015416613c0c565b903386526005602052604086206001600160801b036108ad87828454166136f9565b166001600160801b031982541617905533865260056020526108fa604087206108da87825460801c61363e565b81546001600160801b031660809190911b6001600160801b031916179055565b3386526005602052600160408720016001600160401b0383166001600160401b0319825416179055338652600560205260056040872061095163ffffffff61094b6001600160401b0342168761365e565b16614960565b92670de0b6b3a76400006109658583613313565b048181106109d6579061097791613306565b806109b7575b505b01556040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b0390a26001805580f35b806109c76109d0926010546132f9565b601055336149d1565b5f61097d565b6109df91613306565b806109eb575b5061097f565b601054610a089161053b918111610a0e5761053381601054613306565b5f6109e5565b8a60105561367e565b635bb93cd760e01b8452600484fd5b631f2a200560e01b8452600484fd5b8380fd5b8280fd5b50346103185780600319360112610318576020604051674563918244f400008152f35b50346103185780600319360112610318576040516006602160991b018152602090f35b613282565b5034610318576040610ab5610a9c36613264565b92610aa8929192613b90565b610ab0613bb0565b613719565b6001805582519182526020820152f35b50346103185760203660031901126103185760043563ffffffff811690818103610a3957610af1613b90565b610af9613bb0565b338352600560205260408320805460801c15610a2657610b176147ec565b610b1f613a59565b60018101916001600160401b0383541690814210155f14610d4457505082158015610d37575b610d2857610b5d6001600160401b0342169384613bec565b926001600160401b03610b6f82613bcb565b166001600160401b03851611610d17575b6005820190815480155f14610d125750670de0b6b3a76400005b670de0b6b3a7640000811180610d05575b610c7e575b5063ffffffff61094b610bc3928761365e565b915460801c80610c26575b50556001600160401b0382166001600160401b03198254161790556001600160401b03604051911681527fba658ab540503bde77f18ef3e57928a936d504ef5c57bdd3f56f69b96a78dcfc60203392a25b6001805580f35b670de0b6b3a763ffff19830190838211610c6a57670de0b6b3a764000091610c4d91613313565b048015610bce57806109c7610c64926010546132f9565b5f610bce565b634e487b7160e01b87526011600452602487fd5b835460801c670de0b6b3a763ffff198201918211610cf157610bc392670de0b6b3a7640000610cb563ffffffff9461094b94613313565b0480610cc5575b50925050610bb0565b601054610ce29161053b918111610ce85761053381601054613306565b5f610cbc565b8b60105561367e565b634e487b7160e01b88526011600452602488fd5b50835460801c1515610bab565b610b9a565b9250610d2283613bcb565b92610b80565b630f962f3d60e31b8452600484fd5b506303c267008311610b45565b819450610d5a906001600160401b039492613c0c565b9160058101805480155f14610e2a5750670de0b6b3a76400005b610d8863ffffffff61094b8842168861365e565b925460801c9081151580610e21575b610de1575b50505582821683198254161790556040519283521660208201527f6af432668cf14bfd72598906f14d15dd09552b679e589c26b753ff547bc5a60860403392a2610c1f565b670de0b6b3a764000091610df8610dfe9286613306565b90613313565b0480610e0b575b80610d9c565b806109c7610e1b926010546132f9565b5f610e05565b50808411610d97565b610d74565b5034610318578060031936011261031857546040516001600160a01b039091168152602090f35b5034610318578060031936011261031857610e6f6147ec565b80f35b5034610318578060031936011261031857610e8b613b6a565b610e93613bb0565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103185780600319360112610318576020600654604051908152f35b5034610318578060031936011261031857610f1b610f076131be565b604051918291602083526020830190613240565b0390f35b50346103185780600319360112610318576040517f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03168152602090f35b503461031857602036600319011261031857610f7e612fe7565b610f86613b6a565b6001600160a01b03168015610fce57601180546001600160a01b031916821790557f75fd3aa5d9b6e2a8a9d8894008c9263200713f4b1fa9113665e09ceac00277468280a280f35b631e4ec46b60e01b8252600482fd5b5034610318578060031936011261031857610ff6613b6a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346103185780600319360112610318576020600d54604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000115158152f35b5034610318576110a136613264565b6110ad93929193613b90565b6110b5613bb0565b8042116114d55782156114c6576110cb33614a64565b33855260056020526001600160801b0360408620541683116114b75784906110f16147ec565b6110f9613a59565b806111033361345c565b819291158015906114ae575b61149c575b5050507f000000000000000000000000000000000000000000000000000000000000000192835f1461148f579060406111f592965b60035482516001600160801b038916997f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03169591939192611191856130a7565b8452602084018b81528585019190915260608401918252608084019283528451630624e65f60e11b81528451600482015290516001600160801b031660248201526040909301516044840152516064830152516084820152928390819060a4820190565b038186855af19081156114845760409261125b928591869161145c575b506001600160801b0390816003549387519461122d866130c2565b85523060208601521686840152166060820152835194858094819363fc6f786560e01b8352600483016136bc565b03925af1918215611451577f5874005e1be6e01dcd0a13611d0d006a60f97986eaf93aa45b4e3c0e62f76a909386928794611413575b5061053b61139d916004546001600160801b036112b08a8284166136f9565b16906001600160801b031916176004553389526005602052604089206001600160801b036112e18a828454166136f9565b166001600160801b0319825416179055670de0b6b3a764000061130660065483613313565b04670de0b6b3a764000061131c60075484613313565b0490338b52600560205280600260408d200154115f146113fc57338b526005602052611350600260408d2001918254613306565b90555b338a52600560205280600360408c200154115f146113e557338a526005602052611385600360408c2001918254613306565b90555b60105481116105475761053381601054613306565b156113df575b6113ad8133614bf8565b6113b782336142ff565b604080516001600160801b0390951685526020850191909152830152339180606081016109ad565b906113a3565b503389526005602052886003604082200155611388565b50338a526005602052896002604082200155611353565b61139d91945061053b935061143f9060403d60401161144a575b61143781836130f9565b8101906136a6565b939093949150611291565b503d61142d565b6040513d87823e3d90fd5b6001600160801b03925061147d9150853d871161144a5761143781836130f9565b9091611212565b6040513d85823e3d90fd5b6111f59190604090611149565b6114a69233614cb5565b5f8181611114565b5080151561110f565b635bb93cd760e01b8552600485fd5b631f2a200560e01b8552600485fd5b631ab7da6b60e01b8552600485fd5b503461031857806003193601126103185760206040516113888152f35b50346103185760203660031901126103185761151b612fe7565b611523613b6a565b6001600160a01b03168015611580577f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff96020826bffffffffffffffffffffffff60a01b600a54161780600a5561ffff6040519160a01c168152a280f35b63466d7fef60e01b8252600482fd5b50346103185780600319360112610318576020604051620151808152f35b5034610318578060031936011261031857602060ff600254166040519015158152f35b5034610318578060031936011261031857610e6f33614a64565b5034610318576115f9366132d7565b9390611606939293613b90565b61160e613bb0565b84421161079f57811580156119bb575b6107885733865260056020526040862094855460801c156119ac576001600160401b0360018701541642101561199d576116f69160609161165d6147ec565b611665613a59565b611670853033613a0a565b61167b8630336139ae565b7f000000000000000000000000000000000000000000000000000000000000000196871561199457859287915b891561198e5790915b60035494604051956116c287613078565b86526020860152604085015284840152608083015260a08201526040518093819263219f5d1760e01b835260048301613600565b0381897f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af19384156119835786958780938197611914575b50925f5160206150cf5f395f51905f529592670de0b6b3a76400006118128a6005611863999661074c9c996004546001600160801b0361177a8682841661363e565b16906001600160801b03191617600455338152826020526117a6604082206108da86825460801c61363e565b856117be6001600160801b0360065496169586613313565b04338252836020526117d8600260408420019182546132f9565b905561180160036040886117ee60075489613313565b04933381528660205220019182546132f9565b9055015480610df857508290613313565b04806118fe575b50156118ef579061182d9196878592613306565b90806118df575b50806118cf575b50604080516001600160801b0387168152602081019590955284015233929081906060820190565b0390a233825260056020527f1632c94c5e5cdb04939cdca9be282eb8ebf86e6d8dacdbc60ce14b7fdf9f49696109ad6001600160401b036001604086200154166040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b6118d990336142ff565b5f61183b565b6118e99033614342565b5f611834565b929061182d9196878592613306565b806109c761190e926010546132f9565b5f611819565b6118639592985061074c9750600591945092670de0b6b3a76400006118126119625f5160206150cf5f395f51905f52999660603d60601161197c575b61195a81836130f9565b8101906135de565b9590919c8d92969c50969950505050509295939093611738565b503d611950565b6040513d88823e3d90fd5b916116b1565b869286916116a8565b6307b7d7dd60e51b8752600487fd5b631f2a200560e01b8752600487fd5b50821561161e565b5034610318578060031936011261031857602060405173cbb7c0000ab88b473b1f5afd9ef808440eed33bf8152f35b503461031857806003193601126103185760206040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b8152f35b503461031857611a3f366132a4565b611a4d959495929192613b90565b611a55613bb0565b824211611abe5784158015611ab6575b6119ac579261074c9261074c92611a986107529796338b5260056020526001600160401b03600160408d20015416613c0c565b92611aa4873033613a0a565b611aaf8930336139ae565b8887613db8565b508515611a65565b631ab7da6b60e01b8752600487fd5b503461031857806003193601126103185760206040517f00000000000000000000000000000000000000000000000000000000000d89a060020b8152f35b503461031857604036600319011261031857611b25612fe7565b611b2d612ffd565b90611b36613b6a565b6001600160801b0360045416611c05575b6040516370a0823160e01b8152306004820152906001600160a01b0316602082602481845afa918215611bfa578492611bbe575b5060405163a9059cbb60e01b60208201526001600160a01b03939093166024840152604480840192909252908252610e6f9190611bb96064836130f9565b615076565b9291506020833d602011611bf2575b81611bda602093836130f9565b81010312611bee5791519091611bb9611b7b565b5f80fd5b3d9150611bcd565b6040513d86823e3d90fd5b6001600160a01b0381166006602160991b018114159081611c90575b81611c71575b50611b475760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072657363756520636f726520746f6b656e73000000000000006044820152606490fd5b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf915014155f611c27565b7373326b4d0225c429bed050c11c4422d91470aaf48114159150611c21565b503461031857806003193601126103185760206040516303c267008152f35b503461031857806003193601126103185760206040516127108152f35b503461031857611cfa36613264565b611d0693929193613b90565b8042116114d55782156114c657611d1c33614a64565b33855260056020526001600160801b0360408620541683116114b7578490611d426147ec565b611d4a613a59565b7f000000000000000000000000000000000000000000000000000000000000000192831561148f579060406111f5929660035482516001600160801b038916997f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03169591939192611191856130a7565b50346103185780600319360112610318576020600e54604051908152f35b503461031857602036600319011261031857611dfa612fe7565b611e02613b6a565b6001600160a01b038116908115611ffd5760025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481876006602160991b015af18015611bfa57611fe0575b5060025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481877373326b4d0225c429bed050c11c4422d91470aaf45af18015611bfa57611fc3575b5060028054610100600160a81b03191660089290921b610100600160a81b031691909117905560405163095ea7b360e01b8152600481018290525f196024820152602081604481866006602160991b015af1801561148457611fa6575b5060405163095ea7b360e01b8152600481018290525f196024820152602081604481867373326b4d0225c429bed050c11c4422d91470aaf45af1801561148457611f79575b507f36db479a3b4d3672bd6f5fca4484283f60b5ac70647b1ceec13ecbb1d030a2df8280a280f35b611f9a9060203d602011611f9f575b611f9281836130f9565b81019061368e565b611f51565b503d611f88565b611fbe9060203d602011611f9f57611f9281836130f9565b611f0c565b611fdb9060203d602011611f9f57611f9281836130f9565b611eaf565b611ff89060203d602011611f9f57611f9281836130f9565b611e5a565b63466d7fef60e01b8352600483fd5b50346103185761201b366132a4565b94909391612027613b90565b61202f613bb0565b844211611abe5781158015612551575b6119ac573387526005602052604087205460801c156119ac57606061208a9133895260056020526120816001600160401b03600160408c200154169889613c0c565b9661165d6147ec565b03818a7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af19384156125465787968892899661251d575b50338952600560205260408920908789835460801c8c6121de63ffffffff61094b6005890196875480155f146125175750670de0b6b3a7640000945b6004546001600160801b0361211e8a82841661363e565b16906001600160801b0319161760045561215f61213f898d5460801c61363e565b8c546001600160801b031660809190911b6001600160801b031916178c55565b670de0b6b3a764000061217f6001600160801b036006549a16998a613313565b04338252600560205261219a600260408420019182546132f9565b90556121cc60036040670de0b6b3a76400006121b86007548d613313565b0493338152600560205220019182546132f9565b90556001600160401b0342169061365e565b94421061247357670de0b6b3a764000081118061246a575b61240e575b50801515806123fd575b612343575b50946122cf9460016109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d613313565b048061232d575b505501805467ffffffffffffffff19166001600160401b038b161790551561231e57906122999196878592613306565b908061230e575b50806122fe575b50604080516001600160801b038a168152602081019590955284015233929081906060820190565b0390a26040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b61230890336142ff565b5f6122a7565b6123189033614342565b5f6122a0565b92906122999196878592613306565b806109c761233d926010546132f9565b5f612269565b670de0b6b3a763ffff198401908482116123e9576109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98836123b46122cf9f9a60019a613313565b04806123d3575b50989d50505050959a50959a9c50959a50509461220a565b806109c76123e3926010546132f9565b5f6123bb565b634e487b7160e01b8e52601160045260248efd5b50670de0b6b3a76400008411612205565b670de0b6b3a763ffff1981019081116123e957612434670de0b6b3a76400009183613313565b048d81612442575b506121fb565b8161053b916124639360105410155f14610533575061053381601054613306565b5f8d61243c565b508115156121f6565b6109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98886001986122cf9f9a15158061250e575b6124dc575b5050613313565b8591610df86124eb9286613306565b04806124f8575b806124d5565b806109c7612508926010546132f9565b5f6124f2565b508084116124d0565b94612107565b919750945061253b915060603d60601161197c5761195a81836130f9565b91969091945f6120cb565b6040513d89823e3d90fd5b50821561203f565b5034610318578060031936011261031857612572613b6a565b60025460ff8116156125b25760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b638dfc202b60e01b8252600482fd5b5034610318578060031936011261031857602060405162278d008152f35b503461031857806003193601126103185760206040517373326b4d0225c429bed050c11c4422d91470aaf48152f35b5034610318576040366003190112610318576004356001600160401b0381116129745761263f903690600401613013565b91906024356001600160401b038111610a3957612660903690600401613013565b61266b949194613b6a565b8115801561296c575b61295d576001600160401b03821161286857612691600854613040565b601f81116128f5575b508184601f821160011461288757859161287c575b508260011b905f198460031b1c1916176008555b6001600160401b038111612868576126dc600954613040565b601f8111612800575b5083601f821160011461276c57948161275b9286977f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b9791612761575b508160011b905f198360031b1c1916176009555b61274d6040519586956040875260408701916135aa565b9184830360208601526135aa565b0390a180f35b90508201355f612722565b600985525f51602061510f5f395f51905f5290601f198316865b8181106127e8575091839161275b947f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b989994106127cf575b5050600181811b01600955612736565b8301355f19600384901b60f8161c191690555f806127bf565b9192602060018192868c013581550194019201612786565b60098552601f820160051c5f51602061510f5f395f51905f52019060208310612853575b601f0160051c5f51602061510f5f395f51905f5201905b81811061284857506126e5565b85815560010161283b565b5f51602061510f5f395f51905f529150612824565b634e487b7160e01b84526041600452602484fd5b90508301355f6126af565b600886525f5160206150ef5f395f51905f52915083601f198116875b8181106128da5750106128c1575b5050600182811b016008556126c3565b8401355f19600385901b60f8161c191690555f806128b1565b878401358555600190940193602093840193879350016128a3565b60088552601f830160051c5f5160206150ef5f395f51905f52019060208410612948575b601f0160051c5f5160206150ef5f395f51905f5201905b81811061293d575061269a565b858155600101612930565b5f5160206150ef5f395f51905f529150612919565b6320db826760e01b8452600484fd5b508015612674565b5080fd5b5034610318578060031936011261031857600b54600c54600d54600e54604080519485526020850193909352918301526060820152608090f35b50346103185780600319360112610318576020600b54604051908152f35b506129da36613264565b916129e3613b90565b6129eb613bb0565b8242116114d55734158015612ac6575b6114c657612a2d92859492612a2592612a1334613ca8565b612a1e8530336139ae565b84346143ce565b929034613306565b80612a4b575b50612a3e9250613306565b8061076857506001805580f35b6006602160991b013b15612ac157604051632e1a7d4d60e01b8152600481018290528481602481836006602160991b015af1801561145157612aac575b50838080612a3e96612aa594335af1612a9f61352c565b5061356a565b8392612a33565b612ab78580926130f9565b612ac1575f612a88565b505050fd5b5083156129fb565b5060a0366003190112611bee576004359060643560843563ffffffff81168103611bee57612afa613b90565b612b02613bb0565b814211612c145734158015612c0c575b612bfd57612b6991612b3f612b6192335f5260056020526001600160401b03600160405f20015416613c0c565b90612b4934613ca8565b612b548630336139ae565b6044356024358734613db8565b919034613306565b9283612b7b575b612a3e929350613306565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018590525f81602481836006602160991b015af18015612bf257612bd8575b50612bd083808080612a3e9798335af1612a9f61352c565b839250612b70565b612a3e93505f612be7916130f9565b612bd05f9350612bb8565b6040513d5f823e3d90fd5b631f2a200560e01b5f5260045ffd5b508315612b12565b631ab7da6b60e01b5f5260045ffd5b34611bee576020366003190112611bee576040612c46612c41612fe7565b61345c565b82519182526020820152f35b34611bee576020366003190112611bee5760a0612c6d612fe7565b600180831b0381165f526005602052612c8960405f209161345c565b906001600160401b036001845494015416604051936001600160801b038116855260801c6020850152604084015260608301526080820152f35b34611bee576020366003190112611bee5760043561ffff8116808203611bee5761138890612cef613b6a565b11612d5357600a805461ffff60a01b19811660a093841b61ffff60a01b1617918290556040519190921c61ffff1681526001600160a01b03909116907f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff990602090a2005b63466d7fef60e01b5f5260045ffd5b34611bee575f366003190112611bee576020600354604051908152f35b34611bee575f366003190112611bee57610f1b610f0761311a565b34611bee575f366003190112611bee5760206001600160801b0360045416604051908152f35b34611bee576080366003190112611bee57612dd9612fe7565b50612de2612ffd565b506064356001600160401b038111611bee57612e02903690600401613013565b5050604051630a85bd0160e11b8152602090f35b34611bee576020366003190112611bee576020612e39612e34612fe7565b613344565b604051908152f35b34611bee575f366003190112611bee5760406003546001600160801b036004541682519182526020820152f35b34611bee576040366003190112611bee5760115460043590602435906001600160a01b03163303612fd8578115612bfd576201518081108015612fcc575b612fbd57612ebb8230336139ae565b612ec3613a59565b612ecd81426132f9565b9082600d54428111612f88575b5050670de0b6b3a7640000830290838204670de0b6b3a76400001484151715612f7457612f0691613326565b600b54915f198314612f7457612f6f60017f966e984f09b14de9e3af2a3e121a423498feeec6949fc36854f284ea269214d494019485600b5542600c5582600d5583600e5560405193849342859094939260609260808301968352602083015260408201520152565b0390a2005b634e487b7160e01b5f52601160045260245ffd5b612fb5929450612fae612fa5670de0b6b3a7640000924290613306565b600e5490613313565b04906132f9565b918380612eda565b63e7726b7960e01b5f5260045ffd5b5062278d008111612eac565b631e4ec46b60e01b5f5260045ffd5b600435906001600160a01b0382168203611bee57565b602435906001600160a01b0382168203611bee57565b9181601f84011215611bee578235916001600160401b038311611bee5760208381860195010111611bee57565b90600182811c9216801561306e575b602083101461305a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161304f565b60c081019081106001600160401b0382111761309357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761309357604052565b608081019081106001600160401b0382111761309357604052565b61016081019081106001600160401b0382111761309357604052565b90601f801991011681019081106001600160401b0382111761309357604052565b604051905f826008549161312d83613040565b808352926001811690811561319f5750600114613153575b613151925003836130f9565b565b5060085f90815290915f5160206150ef5f395f51905f525b81831061318357505090602061315192820101613145565b602091935080600191548385890101520191019091849261316b565b6020925061315194915060ff191682840152151560051b820101613145565b604051905f82600954916131d183613040565b808352926001811690811561319f57506001146131f457613151925003836130f9565b5060095f90815290915f51602061510f5f395f51905f525b81831061322457505090602061315192820101613145565b602091935080600191548385890101520191019091849261320c565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6080906003190112611bee5760043590602435906044359060643590565b34611bee575f366003190112611bee576020604051670de0b6b3a76400008152f35b60c0906003190112611bee57600435906024359060443590606435906084359060a43563ffffffff81168103611bee5790565b60a0906003190112611bee576004359060243590604435906064359060843590565b91908201809211612f7457565b91908203918211612f7457565b81810292918115918404141715612f7457565b8115613330570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b0381165f9081526005602052604090209061336590613b01565b80156133b557600f5460105480151580613451575b80613446575b6133bb575b5061339b670de0b6b3a764000091600493613313565b04910154808210155f146133b5576133b291613306565b90565b50505f90565b91600d548042115f14613430576133e2612fa5670de0b6b3a7640000925b600c5490613306565b0490670de0b6b3a7640000820291808304670de0b6b3a76400001490151715612f7457613427670de0b6b3a76400009361342160049661339b95613326565b906132f9565b92509250613385565b50670de0b6b3a76400006133e2612fa5426133d9565b50600c541515613380565b50600c54421161337a565b60018060a01b03165f52600560205260405f2061348781546001600160801b038160801c91166132f9565b801561352457670de0b6b3a76400006134b1816134a660065485613313565b049260075490613313565b600284015491900491818110613519576003916134cd91613306565b925b0154808210613510576134e191613306565b905b7f00000000000000000000000000000000000000000000000000000000000000011561350c5791565b9091565b50505f906134e3565b505060035f926134cf565b50505f905f90565b3d15613565573d906001600160401b038211613093576040519161355a601f8201601f1916602001846130f9565b82523d5f602084013e565b606090565b1561357157565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b908060209392818452848401375f828201840152601f01601f1916010190565b51906001600160801b0382168203611bee57565b90816060910312611bee576135f2816135ca565b916040602083015192015190565b91909160a08060c083019480518452602081015160208501526040810151604085015260608101516060850152608081015160808501520151910152565b906001600160801b03809116911601906001600160801b038211612f7457565b906001600160401b03809116911603906001600160401b038211612f7457565b600160ff1b8114612f74575f0390565b90816020910312611bee57518015158103611bee5790565b9190826040910312611bee576020825192015190565b91909160606001600160801b038160808401958051855260018060a01b036020820151166020860152826040820151166040860152015116910152565b906001600160801b03809116911603906001600160801b038211612f7457565b909193924211612c145761372c33614a64565b6137346147ec565b61373c613a59565b61374533614ee5565b5061374f3361345c565b92909180151590816139a4575b5061398b57801515908161399a575b5061398b57801580613983575b61397957335f90815260056020526040902054670de0b6b3a7640000906137e3906137b190608081901c906001600160801b031661363e565b826137c96001600160801b0360065493169283613313565b04335f526005602052600260405f20015560075490613313565b04335f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000015f14613973575b5f938161395a575b8261393e575b841061392f57600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b906001600160a01b038116908115158061391f575b156139135761271061388561ffff61388d9360a01c1689613313565b048097613306565b9384918780613903575b5050816138f4575b6040518481528160208201527f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960403392a2604080519485526020850191909152830152606082018590523391608090a29190565b6138fe823361437b565b61389f565b61390c9161437b565b5f87613897565b5061388d5f8097613306565b5061ffff8160a01c161515613869565b638199f5f360e01b5f5260045ffd5b936139549061342161394e6131be565b85614f9d565b9361382a565b935061396d61396761311a565b82614f9d565b93613824565b9061381c565b505090505f905f90565b508115613778565b638d53e55360e01b5f5260045ffd5b905082105f61376b565b905082105f61375c565b6040516323b872dd60e01b60208201526001600160a01b03918216602482015291166044820152606480820192909252908152613151906139f06084826130f9565b7373326b4d0225c429bed050c11c4422d91470aaf4615076565b6040516323b872dd60e01b60208201526001600160a01b0391821660248201529116604482015260648082019290925290815261315190613a4c6084826130f9565b6006602160991b01615076565b600c548015613afe5780421115613afe57600d548042115f14613af357613a8290915b82613306565b80613a8b575050565b6010549081613a9c575b5050600c55565b613ab2670de0b6b3a764000091600e5490613313565b04670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612f7457613ae991613ae191613326565b600f546132f9565b600f555f80613a95565b50613a824291613a7c565b50565b60018060a01b03165f5260056020526133b260405f20600581015480155f14613b4f57506001600160801b03670de0b6b3a7640000613b4781935b54938460801c613313565b0491166132f9565b670de0b6b3a7640000613b476001600160801b039293613b3c565b5f546001600160a01b03163303613b7d57565b63118cdaa760e01b5f523360045260245ffd5b600260015414613ba1576002600155565b633ee5aeb560e01b5f5260045ffd5b60ff60025416613bbc57565b63d93c066560e01b5f5260045ffd5b6001600160401b036303c26700911601906001600160401b038211612f7457565b906001600160401b03809116911601906001600160401b038211612f7457565b9063ffffffff1680158015613c9b575b613c8c57613c346001600160401b0342169182613bec565b906001600160401b03613c4682613bcb565b166001600160401b03831611613c7c575b506001600160401b0381166001600160401b038316115f14613c77575090565b905090565b613c869150613bcb565b5f613c57565b630f962f3d60e31b5f5260045ffd5b506303c267008111613c1c565b80613cb05750565b6006602160991b013b15611bee57604051630d0e30db60e41b8152905f90829060049082906006602160991b015af18015612bf257613cec5750565b5f613151916130f9565b9190826080910312611bee57815191613d11602082016135ca565b916060604083015192015190565b9190916101408061016083019460018060a01b03815116845260018060a01b03602082015116602085015262ffffff6040820151166040850152606081015160020b6060850152608081015160020b608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015260018060a01b03610120820151166101208501520151910152565b929593909495613dc66147ec565b613dce613a59565b335f52600560205260405f2093845460801c926005860192835480155f146142f95750670de0b6b3a7640000955b600354806141e15750986080939291613f289a7f000000000000000000000000000000000000000000000000000000000000000194855f146141db5792905b85156141d55791925b85156141ba576006602160991b01955b156141ad577373326b4d0225c429bed050c11c4422d91470aaf45b60405196613e7c886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b60608701527f00000000000000000000000000000000000000000000000000000000000d89a060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180988192634418b22b60e11b835260048301613d1f565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1928315612bf2575f945f945f985f91614160575b509161408791670de0b6b3a7640000939996976003555b6004546001600160801b03613f998a82841661363e565b16906001600160801b031916176004556001815460801c91613fe86001600160801b03613fc9818d1680966132f9565b83546001600160801b0316911660801b6001600160801b031916178255565b016001600160401b038c166001600160401b03198254161790558361400f60065483613313565b04335f52600560205261402a600260405f20019182546132f9565b90558361403960075483613313565b04335f526005602052614054600360405f20019182546132f9565b905561407163ffffffff61094b426001600160401b03168e61365e565b9586928115158061250e576124dc575050613313565b048061414a575b50557f00000000000000000000000000000000000000000000000000000000000000011561411e576141197f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f643619194955b604080516001600160801b03861681526020810188905290810188905295969533905f5160206150cf5f395f51905f529080606081016122cf565b0390a2565b937f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f6436190614119906140de565b806109c761415a926010546132f9565b5f61408e565b90506140879850670de0b6b3a764000092965061419691955060803d6080116141a6575b61418e81836130f9565b810190613cf6565b9099929791969193509091613f6b565b503d614184565b6006602160991b01613e6f565b7373326b4d0225c429bed050c11c4422d91470aaf495613e54565b92613e44565b90613e3b565b92916060946142609b927f000000000000000000000000000000000000000000000000000000000000000191825f146142f35793915b156142ed5791925b6040519561422c87613078565b86526020860152604085015284840152608083015260a08201526040518098819263219f5d1760e01b835260048301613600565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1928315612bf2575f935f975f916142b8575b50670de0b6b3a76400009161408791989596613f82565b6140879850670de0b6b3a76400009295506142e2915060603d60601161197c5761195a81836130f9565b9098509094916142a1565b9261421f565b91614217565b95613dfc565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906139f081606481015b03601f1981018352826130f9565b60405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481019190915261315190613a4c8160648101614334565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906143b48160648101614334565b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf615076565b92909493916143db6147ec565b6143e3613a59565b600354806146f3575094608093929161450f967f000000000000000000000000000000000000000000000000000000000000000194855f146146ed5792905b85156146e75791925b85156146cc576006602160991b01955b156146bf577373326b4d0225c429bed050c11c4422d91470aaf45b60405196614463886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b60608701527f00000000000000000000000000000000000000000000000000000000000d89a060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180948192634418b22b60e11b835260048301613d1f565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f915f905f945f91614697575b509390926003555b6004546001600160801b036145718582841661363e565b16906001600160801b03191617600455335f52600560205260405f206001600160801b036145a2858284541661363e565b166001600160801b0319825416179055614630600654670de0b6b3a76400006145d56001600160801b0387169283613313565b04335f5260056020526145f0600260405f20019182546132f9565b9055670de0b6b3a764000061460760075483613313565b04335f526005602052614622600360405f20019182546132f9565b90556109c7816010546132f9565b7f0000000000000000000000000000000000000000000000000000000000000001156146915792915b604080516001600160801b03909216825260208201859052810183905233905f5160206150cf5f395f51905f52908060608101614119565b91614659565b92945050506146b5915060803d6080116141a65761418e81836130f9565b939190935f614552565b6006602160991b01614456565b7373326b4d0225c429bed050c11c4422d91470aaf49561443b565b9261442b565b90614422565b929160609461477297927f000000000000000000000000000000000000000000000000000000000000000191825f146147e65793915b156147e05791925b6040519561473e87613078565b86526020860152604085015284840152608083015260a08201526040518094819263219f5d1760e01b835260048301613600565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f915f935f916147ba575b509261455a565b919350506147d7915060603d60601161197c5761195a81836130f9565b9290925f6147b3565b92614731565b91614729565b6003548015801561494d575b613afe57604080516148479261480d826130c2565b81523060208201526001600160801b03828201526001600160801b03606082015281518093819263fc6f786560e01b8352600483016136bc565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f905f9261492b575b50801580156148e2575b50508015801561489e575050565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f74576148d56148dd916001600160801b036004541690613326565b6007546132f9565b600755565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f7457614919614921916001600160801b036004541690613326565b6006546132f9565b6006555f80614890565b9050614946915060403d60401161144a5761143781836130f9565b905f614886565b506001600160801b0360045416156147f8565b63ffffffff1680156149c4576303c267008110156149b75780673782dace9d9000000290673782dace9d900000820403612f74576303c267009004670de0b6b3a76400000180670de0b6b3a764000011612f745790565b50674563918244f4000090565b50670de0b6b3a764000090565b6001600160a01b03165f9081526005602052604081209190811315614a1a576004670de0b6b3a7640000614a0b614a1693600f5490613313565b0492019182546132f9565b9055565b5f8112614a25575050565b670de0b6b3a7640000614a45614a3c60049361367e565b600f5490613313565b04910190808254115f14614a5e57614a16908254613306565b505f9055565b6001600160a01b0381165f818152600560205260409020805491929160801c151580614be1575b614a9457505050565b614a9c613a59565b6005810191825480155f14614bdc5750670de0b6b3a76400005b670de0b6b3a76400008111614b39575b50506001600160801b03670de0b6b3a76400007f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369360018484614b13602097548060801c9788911661363e565b166001600160801b0316815501805467ffffffffffffffff1916905555604051908152a2565b825460801c93670de0b6b3a763ffff198201918211612f745760016020946001600160801b0394670de0b6b3a7640000614b9581967f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369a613313565b049081614ba9575b50509450509350614ac6565b601054614bcc92614bc6918111614bd35761053381601054613306565b906149d1565b5f80614b9d565b5f60105561367e565b614ab6565b506001600160401b03600182015416421015614a8b565b5f9082614c0457505050565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018490525f81602481836006602160991b015af18015612bf257614ca0575b5090918291829182916001600160a01b03165af1614c5d61352c565b5015614c6557565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b614cad9192505f906130f9565b5f905f614c41565b91909250821580614edd575b614ed857614cce82614ee5565b506001600160a01b0382165f81815260056020526040902054909390670de0b6b3a764000090614d4290614d1090608081901c906001600160801b031661363e565b82614d286001600160801b0360065493169283613313565b04875f526005602052600260405f20015560075490613313565b04845f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000015f14614ed2575b5f9281614ebf575b82614ea9575b83614d96575b5050505050565b600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b94614e609290916001600160a01b038116919082151580614e99575b15614e8d57612710614df261ffff614dfa9360a01c1686613313565b048094613306565b918380614e7d575b50508180614e6d575b5050867f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960408051878152886020820152a2604051948594859094939260609260808301968352602083015260408201520152565b0390a25f80808080614d8f565b614e769161437b565b5f81614e0b565b614e869161437b565b5f83614e02565b50614dfa5f8094613306565b5061ffff8160a01c161515614dd6565b92614eb99061342161394e6131be565b92614d89565b9250614ecc61396761311a565b92614d83565b90614d7b565b505050565b508015614cc1565b6001600160a01b0381165f8181526005602052604090209190614f0782613b01565b8015614f9457670de0b6b3a7640000614f25600492600f5490613313565b049301918254808510155f14614f8c57614f3f9085613306565b905b81614f4f5750505055600190565b81614f8091602094967f1ea97d707845967623ce740ccc94ddadf7dea761b865a44d0ff09da7404d066e96556142ff565b604051908152a2600190565b505f90614f41565b50505050600190565b600254604051926020929160081c6001600160a01b031690614fbe856130c2565b84526150065f848601953087526040810194855260608101968288526040519788968795869463b858183f60e01b86528a6004870152516080602487015260a4860190613240565b92516001600160a01b0316604485015251606484015251608483015203925af15f9181615042575b506133b25763081ceff360e41b5f5260045ffd5b9091506020813d60201161506e575b8161505e602093836130f9565b81010312611bee5751905f61502e565b3d9150615051565b905f602091828151910182855af115612bf2575f513d6150c557506001600160a01b0381163b155b6150a55750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b6001141561509e56fe2c057cf23622b35e0ed9529292a5f7e4cc561dc18ad64550b35e47ff7f287dcbf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee36e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7afa26469706673582212204d266e960103008a4a99e35fade2f03575378edf2c747c47539702a08a5eab1c64736f6c634300081c0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b770000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2
-----Decoded View---------------
Arg [0] : _positionManager (address): 0xCfB05AB06D338FD85BBF4486e69809D96A906b77
Arg [1] : _swapRouter (address): 0x6F887c0Bee01FAfacA39E46cA14cc1D48e28090F
Arg [2] : _treasuryAddress (address): 0x35160D6A914C553E627Fe81EA587791f5CBD4f03
Arg [3] : _receiver (address): 0xe1581C10EE235F0DEbb655EA365100bCBD84BAD2
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b77
Arg [1] : 0000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f
Arg [2] : 00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03
Arg [3] : 000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ 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.