UNPKG

@levxdao/ve

Version:

VE and Gauge Voting for NFTs

38 lines 121 kB
{ "language": "Solidity", "sources": { "contracts/BoostedVotingEscrowDelegate.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.14;\n\nimport \"./VotingEscrowDelegate.sol\";\n\ncontract BoostedVotingEscrowDelegate is VotingEscrowDelegate {\n uint256 public immutable minDuration;\n uint256 public immutable maxBoost;\n uint256 public immutable deadline;\n\n constructor(\n address _ve,\n address _token,\n address _discountToken,\n uint256 _minDuration,\n uint256 _maxBoost,\n uint256 _deadline\n ) VotingEscrowDelegate(_ve, _token, _discountToken) {\n minDuration = _minDuration;\n maxBoost = _maxBoost;\n deadline = _deadline;\n }\n\n function _createLock(\n uint256 amountToken,\n uint256 duration,\n bool discounted\n ) internal override {\n require(block.timestamp < deadline, \"BVED: EXPIRED\");\n require(duration >= minDuration, \"BVED: DURATION_TOO_SHORT\");\n\n super._createLock(amountToken, duration, discounted);\n }\n\n function _increaseAmount(uint256 amountToken, bool discounted) internal override {\n require(block.timestamp < deadline, \"BVED: EXPIRED\");\n\n super._increaseAmount(amountToken, discounted);\n }\n\n function _getAmounts(uint256 amount, uint256 duration)\n internal\n view\n override\n returns (uint256 amountVE, uint256 amountToken)\n {\n amountVE = (amount * maxBoost * duration) / _maxDuration;\n amountToken = amount;\n }\n}\n" }, "contracts/VotingEscrowDelegate.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.14;\n\nimport \"./interfaces/IVotingEscrowDelegate.sol\";\nimport \"./interfaces/IVotingEscrow.sol\";\nimport \"./interfaces/INFT.sol\";\n\nabstract contract VotingEscrowDelegate is IVotingEscrowDelegate {\n address public immutable ve;\n address public immutable token;\n address public immutable discountToken;\n\n uint256 internal immutable _maxDuration;\n uint256 internal immutable _interval;\n\n event CreateLock(address indexed account, uint256 amount, uint256 discount, uint256 indexed locktime);\n event IncreaseAmount(address indexed account, uint256 amount, uint256 discount);\n\n constructor(\n address _ve,\n address _token,\n address _discountToken\n ) {\n ve = _ve;\n token = _token;\n discountToken = _discountToken;\n\n _maxDuration = IVotingEscrow(_ve).maxDuration();\n _interval = IVotingEscrow(ve).interval();\n }\n\n modifier eligibleForDiscount {\n require(INFT(discountToken).balanceOf(msg.sender) > 0, \"VED: DISCOUNT_TOKEN_NOT_OWNED\");\n _;\n }\n\n function createLockDiscounted(uint256 amount, uint256 duration) external eligibleForDiscount {\n _createLock(amount, duration, true);\n }\n\n function createLock(uint256 amount, uint256 duration) external {\n _createLock(amount, duration, false);\n }\n\n function _createLock(\n uint256 amount,\n uint256 duration,\n bool discounted\n ) internal virtual {\n require(duration <= _maxDuration, \"VED: DURATION_TOO_LONG\");\n\n uint256 unlockTime = ((block.timestamp + duration) / _interval) * _interval; // rounded down to a multiple of interval\n uint256 _duration = unlockTime - block.timestamp;\n (uint256 amountVE, uint256 amountToken) = _getAmounts(amount, _duration);\n if (discounted) {\n amountVE = (amountVE * 100) / 90;\n }\n\n emit CreateLock(msg.sender, amountVE, amountVE - amountToken, unlockTime);\n IVotingEscrow(ve).createLockFor(msg.sender, amountVE, amountVE - amountToken, _duration);\n }\n\n function increaseAmountDiscounted(uint256 amount) external eligibleForDiscount {\n _increaseAmount(amount, true);\n }\n\n function increaseAmount(uint256 amount) external {\n _increaseAmount(amount, false);\n }\n\n function _increaseAmount(uint256 amount, bool discounted) internal virtual {\n uint256 unlockTime = IVotingEscrow(ve).unlockTime(msg.sender);\n require(unlockTime > 0, \"VED: LOCK_NOT_FOUND\");\n\n (uint256 amountVE, uint256 amountToken) = _getAmounts(amount, unlockTime - block.timestamp);\n if (discounted) {\n amountVE = (amountVE * 100) / 90;\n }\n\n emit IncreaseAmount(msg.sender, amountVE, amountVE - amountToken);\n IVotingEscrow(ve).increaseAmountFor(msg.sender, amountVE, amountVE - amountToken);\n }\n\n function _getAmounts(uint256 amount, uint256 duration)\n internal\n view\n virtual\n returns (uint256 amountVE, uint256 amountToken);\n\n function withdraw(address, uint256) external virtual override {\n // Empty\n }\n}\n" }, "contracts/interfaces/IVotingEscrowDelegate.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\ninterface IVotingEscrowDelegate {\n event Withdraw(address indexed addr, uint256 amount, uint256 penaltyRate);\n\n function withdraw(address addr, uint256 penaltyRate) external;\n}\n" }, "contracts/interfaces/IVotingEscrow.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\ninterface IVotingEscrow {\n event SetMigrator(address indexed account);\n event SetDelegate(address indexed account, bool isDelegate);\n event Deposit(\n address indexed provider,\n uint256 value,\n uint256 discount,\n uint256 indexed unlockTime,\n int128 indexed _type,\n uint256 ts\n );\n event Cancel(address indexed provider, uint256 value, uint256 discount, uint256 penaltyRate, uint256 ts);\n event Withdraw(address indexed provider, uint256 value, uint256 discount, uint256 ts);\n event Migrate(address indexed provider, uint256 value, uint256 discount, uint256 ts);\n event Supply(uint256 prevSupply, uint256 supply);\n\n function interval() external view returns (uint256);\n\n function maxDuration() external view returns (uint256);\n\n function token() external view returns (address);\n\n function name() external view returns (string memory);\n\n function symbol() external view returns (string memory);\n\n function decimals() external view returns (uint8);\n\n function migrator() external view returns (address);\n\n function isDelegate(address account) external view returns (bool);\n\n function supply() external view returns (uint256);\n\n function migrated(address account) external view returns (bool);\n\n function delegateAt(address account, uint256 index) external view returns (address);\n\n function locked(address account)\n external\n view\n returns (\n int128 amount,\n int128 discount,\n uint256 start,\n uint256 end\n );\n\n function epoch() external view returns (uint256);\n\n function pointHistory(uint256 epoch)\n external\n view\n returns (\n int128 bias,\n int128 slope,\n uint256 ts,\n uint256 blk\n );\n\n function userPointHistory(address account, uint256 epoch)\n external\n view\n returns (\n int128 bias,\n int128 slope,\n uint256 ts,\n uint256 blk\n );\n\n function userPointEpoch(address account) external view returns (uint256);\n\n function slopeChanges(uint256 epoch) external view returns (int128);\n\n function delegateLength(address addr) external view returns (uint256);\n\n function getLastUserSlope(address addr) external view returns (int128);\n\n function getCheckpointTime(address _addr, uint256 _idx) external view returns (uint256);\n\n function unlockTime(address _addr) external view returns (uint256);\n\n function setMigrator(address _migrator) external;\n\n function setDelegate(address account, bool _isDelegate) external;\n\n function checkpoint() external;\n\n function depositFor(address _addr, uint256 _value) external;\n\n function createLockFor(\n address _addr,\n uint256 _value,\n uint256 _discount,\n uint256 _duration\n ) external;\n\n function createLock(uint256 _value, uint256 _duration) external;\n\n function increaseAmountFor(\n address _addr,\n uint256 _value,\n uint256 _discount\n ) external;\n\n function increaseAmount(uint256 _value) external;\n\n function increaseUnlockTime(uint256 _duration) external;\n\n function cancel() external;\n\n function withdraw() external;\n\n function migrate() external;\n\n function balanceOf(address addr) external view returns (uint256);\n\n function balanceOf(address addr, uint256 _t) external view returns (uint256);\n\n function balanceOfAt(address addr, uint256 _block) external view returns (uint256);\n\n function totalSupply() external view returns (uint256);\n\n function totalSupply(uint256 t) external view returns (uint256);\n\n function totalSupplyAt(uint256 _block) external view returns (uint256);\n}\n" }, "contracts/interfaces/INFT.sol": { "content": "// SPDX-License-Identifier: WTFPL\npragma solidity ^0.8.0;\n\ninterface INFT {\n function balanceOf(address owner) external view returns (uint256 balance);\n\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n function mint(\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n\n function mintBatch(\n address to,\n uint256[] calldata tokenIds,\n bytes calldata data\n ) external;\n\n function burn(\n uint256 tokenId,\n uint256 label,\n bytes32 data\n ) external;\n}\n" }, "contracts/LPVotingEscrowDelegate.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.14;\n\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Pair.sol\";\nimport \"./VotingEscrowDelegate.sol\";\nimport \"./interfaces/IVotingEscrowMigrator.sol\";\n\ncontract LPVotingEscrowDelegate is VotingEscrowDelegate {\n using SafeERC20 for IERC20;\n\n struct LockedBalance {\n uint256 amount;\n uint256 end;\n }\n\n bool internal immutable isToken1;\n uint256 public immutable minAmount;\n uint256 public immutable maxBoost;\n\n uint256 public lockedTotal;\n mapping(address => uint256) public locked;\n\n constructor(\n address _ve,\n address _lpToken,\n address _discountToken,\n bool _isToken1,\n uint256 _minAmount,\n uint256 _maxBoost\n ) VotingEscrowDelegate(_ve, _lpToken, _discountToken) {\n isToken1 = _isToken1;\n minAmount = _minAmount;\n maxBoost = _maxBoost;\n }\n\n function _createLock(\n uint256 amount,\n uint256 duration,\n bool discounted\n ) internal override {\n require(amount >= minAmount, \"LSVED: AMOUNT_TOO_LOW\");\n\n super._createLock(amount, duration, discounted);\n\n lockedTotal += amount;\n locked[msg.sender] += amount;\n IERC20(token).safeTransferFrom(msg.sender, address(this), amount);\n }\n\n function _increaseAmount(uint256 amount, bool discounted) internal override {\n require(amount >= minAmount, \"LSVED: AMOUNT_TOO_LOW\");\n\n super._increaseAmount(amount, discounted);\n\n lockedTotal += amount;\n locked[msg.sender] += amount;\n IERC20(token).safeTransferFrom(msg.sender, address(this), amount);\n }\n\n function _getAmounts(uint256 amount, uint256)\n internal\n view\n override\n returns (uint256 amountVE, uint256 amountToken)\n {\n (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(token).getReserves();\n uint256 reserve = isToken1 ? uint256(reserve1) : uint256(reserve0);\n\n uint256 totalSupply = IUniswapV2Pair(token).totalSupply();\n uint256 _amountToken = (amount * reserve) / totalSupply;\n\n amountVE = _amountToken + (_amountToken * maxBoost * (totalSupply - lockedTotal)) / totalSupply / totalSupply;\n uint256 upperBound = (_amountToken * 333) / 10;\n if (amountVE > upperBound) {\n amountVE = upperBound;\n }\n amountToken = 0;\n }\n\n function withdraw(address addr, uint256 penaltyRate) external override {\n require(msg.sender == ve, \"LSVED: FORBIDDEN\");\n\n uint256 amount = locked[addr];\n require(amount > 0, \"LSVED: LOCK_NOT_FOUND\");\n\n lockedTotal -= amount;\n locked[addr] = 0;\n IERC20(token).safeTransfer(addr, (amount * (1e18 - penaltyRate)) / 1e18);\n\n emit Withdraw(addr, amount, penaltyRate);\n }\n}\n" }, "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" }, "contracts/uniswapv2/interfaces/IUniswapV2Pair.sol": { "content": "pragma solidity >=0.5.0;\n\ninterface IUniswapV2Pair {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n function MINIMUM_LIQUIDITY() external pure returns (uint);\n function factory() external view returns (address);\n function token0() external view returns (address);\n function token1() external view returns (address);\n function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n function price0CumulativeLast() external view returns (uint);\n function price1CumulativeLast() external view returns (uint);\n function kLast() external view returns (uint);\n\n function mint(address to) external returns (uint liquidity);\n function burn(address to) external returns (uint amount0, uint amount1);\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\n function skim(address to) external;\n function sync() external;\n\n function initialize(address, address) external;\n}" }, "contracts/interfaces/IVotingEscrowMigrator.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\ninterface IVotingEscrowMigrator {\n function migrate(\n address account,\n int128 amount,\n int128 discount,\n uint256 start,\n uint256 end,\n address[] calldata delegates\n ) external;\n}\n" }, "@openzeppelin/contracts/token/ERC20/IERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n" }, "@openzeppelin/contracts/utils/Address.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" }, "contracts/VotingEscrow.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.14;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport \"./interfaces/IVotingEscrow.sol\";\nimport \"./interfaces/IVotingEscrowMigrator.sol\";\nimport \"./interfaces/IVotingEscrowDelegate.sol\";\nimport \"./libraries/Integers.sol\";\n\n/**\n * @title Voting Escrow\n * @author LevX (team@levx.io)\n * @notice Votes have a weight depending on time, so that users are\n * committed to the future of (whatever they are voting for)\n * @dev Vote weight decays linearly over time. Lock time cannot be\n * more than `MAXTIME`.\n * @dev Ported from vyper (https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy)\n */\n\n// Voting escrow to have time-weighted votes\n// Votes have a weight depending on time, so that users are committed\n// to the future of (whatever they are voting for).\n// The weight in this implementation is linear, and lock cannot be more than maxtime:\n// w ^\n// 1 + /\n// | /\n// | /\n// | /\n// |/\n// 0 +--------+------> time\n// maxtime\n\ncontract VotingEscrow is Ownable, ReentrancyGuard, IVotingEscrow {\n using SafeERC20 for IERC20;\n using Integers for int128;\n using Integers for uint256;\n\n struct Point {\n int128 bias;\n int128 slope; // - dweight / dt\n uint256 ts;\n uint256 blk; // block\n }\n\n struct LockedBalance {\n int128 amount;\n int128 discount;\n uint256 start;\n uint256 end;\n }\n\n int128 public constant DEPOSIT_FOR_TYPE = 0;\n int128 public constant CRETE_LOCK_TYPE = 1;\n int128 public constant INCREASE_LOCK_AMOUNT = 2;\n int128 public constant INCREASE_UNLOCK_TIME = 3;\n uint256 internal constant MULTIPLIER = 1e18;\n\n uint256 public immutable override interval;\n uint256 public immutable override maxDuration;\n address public immutable override token;\n string public override name;\n string public override symbol;\n uint8 public immutable override decimals;\n\n address public override migrator;\n mapping(address => bool) public override isDelegate;\n\n uint256 public override supply;\n mapping(address => bool) public override migrated;\n mapping(address => address[]) public override delegateAt;\n mapping(address => LockedBalance) public override locked;\n uint256 public override epoch;\n\n mapping(uint256 => Point) public override pointHistory; // epoch -> unsigned point\n mapping(address => mapping(uint256 => Point)) public override userPointHistory; // user -> Point[user_epoch]\n mapping(address => uint256) public override userPointEpoch;\n mapping(uint256 => int128) public override slopeChanges; // time -> signed slope change\n\n constructor(\n address _token,\n string memory _name,\n string memory _symbol,\n uint256 _interval,\n uint256 _maxDuration\n ) {\n token = _token;\n name = _name;\n symbol = _symbol;\n decimals = IERC20Metadata(_token).decimals();\n\n interval = _interval;\n maxDuration = (_maxDuration / _interval) * _interval; // rounded down to a multiple of interval\n\n pointHistory[0].blk = block.number;\n pointHistory[0].ts = block.timestamp;\n }\n\n modifier beforeMigrated(address addr) {\n require(!migrated[addr], \"VE: LOCK_MIGRATED\");\n _;\n }\n\n modifier onlyDelegate {\n require(isDelegate[msg.sender], \"VE: NOT_DELEGATE\");\n _;\n }\n\n /**\n * @notice Check if the call is from an EOA or a whitelisted smart contract, revert if not\n */\n modifier authorized {\n if (msg.sender != tx.origin) {\n require(isDelegate[msg.sender], \"VE: CONTRACT_NOT_DELEGATE\");\n }\n _;\n }\n\n function delegateLength(address addr) external view returns (uint256) {\n return delegateAt[addr].length;\n }\n\n /**\n * @notice Get the most recently recorded rate of voting power decrease for `addr`\n * @param addr Address of the user wallet\n * @return Value of the slope\n */\n function getLastUserSlope(address addr) external view override returns (int128) {\n uint256 uepoch = userPointEpoch[addr];\n return userPointHistory[addr][uepoch].slope;\n }\n\n /**\n * @notice Get the timestamp for checkpoint `_idx` for `_addr`\n * @param _addr User wallet address\n * @param _idx User epoch number\n * @return Epoch time of the checkpoint\n */\n function getCheckpointTime(address _addr, uint256 _idx) external view override returns (uint256) {\n return userPointHistory[_addr][_idx].ts;\n }\n\n /**\n * @notice Get timestamp when `_addr`'s lock finishes\n * @param _addr User wallet\n * @return Epoch time of the lock end\n */\n function unlockTime(address _addr) external view override returns (uint256) {\n return locked[_addr].end;\n }\n\n function setMigrator(address _migrator) external override onlyOwner {\n require(migrator == address(0), \"VE: MIGRATOR_SET\");\n\n migrator = _migrator;\n\n emit SetMigrator(_migrator);\n }\n\n function setDelegate(address account, bool _isDelegate) external override onlyOwner {\n isDelegate[account] = _isDelegate;\n\n emit SetDelegate(account, _isDelegate);\n }\n\n /**\n * @notice Record global and per-user data to checkpoint\n * @param addr User's wallet address. No user checkpoint if 0x0\n * @param old_locked Pevious locked amount / end lock time for the user\n * @param new_locked New locked amount / end lock time for the user\n */\n function _checkpoint(\n address addr,\n LockedBalance memory old_locked,\n LockedBalance memory new_locked\n ) internal {\n Point memory u_old;\n Point memory u_new;\n int128 old_dslope;\n int128 new_dslope;\n uint256 _epoch = epoch;\n\n if (addr != address(0)) {\n // Calculate slopes and biases\n // Kept at zero when they have to\n if (old_locked.end > block.timestamp && old_locked.amount > 0) {\n u_old.slope = old_locked.amount / maxDuration.toInt128();\n u_old.bias = u_old.slope * (old_locked.end - block.timestamp).toInt128();\n }\n if (new_locked.end > block.timestamp && new_locked.amount > 0) {\n u_new.slope = new_locked.amount / maxDuration.toInt128();\n u_new.bias = u_new.slope * (new_locked.end - block.timestamp).toInt128();\n }\n\n // Read values of scheduled changes in the slope\n // old_locked.end can be in the past and in the future\n // new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros\n old_dslope = slopeChanges[old_locked.end];\n if (new_locked.end != 0) {\n if (new_locked.end == old_locked.end) new_dslope = old_dslope;\n else new_dslope = slopeChanges[new_locked.end];\n }\n }\n\n Point memory last_point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});\n if (_epoch > 0) last_point = pointHistory[_epoch];\n uint256 last_checkpoint = last_point.ts;\n // initial_last_point is used for extrapolation to calculate block number\n // (approximately, for *At methods) and save them\n // as we cannot figure that out exactly from inside the contract\n Point memory initial_last_point = Point(last_point.bias, last_point.slope, last_point.ts, last_point.blk);\n uint256 block_slope; // dblock/dt\n if (block.timestamp > last_point.ts)\n block_slope = (MULTIPLIER * (block.number - last_point.blk)) / (block.timestamp - last_point.ts);\n // If last point is already recorded in this block, slope=0\n // But that's ok b/c we know the block in such case\n\n {\n // Go over weeks to fill history and calculate what the current point is\n uint256 t_i = (last_checkpoint / interval) * interval;\n for (uint256 i; i < 255; i++) {\n // Hopefully it won't happen that this won't get used in 5 years!\n // If it does, users will be able to withdraw but vote weight will be broken\n t_i += interval;\n int128 d_slope;\n if (t_i > block.timestamp) t_i = block.timestamp;\n else d_slope = slopeChanges[t_i];\n last_point.bias -= last_point.slope * (t_i - last_checkpoint).toInt128();\n last_point.slope += d_slope;\n if (last_point.bias < 0)\n // This can happen\n last_point.bias = 0;\n if (last_point.slope < 0)\n // This cannot happen - just in case\n last_point.slope = 0;\n last_checkpoint = t_i;\n last_point.ts = t_i;\n last_point.blk = initial_last_point.blk + (block_slope * (t_i - initial_last_point.ts)) / MULTIPLIER;\n _epoch += 1;\n if (t_i == block.timestamp) {\n last_point.blk = block.number;\n break;\n } else pointHistory[_epoch] = last_point;\n }\n }\n\n epoch = _epoch;\n // Now point_history is filled until t=now\n\n if (addr != address(0)) {\n // If last point was in this block, the slope change has been applied already\n // But in such case we have 0 slope(s)\n last_point.slope += (u_new.slope - u_old.slope);\n last_point.bias += (u_new.bias - u_old.bias);\n if (last_point.slope < 0) last_point.slope = 0;\n if (last_point.bias < 0) last_point.bias = 0;\n }\n\n // Record the changed point into history\n pointHistory[_epoch] = last_point;\n\n if (addr != address(0)) {\n // Schedule the slope changes (slope is going down)\n // We subtract new_user_slope from [new_locked.end]\n // and add old_user_slope to [old_locked.end]\n if (old_locked.end > block.timestamp) {\n // old_dslope was <something> - u_old.slope, so we cancel that\n old_dslope += u_old.slope;\n if (new_locked.end == old_locked.end) old_dslope -= u_new.slope; // It was a new deposit, not extension\n slopeChanges[old_locked.end] = old_dslope;\n }\n\n if (new_locked.end > block.timestamp) {\n if (new_locked.end > old_locked.end) {\n new_dslope -= u_new.slope; // old slope disappeared at this point\n slopeChanges[new_locked.end] = new_dslope;\n }\n // else: we recorded it already in old_dslope\n }\n\n // Now handle user history\n uint256 user_epoch = userPointEpoch[addr] + 1;\n\n userPointEpoch[addr] = user_epoch;\n u_new.ts = block.timestamp;\n u_new.blk = block.number;\n userPointHistory[addr][user_epoch] = u_new;\n }\n }\n\n /**\n * @notice Deposit and lock tokens for a user\n * @param _addr User's wallet address\n * @param _value Amount to deposit\n * @param _discount Amount to get discounted out of _value\n * @param unlock_time New time when to unlock the tokens, or 0 if unchanged\n * @param locked_balance Previous locked amount / timestamp\n */\n function _depositFor(\n address _addr,\n uint256 _value,\n uint256 _discount,\n uint256 unlock_time,\n LockedBalance memory locked_balance,\n int128 _type\n ) internal {\n LockedBalance memory _locked = locked_balance;\n uint256 supply_before = supply;\n\n supply = supply_before + _value;\n LockedBalance memory old_locked;\n (old_locked.amount, old_locked.discount, old_locked.start, old_locked.end) = (\n _locked.amount,\n _locked.discount,\n _locked.start,\n _locked.end\n );\n // Adding to existing lock, or if a lock is expired - creating a new one\n _locked.amount += (_value).toInt128();\n if (_discount != 0) _locked.discount += _discount.toInt128();\n if (unlock_time != 0) {\n if (_locked.start == 0) _locked.start = block.timestamp;\n _locked.end = unlock_time;\n }\n locked[_addr] = _locked;\n\n // Possibilities:\n // Both old_locked.end could be current or expired (>/< block.timestamp)\n // value == 0 (extend lock) or value > 0 (add to lock or extend lock)\n // _locked.end > block.timestamp (always)\n _checkpoint(_addr, old_locked, _locked);\n\n if (_value > _discount) {\n IERC20(token).safeTransferFrom(_addr, address(this), _value - _discount);\n }\n\n emit Deposit(_addr, _value, _discount, _locked.end, _type, block.timestamp);\n emit Supply(supply_before, supply_before + _value);\n }\n\n function _pushDelegate(address addr, address delegate) internal {\n bool found;\n address[] storage delegates = delegateAt[addr];\n for (uint256 i; i < delegates.length; ) {\n if (delegates[i] == delegate) found = true;\n unchecked {\n ++i;\n }\n }\n if (!found) delegateAt[addr].push(delegate);\n }\n\n /**\n * @notice Record global data to checkpoint\n */\n function checkpoint() external override {\n _checkpoint(address(0), LockedBalance(0, 0, 0, 0), LockedBalance(0, 0, 0, 0));\n }\n\n /**\n * @notice Deposit `_value` tokens for `_addr` and add to the lock\n * @dev Anyone (even a smart contract) can deposit for someone else, but\n * cannot extend their locktime and deposit for a brand new user\n * @param _addr User's wallet address\n * @param _value Amount to add to user's lock\n */\n function depositFor(address _addr, uint256 _value) external override nonReentrant beforeMigrated(_addr) {\n LockedBalance memory _locked = locked[_addr];\n\n require(_value > 0, \"VE: INVALID_VALUE\");\n require(_locked.amount > 0, \"VE: LOCK_NOT_FOUND\");\n require(_locked.end > block.timestamp, \"VE: LOCK_EXPIRED\");\n\n _depositFor(_addr, _value, 0, 0, _locked, DEPOSIT_FOR_TYPE);\n }\n\n /**\n * @notice Deposit `_value` tokens with `_discount` for `_addr` and lock for `_duration`\n * @dev Only delegates can creat a lock for someone else\n * @param _addr User's wallet address\n * @param _value Amount to add to user's lock\n * @param _discount Amount to get discounted out of _value\n * @param _duration Epoch time until tokens unlock from now\n */\n function createLockFor(\n address _addr,\n uint256 _value,\n uint256 _discount,\n uint256 _duration\n ) external override nonReentrant onlyDelegate beforeMigrated(_addr) {\n _pushDelegate(_addr, msg.sender);\n\n uint256 unlock_time = ((block.timestamp + _duration) / interval) * interval; // Locktime is rounded down to a multiple of interval\n LockedBalance memory _locked = locked[_addr];\n\n require(_value > 0, \"VE: INVALID_VALUE\");\n require(_value >= _discount, \"VE: DISCOUNT_TOO_HIGH\");\n require(_locked.amount == 0, \"VE: EXISTING_LOCK_FOUND\");\n require(unlock_time > block.timestamp, \"VE: UNLOCK_TIME_TOO_EARLY\");\n require(unlock_time <= block.timestamp + maxDuration, \"VE: UNLOCK_TIME_TOO_LATE\");\n\n _depositFor(_addr, _value, _discount, unlock_time, _locked, CRETE_LOCK_TYPE);\n }\n\n /**\n * @notice Deposit `_value` tokens for `msg.sender` and lock for `_duration`\n * @param _value Amount to deposit\n * @param _duration Epoch time until tokens unlock from now\n */\n function createLock(uint256 _value, uint256 _duration)\n external\n override\n nonReentrant\n authorized\n beforeMigrated(msg.sender)\n {\n uint256 unlock_time = ((block.timestamp + _duration) / interval) * interval; // Locktime is rounded down to a multiple of interval\n LockedBalance memory _locked = locked[msg.sender];\n\n require(_value > 0, \"VE: INVALID_VALUE\");\n require(_locked.amount == 0, \"VE: EXISTING_LOCK_FOUND\");\n require(unlock_time > block.timestamp, \"VE: UNLOCK_TIME_TOO_EARLY\");\n require(unlock_time <= block.timestamp + maxDuration, \"VE: UNLOCK_TIME_TOO_LATE\");\n\n _depositFor(msg.sender, _value, 0, unlock_time, _locked, CRETE_LOCK_TYPE);\n }\n\n /**\n * @notice Deposit `_value` additional tokens for `msg.sender`\n * without modifying the unlock time\n * @param _addr User's wallet address\n * @param _value Amount of tokens to deposit and add to the lock\n * @param _discount Amount to get discounted out of _value\n */\n function increaseAmountFor(\n address _addr,\n uint256 _value,\n uint256 _discount\n ) external override nonReentrant onlyDelegate beforeMigrated(_addr) {\n _pushDelegate(_addr, msg.sender);\n\n LockedBalance memory _locked = locked[_addr];\n\n require(_value > 0, \"VE: INVALID_VALUE\");\n require(_value >= _discount, \"VE: DISCOUNT_TOO_HIGH\");\n require(_locked.amount > 0, \"VE: LOCK_NOT_FOUND\");\n require(_locked.end > block.timestamp, \"VE: LOCK_EXPIRED\");\n\n _depositFor(_addr, _value, _discount, 0, _locked, INCREASE_LOCK_AMOUNT);\n }\n\n /**\n * @notice Deposit `_value` additional tokens for `msg.sender`\n * without modifying the unlock time\n *