编译器
0.8.20+commit.a1b79de6
文件 1 的 8:Address.sol
pragma solidity ^0.8.20;
library Address {
error AddressInsufficientBalance(address account);
error AddressEmptyCode(address target);
error FailedInnerCall();
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
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 AddressInsufficientBalance(address(this));
}
(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 FailedInnerCall();
}
}
}
文件 2 的 8:Context.sol
pragma solidity ^0.8.20;
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;
}
}
文件 3 的 8: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);
}
文件 4 的 8:IERC20Permit.sol
pragma solidity ^0.8.20;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
文件 5 的 8:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 6 的 8:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.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 _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;
}
}
文件 7 的 8:SystemStart.sol
pragma solidity 0.8.20;
contract SystemStart {
uint256 public immutable startTime;
constructor(uint256 _startTime) {
require(_startTime % 1 weeks == 0, "SystemStart: startTime must be a Thursday 00:00:00 GMT");
startTime = _startTime;
}
function getWeek() public view returns (uint256 week) {
return (block.timestamp - startTime) / 1 weeks;
}
}
文件 8 的 8:TokenLocker.sol
pragma solidity 0.8.20;
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SystemStart} from "./utils/SystemStart.sol";
contract TokenLocker is Ownable, SystemStart {
using SafeERC20 for IERC20;
uint256 public constant MAX_LOCK_WEEKS = 52;
uint256 public immutable lockToTokenRatio;
IERC20 public immutable lockToken;
address public immutable deploymentManager;
bool public penaltyWithdrawalsEnabled;
uint256 public allowPenaltyWithdrawAfter;
struct AccountData {
uint32 locked;
uint32 unlocked;
uint32 frozen;
uint16 week;
uint256[256] updateWeeks;
}
struct LockData {
uint256 amount;
uint256 weeksToUnlock;
}
struct ExtendLockData {
uint256 amount;
uint256 currentWeeks;
uint256 newWeeks;
}
address public feeReceiver;
uint32 public totalDecayRate;
uint16 public totalUpdatedWeek;
uint40[65535] totalWeeklyWeights;
uint32[65535] totalWeeklyUnlocks;
mapping(address => uint40[65535]) accountWeeklyWeights;
mapping(address => uint32[65535]) accountWeeklyUnlocks;
mapping(address => AccountData) accountLockData;
event LockCreated(address indexed account, uint256 amount, uint256 _weeks);
event LockExtended(address indexed account, uint256 amount, uint256 _weeks, uint256 newWeeks);
event LocksCreated(address indexed account, LockData[] newLocks);
event LocksExtended(address indexed account, ExtendLockData[] locks);
event LocksFrozen(address indexed account, uint256 amount);
event LocksUnfrozen(address indexed account, uint256 amount);
event LocksWithdrawn(address indexed account, uint256 withdrawn, uint256 penalty);
constructor(
IERC20 _token,
address _owner,
address _manager,
address _feeReceiver,
uint256 _startTime,
uint256 _lockToTokenRatio
) SystemStart(_startTime) Ownable(_owner) {
lockToken = _token;
deploymentManager = _manager;
feeReceiver = _feeReceiver;
lockToTokenRatio = _lockToTokenRatio;
}
modifier notFrozen(address account) {
require(accountLockData[account].frozen == 0, "Lock is frozen");
_;
}
function setAllowPenaltyWithdrawAfter(uint256 _timestamp) external returns (bool) {
require(msg.sender == deploymentManager, "!deploymentManager");
require(allowPenaltyWithdrawAfter == 0, "Already set");
require(_timestamp > block.timestamp && _timestamp < block.timestamp + 13 weeks, "Invalid timestamp");
allowPenaltyWithdrawAfter = _timestamp;
return true;
}
function setPenaltyWithdrawalsEnabled(bool _enabled) external onlyOwner returns (bool) {
uint256 start = allowPenaltyWithdrawAfter;
require(start != 0 && block.timestamp > start, "Not yet!");
penaltyWithdrawalsEnabled = _enabled;
return true;
}
function setFeeReceiver(address _receiver) external onlyOwner returns (bool) {
require(_receiver != address(0), "!_receiver");
feeReceiver = _receiver;
return true;
}
function getAccountBalances(address account) external view returns (uint256 locked, uint256 unlocked) {
AccountData storage accountData = accountLockData[account];
uint256 frozen = accountData.frozen;
unlocked = accountData.unlocked;
if (frozen > 0) {
return (frozen, unlocked);
}
locked = accountData.locked;
if (locked > 0) {
uint32[65535] storage weeklyUnlocks = accountWeeklyUnlocks[account];
uint256 accountWeek = accountData.week;
uint256 systemWeek = getWeek();
uint256 bitfield = accountData.updateWeeks[accountWeek >> 8] >> (accountWeek % 256);
while (accountWeek < systemWeek) {
accountWeek++;
bitfield = accountWeek % 256 == 0 ? accountData.updateWeeks[accountWeek >> 8] : bitfield >>= 1;
if (bitfield & uint256(1) == 1) {
uint256 u = weeklyUnlocks[accountWeek];
locked -= u;
unlocked += u;
if (locked == 0) break;
}
}
}
}
function getAccountWeight(address account) external view returns (uint256) {
return getAccountWeightAt(account, getWeek());
}
function getAccountWeightAt(address account, uint256 week) public view returns (uint256) {
if (week > getWeek()) return 0;
uint32[65535] storage weeklyUnlocks = accountWeeklyUnlocks[account];
uint40[65535] storage weeklyWeights = accountWeeklyWeights[account];
AccountData storage accountData = accountLockData[account];
uint256 accountWeek = accountData.week;
if (accountWeek >= week) return weeklyWeights[week];
uint256 locked = accountData.locked;
uint256 weight = weeklyWeights[accountWeek];
if (locked == 0 || accountData.frozen > 0) {
return weight;
}
uint256 bitfield = accountData.updateWeeks[accountWeek >> 8] >> (accountWeek % 256);
while (accountWeek < week) {
accountWeek++;
weight -= locked;
bitfield = accountWeek % 256 == 0 ? accountData.updateWeeks[accountWeek >> 8] : bitfield >> 1;
if (bitfield & uint256(1) == 1) {
uint256 amount = weeklyUnlocks[accountWeek];
locked -= amount;
if (locked == 0) break;
}
}
return weight;
}
function getAccountActiveLocks(
address account,
uint256 minWeeks
) external view returns (LockData[] memory lockData, uint256 frozenAmount) {
AccountData storage accountData = accountLockData[account];
frozenAmount = accountData.frozen;
if (frozenAmount == 0) {
if (minWeeks == 0) minWeeks = 1;
uint32[65535] storage unlocks = accountWeeklyUnlocks[account];
uint256 systemWeek = getWeek();
uint256 currentWeek = systemWeek + minWeeks;
uint256 maxLockWeek = systemWeek + MAX_LOCK_WEEKS;
uint256[] memory unlockWeeks = new uint256[](MAX_LOCK_WEEKS);
uint256 bitfield = accountData.updateWeeks[currentWeek / 256] >> (currentWeek % 256);
uint256 length;
while (currentWeek <= maxLockWeek) {
if (bitfield & uint256(1) == 1) {
unlockWeeks[length] = currentWeek;
length++;
}
currentWeek++;
bitfield = currentWeek % 256 == 0 ? accountData.updateWeeks[currentWeek / 256] : bitfield >> 1;
}
lockData = new LockData[](length);
uint256 x = length;
for (uint256 i = 0; x != 0; i++) {
x--;
uint256 idx = unlockWeeks[x];
lockData[i] = LockData({ weeksToUnlock: idx - systemWeek, amount: unlocks[idx] });
}
}
return (lockData, frozenAmount);
}
function getWithdrawWithPenaltyAmounts(
address account,
uint256 amountToWithdraw
) external view returns (uint256, uint256) {
AccountData storage accountData = accountLockData[account];
uint32[65535] storage unlocks = accountWeeklyUnlocks[account];
if (amountToWithdraw != type(uint256).max) amountToWithdraw *= lockToTokenRatio;
uint256 unlocked = accountData.unlocked * lockToTokenRatio;
if (unlocked >= amountToWithdraw) {
return (amountToWithdraw, 0);
}
uint256 remaining = amountToWithdraw - unlocked;
uint256 penaltyTotal;
uint256 accountWeek = accountData.week;
uint256 systemWeek = getWeek();
uint256 offset = systemWeek - accountWeek;
uint256 bitfield = accountData.updateWeeks[accountWeek >> 8];
for (uint256 weeksToUnlock = 1; weeksToUnlock < MAX_LOCK_WEEKS; weeksToUnlock++) {
accountWeek++;
if (accountWeek % 256 == 0) {
bitfield = accountData.updateWeeks[accountWeek >> 8];
}
if ((bitfield >> (accountWeek % 256)) & uint256(1) == 1) {
uint256 lockAmount = unlocks[accountWeek] * lockToTokenRatio;
uint256 penaltyOnAmount = 0;
if (accountWeek > systemWeek) {
penaltyOnAmount = (lockAmount * (weeksToUnlock - offset)) / MAX_LOCK_WEEKS;
}
if (lockAmount - penaltyOnAmount > remaining) {
penaltyOnAmount =
(remaining * MAX_LOCK_WEEKS) /
(MAX_LOCK_WEEKS - (weeksToUnlock - offset)) -
remaining;
uint256 dust = ((penaltyOnAmount + remaining) % lockToTokenRatio);
if (dust > 0) penaltyOnAmount += lockToTokenRatio - dust;
penaltyTotal += penaltyOnAmount;
remaining = 0;
} else {
penaltyTotal += penaltyOnAmount;
remaining -= lockAmount - penaltyOnAmount;
}
if (remaining == 0) {
break;
}
}
}
amountToWithdraw -= remaining;
return (amountToWithdraw, penaltyTotal);
}
function getTotalWeight() external view returns (uint256) {
return getTotalWeightAt(getWeek());
}
function getTotalWeightAt(uint256 week) public view returns (uint256) {
uint256 systemWeek = getWeek();
if (week > systemWeek) return 0;
uint32 updatedWeek = totalUpdatedWeek;
if (week <= updatedWeek) return totalWeeklyWeights[week];
uint32 rate = totalDecayRate;
uint40 weight = totalWeeklyWeights[updatedWeek];
if (rate == 0 || updatedWeek >= systemWeek) {
return weight;
}
while (updatedWeek < systemWeek) {
updatedWeek++;
weight -= rate;
rate -= totalWeeklyUnlocks[updatedWeek];
}
return weight;
}
function getAccountWeightWrite(address account) external returns (uint256) {
return _weeklyWeightWrite(account);
}
function getTotalWeightWrite() public returns (uint256) {
uint256 week = getWeek();
uint32 rate = totalDecayRate;
uint32 updatedWeek = totalUpdatedWeek;
uint40 weight = totalWeeklyWeights[updatedWeek];
if (weight == 0) {
totalUpdatedWeek = uint16(week);
return 0;
}
while (updatedWeek < week) {
updatedWeek++;
weight -= rate;
totalWeeklyWeights[updatedWeek] = weight;
rate -= totalWeeklyUnlocks[updatedWeek];
}
totalDecayRate = rate;
totalUpdatedWeek = uint16(week);
return weight;
}
function lock(address _account, uint256 _amount, uint256 _weeks) external returns (bool) {
require(_weeks > 0, "Min 1 week");
require(_amount > 0, "Amount must be nonzero");
_lock(_account, _amount, _weeks);
lockToken.safeTransferFrom(msg.sender, address(this), _amount * lockToTokenRatio);
return true;
}
function _lock(address _account, uint256 _amount, uint256 _weeks) internal {
require(_weeks <= MAX_LOCK_WEEKS, "Exceeds MAX_LOCK_WEEKS");
AccountData storage accountData = accountLockData[_account];
uint256 accountWeight = _weeklyWeightWrite(_account);
uint256 totalWeight = getTotalWeightWrite();
uint256 systemWeek = getWeek();
uint256 frozen = accountData.frozen;
if (frozen > 0) {
accountData.frozen = uint32(frozen + _amount);
_weeks = MAX_LOCK_WEEKS;
} else {
if (_weeks == 1 && block.timestamp % 1 weeks > 4 days) _weeks = 2;
accountData.locked = uint32(accountData.locked + _amount);
totalDecayRate = uint32(totalDecayRate + _amount);
uint32[65535] storage unlocks = accountWeeklyUnlocks[_account];
uint256 unlockWeek = systemWeek + _weeks;
uint256 previous = unlocks[unlockWeek];
unlocks[unlockWeek] = uint32(previous + _amount);
totalWeeklyUnlocks[unlockWeek] += uint32(_amount);
if (previous == 0) {
uint256 idx = unlockWeek / 256;
uint256 bitfield = accountData.updateWeeks[idx] | (uint256(1) << (unlockWeek % 256));
accountData.updateWeeks[idx] = bitfield;
}
}
accountWeeklyWeights[_account][systemWeek] = uint40(accountWeight + _amount * _weeks);
totalWeeklyWeights[systemWeek] = uint40(totalWeight + _amount * _weeks);
emit LockCreated(_account, _amount, _weeks);
}
function extendLock(
uint256 _amount,
uint256 _weeks,
uint256 _newWeeks
) external notFrozen(msg.sender) returns (bool) {
require(_weeks > 0, "Min 1 week");
require(_newWeeks <= MAX_LOCK_WEEKS, "Exceeds MAX_LOCK_WEEKS");
require(_weeks < _newWeeks, "newWeeks must be greater than weeks");
require(_amount > 0, "Amount must be nonzero");
AccountData storage accountData = accountLockData[msg.sender];
uint256 systemWeek = getWeek();
uint256 increase = (_newWeeks - _weeks) * _amount;
uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];
uint256 weight = _weeklyWeightWrite(msg.sender);
accountWeeklyWeights[msg.sender][systemWeek] = uint40(weight + increase);
uint256 changedWeek = systemWeek + _weeks;
uint256 previous = unlocks[changedWeek];
unlocks[changedWeek] = uint32(previous - _amount);
totalWeeklyUnlocks[changedWeek] -= uint32(_amount);
if (previous == _amount) {
uint256 idx = changedWeek / 256;
uint256 bitfield = accountData.updateWeeks[idx] & ~(uint256(1) << (changedWeek % 256));
accountData.updateWeeks[idx] = bitfield;
}
changedWeek = systemWeek + _newWeeks;
previous = unlocks[changedWeek];
unlocks[changedWeek] = uint32(previous + _amount);
totalWeeklyUnlocks[changedWeek] += uint32(_amount);
if (previous == 0) {
uint256 idx = changedWeek / 256;
uint256 bitfield = accountData.updateWeeks[idx] | (uint256(1) << (changedWeek % 256));
accountData.updateWeeks[idx] = bitfield;
}
totalWeeklyWeights[systemWeek] = uint40(getTotalWeightWrite() + increase);
emit LockExtended(msg.sender, _amount, _weeks, _newWeeks);
return true;
}
function lockMany(address _account, LockData[] calldata newLocks) external notFrozen(_account) returns (bool) {
AccountData storage accountData = accountLockData[_account];
uint32[65535] storage unlocks = accountWeeklyUnlocks[_account];
uint256 accountWeight = _weeklyWeightWrite(_account);
uint256 systemWeek = getWeek();
uint256[2] memory bitfield = [
accountData.updateWeeks[systemWeek / 256],
accountData.updateWeeks[(systemWeek / 256) + 1]
];
uint256 increasedAmount;
uint256 increasedWeight;
uint256 length = newLocks.length;
for (uint256 i = 0; i < length; i++) {
uint256 amount = newLocks[i].amount;
uint256 week = newLocks[i].weeksToUnlock;
require(amount > 0, "Amount must be nonzero");
require(week > 0, "Min 1 week");
require(week <= MAX_LOCK_WEEKS, "Exceeds MAX_LOCK_WEEKS");
if (week == 1 && block.timestamp % 1 weeks > 4 days) week = 2;
increasedAmount += amount;
increasedWeight += amount * week;
uint256 unlockWeek = systemWeek + week;
uint256 previous = unlocks[unlockWeek];
unlocks[unlockWeek] = uint32(previous + amount);
totalWeeklyUnlocks[unlockWeek] += uint32(amount);
if (previous == 0) {
uint256 idx = (unlockWeek / 256) - (systemWeek / 256);
bitfield[idx] = bitfield[idx] | (uint256(1) << (unlockWeek % 256));
}
}
accountData.updateWeeks[systemWeek / 256] = bitfield[0];
accountData.updateWeeks[(systemWeek / 256) + 1] = bitfield[1];
lockToken.safeTransferFrom(msg.sender, address(this), increasedAmount * lockToTokenRatio);
accountWeeklyWeights[_account][systemWeek] = uint40(accountWeight + increasedWeight);
totalWeeklyWeights[systemWeek] = uint40(getTotalWeightWrite() + increasedWeight);
accountData.locked = uint32(accountData.locked + increasedAmount);
totalDecayRate = uint32(totalDecayRate + increasedAmount);
emit LocksCreated(_account, newLocks);
return true;
}
function extendMany(ExtendLockData[] calldata newExtendLocks) external notFrozen(msg.sender) returns (bool) {
AccountData storage accountData = accountLockData[msg.sender];
uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];
uint256 accountWeight = _weeklyWeightWrite(msg.sender);
uint256 systemWeek = getWeek();
uint256[2] memory bitfield = [
accountData.updateWeeks[systemWeek / 256],
accountData.updateWeeks[(systemWeek / 256) + 1]
];
uint256 increasedWeight;
uint256 length = newExtendLocks.length;
for (uint256 i = 0; i < length; i++) {
uint256 amount = newExtendLocks[i].amount;
uint256 oldWeeks = newExtendLocks[i].currentWeeks;
uint256 newWeeks = newExtendLocks[i].newWeeks;
require(oldWeeks > 0, "Min 1 week");
require(newWeeks <= MAX_LOCK_WEEKS, "Exceeds MAX_LOCK_WEEKS");
require(oldWeeks < newWeeks, "newWeeks must be greater than weeks");
require(amount > 0, "Amount must be nonzero");
increasedWeight += (newWeeks - oldWeeks) * amount;
oldWeeks += systemWeek;
uint256 previous = unlocks[oldWeeks];
unlocks[oldWeeks] = uint32(previous - amount);
totalWeeklyUnlocks[oldWeeks] -= uint32(amount);
if (previous == amount) {
uint256 idx = (oldWeeks / 256) - (systemWeek / 256);
bitfield[idx] = bitfield[idx] & ~(uint256(1) << (oldWeeks % 256));
}
newWeeks += systemWeek;
previous = unlocks[newWeeks];
unlocks[newWeeks] = uint32(previous + amount);
totalWeeklyUnlocks[newWeeks] += uint32(amount);
if (previous == 0) {
uint256 idx = (newWeeks / 256) - (systemWeek / 256);
bitfield[idx] = bitfield[idx] | (uint256(1) << (newWeeks % 256));
}
}
accountData.updateWeeks[systemWeek / 256] = bitfield[0];
accountData.updateWeeks[(systemWeek / 256) + 1] = bitfield[1];
accountWeeklyWeights[msg.sender][systemWeek] = uint40(accountWeight + increasedWeight);
totalWeeklyWeights[systemWeek] = uint40(getTotalWeightWrite() + increasedWeight);
emit LocksExtended(msg.sender, newExtendLocks);
return true;
}
function freeze() external notFrozen(msg.sender) {
AccountData storage accountData = accountLockData[msg.sender];
uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];
uint256 accountWeight = _weeklyWeightWrite(msg.sender);
uint256 totalWeight = getTotalWeightWrite();
uint256 locked = accountData.locked;
require(locked > 0, "No locked balance");
totalDecayRate = uint32(totalDecayRate - locked);
accountData.frozen = uint32(locked);
accountData.locked = 0;
uint256 systemWeek = getWeek();
accountWeeklyWeights[msg.sender][systemWeek] = uint40(locked * MAX_LOCK_WEEKS);
totalWeeklyWeights[systemWeek] = uint40(totalWeight - accountWeight + locked * MAX_LOCK_WEEKS);
uint256 bitfield = accountData.updateWeeks[systemWeek / 256] >> (systemWeek % 256);
while (locked > 0) {
systemWeek++;
if (systemWeek % 256 == 0) {
bitfield = accountData.updateWeeks[systemWeek / 256];
accountData.updateWeeks[(systemWeek / 256) - 1] = 0;
} else {
bitfield = bitfield >> 1;
}
if (bitfield & uint256(1) == 1) {
uint32 amount = unlocks[systemWeek];
unlocks[systemWeek] = 0;
totalWeeklyUnlocks[systemWeek] -= amount;
locked -= amount;
}
}
accountData.updateWeeks[systemWeek / 256] = 0;
emit LocksFrozen(msg.sender, locked);
}
function unfreeze() external {
AccountData storage accountData = accountLockData[msg.sender];
uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];
uint256 frozen = accountData.frozen;
require(frozen > 0, "Locks already unfrozen");
_weeklyWeightWrite(msg.sender);
getTotalWeightWrite();
totalDecayRate = uint32(totalDecayRate + frozen);
accountData.locked = uint32(frozen);
accountData.frozen = 0;
uint256 systemWeek = getWeek();
uint256 unlockWeek = systemWeek + MAX_LOCK_WEEKS;
unlocks[unlockWeek] = uint32(frozen);
totalWeeklyUnlocks[unlockWeek] += uint32(frozen);
uint256 idx = unlockWeek / 256;
uint256 bitfield = accountData.updateWeeks[idx] | (uint256(1) << (unlockWeek % 256));
accountData.updateWeeks[idx] = bitfield;
emit LocksUnfrozen(msg.sender, frozen);
}
function withdrawExpiredLocks(uint256 _weeks) external returns (bool) {
_weeklyWeightWrite(msg.sender);
getTotalWeightWrite();
AccountData storage accountData = accountLockData[msg.sender];
uint256 unlocked = accountData.unlocked;
require(unlocked > 0, "No unlocked tokens");
accountData.unlocked = 0;
if (_weeks > 0) {
_lock(msg.sender, unlocked, _weeks);
} else {
lockToken.safeTransfer(msg.sender, unlocked * lockToTokenRatio);
emit LocksWithdrawn(msg.sender, unlocked, 0);
}
return true;
}
function withdrawWithPenalty(uint256 amountToWithdraw) external notFrozen(msg.sender) returns (uint256) {
require(penaltyWithdrawalsEnabled, "Penalty withdrawals are disabled");
AccountData storage accountData = accountLockData[msg.sender];
uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];
uint256 weight = _weeklyWeightWrite(msg.sender);
if (amountToWithdraw != type(uint256).max) amountToWithdraw *= lockToTokenRatio;
uint256 unlocked = accountData.unlocked * lockToTokenRatio;
if (unlocked >= amountToWithdraw) {
accountData.unlocked = uint32((unlocked - amountToWithdraw) / lockToTokenRatio);
lockToken.safeTransfer(msg.sender, amountToWithdraw);
return amountToWithdraw;
}
uint256 remaining = amountToWithdraw;
if (unlocked > 0) {
remaining -= unlocked;
accountData.unlocked = 0;
}
uint256 currentWeek = getWeek();
uint256 systemWeek = currentWeek;
uint256 bitfield = accountData.updateWeeks[systemWeek / 256];
uint256 penaltyTotal;
uint256 decreasedWeight;
for (uint256 weeksToUnlock = 1; weeksToUnlock < MAX_LOCK_WEEKS; weeksToUnlock++) {
systemWeek++;
if (systemWeek % 256 == 0) {
accountData.updateWeeks[systemWeek / 256 - 1] = 0;
bitfield = accountData.updateWeeks[systemWeek / 256];
}
if ((bitfield >> (systemWeek % 256)) & uint256(1) == 1) {
uint256 lockAmount = unlocks[systemWeek] * lockToTokenRatio;
uint256 penaltyOnAmount = (lockAmount * weeksToUnlock) / MAX_LOCK_WEEKS;
if (lockAmount - penaltyOnAmount > remaining) {
penaltyOnAmount = (remaining * MAX_LOCK_WEEKS) / (MAX_LOCK_WEEKS - weeksToUnlock) - remaining;
uint256 dust = ((penaltyOnAmount + remaining) % lockToTokenRatio);
if (dust > 0) penaltyOnAmount += lockToTokenRatio - dust;
penaltyTotal += penaltyOnAmount;
uint256 lockReduceAmount = (penaltyOnAmount + remaining) / lockToTokenRatio;
decreasedWeight += lockReduceAmount * weeksToUnlock;
unlocks[systemWeek] -= uint32(lockReduceAmount);
totalWeeklyUnlocks[systemWeek] -= uint32(lockReduceAmount);
remaining = 0;
} else {
penaltyTotal += penaltyOnAmount;
decreasedWeight += (lockAmount / lockToTokenRatio) * weeksToUnlock;
bitfield = bitfield & ~(uint256(1) << (systemWeek % 256));
unlocks[systemWeek] = 0;
totalWeeklyUnlocks[systemWeek] -= uint32(lockAmount / lockToTokenRatio);
remaining -= lockAmount - penaltyOnAmount;
}
if (remaining == 0) {
break;
}
}
}
accountData.updateWeeks[systemWeek / 256] = bitfield;
if (amountToWithdraw == type(uint256).max) {
amountToWithdraw -= remaining;
} else {
require(remaining == 0, "Insufficient balance after fees");
}
accountData.locked -= uint32((amountToWithdraw + penaltyTotal - unlocked) / lockToTokenRatio);
totalDecayRate -= uint32((amountToWithdraw + penaltyTotal - unlocked) / lockToTokenRatio);
accountWeeklyWeights[msg.sender][currentWeek] = uint40(weight - decreasedWeight);
totalWeeklyWeights[currentWeek] = uint40(getTotalWeightWrite() - decreasedWeight);
lockToken.safeTransfer(msg.sender, amountToWithdraw);
lockToken.safeTransfer(feeReceiver, penaltyTotal);
emit LocksWithdrawn(msg.sender, amountToWithdraw, penaltyTotal);
return amountToWithdraw;
}
function _weeklyWeightWrite(address account) internal returns (uint256 weight) {
AccountData storage accountData = accountLockData[account];
uint32[65535] storage weeklyUnlocks = accountWeeklyUnlocks[account];
uint40[65535] storage weeklyWeights = accountWeeklyWeights[account];
uint256 systemWeek = getWeek();
uint256 accountWeek = accountData.week;
weight = weeklyWeights[accountWeek];
if (accountWeek == systemWeek) return weight;
if (accountData.frozen > 0) {
while (systemWeek > accountWeek) {
accountWeek++;
weeklyWeights[accountWeek] = uint40(weight);
}
accountData.week = uint16(systemWeek);
return weight;
}
uint256 locked = accountData.locked;
if (locked == 0) {
if (accountWeek < systemWeek) {
accountData.week = uint16(systemWeek);
}
return 0;
}
uint256 unlocked;
uint256 bitfield = accountData.updateWeeks[accountWeek >> 8] >> (accountWeek % 256);
while (accountWeek < systemWeek) {
accountWeek++;
weight -= locked;
weeklyWeights[accountWeek] = uint40(weight);
bitfield = accountWeek % 256 == 0 ? accountData.updateWeeks[accountWeek >> 8] : bitfield >> 1;
if (bitfield & uint256(1) == 1) {
uint32 amount = weeklyUnlocks[accountWeek];
locked -= amount;
unlocked += amount;
if (locked == 0) {
accountWeek = systemWeek;
break;
}
}
}
accountData.unlocked = uint32(accountData.unlocked + unlocked);
accountData.locked = uint32(locked);
accountData.week = uint16(accountWeek);
}
}
{
"compilationTarget": {
"src/TokenLocker.sol": "TokenLocker"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@solady/=lib/solady/src/",
":ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solady/=lib/solady/src/"
]
}
[{"inputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_manager","type":"address"},{"internalType":"address","name":"_feeReceiver","type":"address"},{"internalType":"uint256","name":"_startTime","type":"uint256"},{"internalType":"uint256","name":"_lockToTokenRatio","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","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":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_weeks","type":"uint256"}],"name":"LockCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_weeks","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newWeeks","type":"uint256"}],"name":"LockExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"weeksToUnlock","type":"uint256"}],"indexed":false,"internalType":"struct TokenLocker.LockData[]","name":"newLocks","type":"tuple[]"}],"name":"LocksCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"currentWeeks","type":"uint256"},{"internalType":"uint256","name":"newWeeks","type":"uint256"}],"indexed":false,"internalType":"struct TokenLocker.ExtendLockData[]","name":"locks","type":"tuple[]"}],"name":"LocksExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"LocksFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"LocksUnfrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"}],"name":"LocksWithdrawn","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"},{"inputs":[],"name":"MAX_LOCK_WEEKS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowPenaltyWithdrawAfter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deploymentManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_weeks","type":"uint256"},{"internalType":"uint256","name":"_newWeeks","type":"uint256"}],"name":"extendLock","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"currentWeeks","type":"uint256"},{"internalType":"uint256","name":"newWeeks","type":"uint256"}],"internalType":"struct TokenLocker.ExtendLockData[]","name":"newExtendLocks","type":"tuple[]"}],"name":"extendMany","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"freeze","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"minWeeks","type":"uint256"}],"name":"getAccountActiveLocks","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"weeksToUnlock","type":"uint256"}],"internalType":"struct TokenLocker.LockData[]","name":"lockData","type":"tuple[]"},{"internalType":"uint256","name":"frozenAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountBalances","outputs":[{"internalType":"uint256","name":"locked","type":"uint256"},{"internalType":"uint256","name":"unlocked","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"week","type":"uint256"}],"name":"getAccountWeightAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountWeightWrite","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getTotalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"week","type":"uint256"}],"name":"getTotalWeightAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalWeightWrite","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getWeek","outputs":[{"internalType":"uint256","name":"week","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amountToWithdraw","type":"uint256"}],"name":"getWithdrawWithPenaltyAmounts","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_weeks","type":"uint256"}],"name":"lock","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"weeksToUnlock","type":"uint256"}],"internalType":"struct TokenLocker.LockData[]","name":"newLocks","type":"tuple[]"}],"name":"lockMany","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lockToTokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"penaltyWithdrawalsEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"setAllowPenaltyWithdrawAfter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"}],"name":"setFeeReceiver","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_enabled","type":"bool"}],"name":"setPenaltyWithdrawalsEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDecayRate","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalUpdatedWeek","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unfreeze","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_weeks","type":"uint256"}],"name":"withdrawExpiredLocks","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountToWithdraw","type":"uint256"}],"name":"withdrawWithPenalty","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]