编译器
0.8.26+commit.8a97fa7a
文件 1 的 11:Address.sol
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
library Address {
error AddressEmptyCode(address target);
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert Errors.FailedCall();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
文件 2 的 11:CheckpointsLib.sol
pragma solidity ^0.8.0;
import "./VoteEscrowLib.sol";
import "./WeekMath.sol";
struct Checkpoint {
uint128 timestamp;
VeBalance value;
}
library Checkpoints {
struct History {
Checkpoint[] _checkpoints;
}
function length(History storage self) internal view returns (uint256) {
return self._checkpoints.length;
}
function get(History storage self, uint256 index) internal view returns (Checkpoint memory) {
return self._checkpoints[index];
}
function push(History storage self, VeBalance memory value) internal {
uint256 pos = self._checkpoints.length;
if (pos > 0 && self._checkpoints[pos - 1].timestamp == WeekMath.getCurrentWeekStart()) {
self._checkpoints[pos - 1].value = value;
} else {
self._checkpoints.push(Checkpoint({timestamp: WeekMath.getCurrentWeekStart(), value: value}));
}
}
}
文件 3 的 11:Errors.sol
pragma solidity ^0.8.20;
library Errors {
error InsufficientBalance(uint256 balance, uint256 needed);
error FailedCall();
error FailedDeployment();
}
文件 4 的 11:IERC1363.sol
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
interface IERC1363 is IERC20, IERC165 {
function transferAndCall(address to, uint256 value) external returns (bool);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
function approveAndCall(address spender, uint256 value) external returns (bool);
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
文件 5 的 11:IERC165.sol
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
文件 6 的 11:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 7 的 11:IVoteEscrow.sol
pragma solidity ^0.8.0;
import "../libraries/VoteEscrowLib.sol";
import "../libraries/CheckpointsLib.sol";
interface IVoteEscrow {
event UpdateLock(address indexed user, uint128 amount, uint128 expiry);
event Withdraw(address indexed user, uint128 amount);
function updateLock(uint128 additionalAmountToLock, uint128 expiry) external returns (uint128);
function withdraw() external returns (uint128);
function totalSupplyAt(uint128 timestamp) external view returns (uint128);
function balanceOf(address user) external view returns (uint128);
function positionData(address user) external view returns (uint128 amount, uint128 expiry);
function totalSupplyStored() external view returns (uint128);
function totalSupplyCurrent() external returns (uint128);
function totalSupplyAndBalanceCurrent(address user) external returns (uint128, uint128);
function getUserHistoryLength(address user) external view returns (uint256);
function getUserHistoryAt(address user, uint256 index) external view returns (Checkpoint memory);
}
文件 8 的 11:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
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);
}
}
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);
}
}
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));
}
}
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));
}
}
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));
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
文件 9 的 11:VoteEscrow.sol
pragma solidity ^0.8.17;
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IVoteEscrow.sol";
import "./libraries/VoteEscrowLib.sol";
import "./libraries/WeekMath.sol";
import "./libraries/CheckpointsLib.sol";
contract VoteEscrow is IVoteEscrow {
using SafeERC20 for IERC20;
using VoteEscrowLib for VeBalance;
using VoteEscrowLib for LockedPosition;
using Checkpoints for Checkpoints.History;
IERC20 public immutable FYDE;
uint128 public constant WEEK = 1 weeks;
uint128 public constant MAX_LOCK_TIME = 104 weeks;
uint128 public constant MIN_LOCK_TIME = 1 weeks;
VeBalance internal _totalSupply;
mapping(address => LockedPosition) public positionData;
uint128 public lastSlopeChangeAppliedAt;
mapping(uint128 => uint128) public slopeChanges;
mapping(uint128 => uint128) public totalSupplyAt;
mapping(address => Checkpoints.History) internal userHistory;
constructor(address _FYDE) {
FYDE = IERC20(_FYDE);
lastSlopeChangeAppliedAt = WeekMath.getCurrentWeekStart();
}
error WeekMathInvalidTime(uint256 wTime);
error UpdateExpiryMustBeCurrent();
error UpdateExpiryMustIncrease();
error UpdateExpiryTooLong();
error UpdateExpiryTooShort();
error VoteEscrowZeroAmount();
error VoteEscrowNotExpired();
function updateLock(uint128 additionalAmountToLock, uint128 newExpiry)
public
returns (uint128 newVeBalance)
{
address user = msg.sender;
if (!WeekMath.isValidWTime(newExpiry)) revert WeekMathInvalidTime(newExpiry);
if (WeekMath.isCurrentlyExpired(newExpiry)) revert UpdateExpiryMustBeCurrent();
if (newExpiry < positionData[user].expiry) revert UpdateExpiryMustIncrease();
if (newExpiry > block.timestamp + MAX_LOCK_TIME) revert UpdateExpiryTooLong();
if (newExpiry < block.timestamp + MIN_LOCK_TIME) revert UpdateExpiryTooShort();
uint128 newTotalAmountLocked = additionalAmountToLock + positionData[user].amount;
if (newTotalAmountLocked == 0) revert VoteEscrowZeroAmount();
uint128 additionalDurationToLock = newExpiry - positionData[user].expiry;
if (additionalAmountToLock > 0) {
FYDE.safeTransferFrom(user, address(this), additionalAmountToLock);
}
newVeBalance = _increasePosition(user, additionalAmountToLock, additionalDurationToLock);
emit UpdateLock(user, newTotalAmountLocked, newExpiry);
}
function withdraw() external returns (uint128 amount) {
address user = msg.sender;
if (!_isPositionExpired(user)) revert VoteEscrowNotExpired();
amount = positionData[user].amount;
if (amount == 0) revert VoteEscrowZeroAmount();
delete positionData[user];
FYDE.safeTransfer(user, amount);
emit Withdraw(user, amount);
}
function balanceOf(address user) public view returns (uint128) {
return positionData[user].convertToVeBalance().getCurrentValue();
}
function totalSupplyStored() external view returns (uint128) {
return _totalSupply.getCurrentValue();
}
function totalSupplyCurrent() public returns (uint128) {
(VeBalance memory supply,) = _applySlopeChange();
return supply.getCurrentValue();
}
function totalSupplyAndBalanceCurrent(address user) external returns (uint128, uint128) {
return (totalSupplyCurrent(), balanceOf(user));
}
function getUserHistoryLength(address user) external view returns (uint256) {
return userHistory[user].length();
}
function getUserHistoryAt(address user, uint256 index) external view returns (Checkpoint memory) {
return userHistory[user].get(index);
}
function _increasePosition(address user, uint128 amountToIncrease, uint128 durationToIncrease)
internal
returns (uint128)
{
LockedPosition memory oldPosition = positionData[user];
(VeBalance memory newSupply,) = _applySlopeChange();
if (!WeekMath.isCurrentlyExpired(oldPosition.expiry)) {
VeBalance memory oldBalance = oldPosition.convertToVeBalance();
newSupply = newSupply.sub(oldBalance);
slopeChanges[oldPosition.expiry] -= oldBalance.slope;
}
LockedPosition memory newPosition =
LockedPosition(oldPosition.amount + amountToIncrease, oldPosition.expiry + durationToIncrease);
VeBalance memory newBalance = newPosition.convertToVeBalance();
newSupply = newSupply.add(newBalance);
slopeChanges[newPosition.expiry] += newBalance.slope;
_totalSupply = newSupply;
positionData[user] = newPosition;
userHistory[user].push(newBalance);
return newBalance.getCurrentValue();
}
function _applySlopeChange() internal returns (VeBalance memory, uint128) {
VeBalance memory supply = _totalSupply;
uint128 wTime = lastSlopeChangeAppliedAt;
uint128 currentWeekStart = WeekMath.getCurrentWeekStart();
if (wTime >= currentWeekStart) return (supply, wTime);
while (wTime < currentWeekStart) {
wTime += WEEK;
supply = supply.sub(slopeChanges[wTime], wTime);
totalSupplyAt[wTime] = supply.getValueAt(wTime);
}
_totalSupply = supply;
lastSlopeChangeAppliedAt = wTime;
return (supply, wTime);
}
function _isPositionExpired(address user) internal view returns (bool) {
return WeekMath.isCurrentlyExpired(positionData[user].expiry);
}
}
文件 10 的 11:VoteEscrowLib.sol
pragma solidity ^0.8.19;
struct VeBalance {
uint128 bias;
uint128 slope;
}
struct LockedPosition {
uint128 amount;
uint128 expiry;
}
error ZeroSlope(uint128 bias, uint128 slope);
library VoteEscrowLib {
uint128 internal constant MAX_LOCK_TIME = 104 weeks;
function add(VeBalance memory a, VeBalance memory b) internal pure returns (VeBalance memory res) {
res.bias = a.bias + b.bias;
res.slope = a.slope + b.slope;
}
function sub(VeBalance memory a, VeBalance memory b) internal pure returns (VeBalance memory res) {
res.bias = a.bias - b.bias;
res.slope = a.slope - b.slope;
}
function sub(VeBalance memory a, uint128 slope, uint128 expiry)
internal
pure
returns (VeBalance memory res)
{
res.slope = a.slope - slope;
res.bias = a.bias - slope * expiry;
}
function isExpired(VeBalance memory a) internal view returns (bool) {
return a.slope * uint128(block.timestamp) >= a.bias;
}
function getCurrentValue(VeBalance memory a) internal view returns (uint128) {
if (isExpired(a)) return 0;
return getValueAt(a, uint128(block.timestamp));
}
function getValueAt(VeBalance memory a, uint128 t) internal pure returns (uint128) {
if (a.slope * t > a.bias) return 0;
return a.bias - a.slope * t;
}
function getExpiry(VeBalance memory a) internal pure returns (uint128) {
if (a.slope == 0) revert ZeroSlope(a.bias, a.slope);
return a.bias / a.slope;
}
function convertToVeBalance(LockedPosition memory position)
internal
pure
returns (VeBalance memory res)
{
res.slope = position.amount / MAX_LOCK_TIME;
res.bias = res.slope * position.expiry;
}
}
文件 11 的 11:WeekMath.sol
pragma solidity ^0.8.20;
library WeekMath {
uint128 internal constant WEEK = 7 days;
function getWeekStartTimestamp(uint128 timestamp) internal pure returns (uint128) {
return (timestamp / WEEK) * WEEK;
}
function getCurrentWeekStart() internal view returns (uint128) {
return getWeekStartTimestamp(uint128(block.timestamp));
}
function isValidWTime(uint256 time) internal pure returns (bool) {
return time % WEEK == 0;
}
function isCurrentlyExpired(uint256 expiry) internal view returns (bool) {
return (expiry <= block.timestamp);
}
}
{
"compilationTarget": {
"src/VoteEscrow.sol": "VoteEscrow"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":ds-test/=lib/solmate/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
":murky/=lib/murky/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solidity-stringutils/=lib/surl/lib/solidity-stringutils/",
":solmate/=lib/solmate/src/",
":surl/=lib/surl/"
]
}
[{"inputs":[{"internalType":"address","name":"_FYDE","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"UpdateExpiryMustBeCurrent","type":"error"},{"inputs":[],"name":"UpdateExpiryMustIncrease","type":"error"},{"inputs":[],"name":"UpdateExpiryTooLong","type":"error"},{"inputs":[],"name":"UpdateExpiryTooShort","type":"error"},{"inputs":[],"name":"VoteEscrowNotExpired","type":"error"},{"inputs":[],"name":"VoteEscrowZeroAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"wTime","type":"uint256"}],"name":"WeekMathInvalidTime","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"expiry","type":"uint128"}],"name":"UpdateLock","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"FYDE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_LOCK_TIME","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_LOCK_TIME","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WEEK","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getUserHistoryAt","outputs":[{"components":[{"internalType":"uint128","name":"timestamp","type":"uint128"},{"components":[{"internalType":"uint128","name":"bias","type":"uint128"},{"internalType":"uint128","name":"slope","type":"uint128"}],"internalType":"struct VeBalance","name":"value","type":"tuple"}],"internalType":"struct Checkpoint","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserHistoryLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSlopeChangeAppliedAt","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"positionData","outputs":[{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint128","name":"expiry","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"","type":"uint128"}],"name":"slopeChanges","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"totalSupplyAndBalanceCurrent","outputs":[{"internalType":"uint128","name":"","type":"uint128"},{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"","type":"uint128"}],"name":"totalSupplyAt","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupplyCurrent","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalSupplyStored","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"additionalAmountToLock","type":"uint128"},{"internalType":"uint128","name":"newExpiry","type":"uint128"}],"name":"updateLock","outputs":[{"internalType":"uint128","name":"newVeBalance","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[{"internalType":"uint128","name":"amount","type":"uint128"}],"stateMutability":"nonpayable","type":"function"}]