Skip to content

Instantly share code, notes, and snippets.

@passandscore
Created February 12, 2024 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save passandscore/5f06bc34bdc491f92f7079ce7380b8d0 to your computer and use it in GitHub Desktop.
Save passandscore/5f06bc34bdc491f92f7079ce7380b8d0 to your computer and use it in GitHub Desktop.
Manages the timelock logic for external tokens.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./ERC20.sol";
import "./interfaces/IERC20.sol";
contract TokenLockers is ERC20 {
error InsufficentFunds();
error InsufficentInputValue();
error InvalidLocktime();
error TransferFailed();
error LockerAlreadyExists();
error ContainsFunds();
error LockerNotFound();
event LockerCreated(
address indexed owner,
string indexed lockerName,
uint256 indexed lockerNumber
);
mapping(address => mapping(uint256 => Locker)) private lockerByUser;
mapping(uint256 => bool) public lockDurations;
mapping(address => uint256[]) private lockerNumbersByUser;
uint256 public totalLockerCount;
uint256 public currentLockerNumber;
IERC20 public defikidsToken;
uint32 public constant DAY_IN_SECONDS = 86400;
struct Locker {
uint256 amount;
uint256 lockTime;
string name;
uint256 lockerNumber;
address owner;
}
modifier validAmount(uint256 amount_) {
if (amount_ == 0) {
revert InsufficentInputValue();
}
_;
}
modifier validLockerNumber(address user_, uint256 lockerNumber_) {
if (lockerByUser[user_][lockerNumber_].lockerNumber == 0) {
revert LockerNotFound();
}
_;
}
/**
* @notice This function sets up the contract by initializing the token's name and symbol and making the contract ownable. It also sets up the lock durations.
*/
function initialize(address defikidsToken_) public initializer {
__ERC20_init("DefiKids-TokenLockers", "DefiKids-TokenLockers");
__Ownable_init();
defikidsToken = IERC20(defikidsToken_);
lockDurations[7 * DAY_IN_SECONDS] = true;
lockDurations[14 * DAY_IN_SECONDS] = true;
lockDurations[30 * DAY_IN_SECONDS] = true;
lockDurations[60 * DAY_IN_SECONDS] = true;
lockDurations[90 * DAY_IN_SECONDS] = true;
}
/**
* @dev Creates a new locker with the specified parameters and adds it to the contract.
*
* @param lockerName_ The name of the locker.
* @param amount_ The amount of cryptocurrency to be locked in the locker.
* @param lockTime_ The duration in seconds for which the cryptocurrency will be locked.
* @param deadline_ The deadline for the permit signature.
* @param v_ The recovery signature 'v' component.
* @param r_ The recovery signature 'r' component.
* @param s_ The recovery signature 's' component.
*
*/
function createLocker(
string memory lockerName_,
uint256 amount_,
uint256 lockTime_,
uint256 deadline_,
uint8 v_,
bytes32 r_,
bytes32 s_
) external validAmount(amount_) {
if (!_isValidLockDuration(lockTime_)) {
revert InvalidLocktime();
}
address sender = msg.sender;
currentLockerNumber++;
_addToLocker(sender, amount_, deadline_, v_, r_, s_);
Locker memory newLocker = Locker(
amount_,
lockTime_,
lockerName_,
currentLockerNumber,
sender
);
lockerByUser[sender][currentLockerNumber] = newLocker;
lockerNumbersByUser[sender].push(currentLockerNumber);
totalLockerCount++;
emit LockerCreated(sender, lockerName_, currentLockerNumber);
}
/**
* @dev Renames an existing locker associated with the caller's address.
* @param lockerNumber_ The new name of the locker.
* @param newLockerName_ The new name of the locker.
*/
function renameLocker(
uint256 lockerNumber_,
string calldata newLockerName_
) public validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
Locker storage currentLockerDetails = lockerByUser[sender][
lockerNumber_
];
currentLockerDetails.name = newLockerName_;
}
/**
* @dev Deletes an existing locker associated with the caller's address.
* @param lockerNumber_ The number of the locker to be deleted.
*/
function deleteLocker(
uint256 lockerNumber_
) public validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
Locker memory lockerDetails = lockerByUser[sender][lockerNumber_];
if (lockerDetails.lockTime > block.timestamp) {
revert InvalidLocktime();
}
if (lockerDetails.amount > 0) {
revert ContainsFunds();
}
//remove the locker number from the array
uint256[] storage lockerNumbers = lockerNumbersByUser[sender];
for (uint256 i = 0; i < lockerNumbers.length; i++) {
if (lockerNumbers[i] == lockerNumber_) {
lockerNumbers[i] = lockerNumbers[lockerNumbers.length - 1];
lockerNumbers.pop();
break;
}
}
delete lockerByUser[sender][lockerNumber_];
}
/**
* @dev Retrieves the details of a locker associated with the caller's address and the specified combination.
* @param lockerNumber_ The locker to retrieve.
*/
function getLockerDetails(
uint256 lockerNumber_
) public view returns (Locker memory) {
Locker memory lockerDetails = lockerByUser[msg.sender][lockerNumber_];
return lockerDetails;
}
/**
* @dev Retrieves the numbers of lockers associated with the caller's address.
* @param user_ The address of the locker owner.
*/
function getLockerCountByUser(address user_) public view returns (uint256) {
return lockerNumbersByUser[user_].length;
}
/**
* @dev Retrieves the total amount of funds locked by the caller for all lockers.
* @param user_ The address of the locker owner.
*/
function getTotalValueLockedByUser(
address user_
) public view returns (uint256) {
uint256 totalValueLocked = 0;
uint256[] memory lockerNumbers = lockerNumbersByUser[user_];
for (uint256 i = 0; i < lockerNumbers.length; i++) {
Locker memory lockerDetails = lockerByUser[user_][lockerNumbers[i]];
totalValueLocked += lockerDetails.amount;
}
return totalValueLocked;
}
/**
* @dev Adds an amount to an existing locker associated with the caller's address.
*
* @param amount_ The additional amount of cryptocurrency to be added to the locker.
* @param lockerNumber_ The number of the locker.
* @param deadline_ The deadline for the permit signature.
* @param v_ The recovery signature 'v' component.
* @param r_ The recovery signature 'r' component.
* @param s_ The recovery signature 's' component.
*
*/
function addToLocker(
uint256 amount_,
uint256 lockerNumber_,
uint256 deadline_,
uint8 v_,
bytes32 r_,
bytes32 s_
) public validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
_addToLocker(sender, amount_, deadline_, v_, r_, s_);
Locker memory lockerDetails = lockerByUser[sender][lockerNumber_];
lockerDetails.amount += amount_;
lockerByUser[sender][lockerNumber_] = lockerDetails;
}
/**
* @dev Allows the owner of a locker to empty the locker and withdraw the locked cryptocurrency.
* @param lockerNumber_ The number of the locker.
*/
function emptyLocker(
uint256 lockerNumber_
) public validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
Locker memory lockerDetails = lockerByUser[sender][lockerNumber_];
if (lockerDetails.lockTime > block.timestamp) {
revert InvalidLocktime();
}
if (!defikidsToken.transfer(sender, lockerDetails.amount)) {
revert TransferFailed();
}
lockerDetails.amount = 0;
lockerByUser[sender][lockerNumber_] = lockerDetails;
}
/**
* @dev Allows the owner of a locker to remove a specified amount from the locker.
* @param lockerNumber_ The number of the locker.
* @param amount_ The amount of cryptocurrency to be removed from the locker.
*/
function removeFromLocker(
uint256 lockerNumber_,
uint256 amount_
) public validAmount(amount_) validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
Locker memory lockerDetails = lockerByUser[sender][lockerNumber_];
if (lockerDetails.lockTime > block.timestamp) {
revert InvalidLocktime();
}
lockerDetails.amount = lockerDetails.amount - amount_;
lockerByUser[sender][lockerNumber_] = lockerDetails;
if (!defikidsToken.transfer(sender, amount_)) {
revert TransferFailed();
}
}
/**
* @dev Allows the owner of a locker to apply a new lock duration to their existing locker.
* @param lockerNumber_ The number of the locker.
* @param newLockTime_ The new lock duration in seconds to be applied to the locker.
*/
function applyNewLock(
uint256 lockerNumber_,
uint256 newLockTime_
) public validLockerNumber(msg.sender, lockerNumber_) {
address sender = msg.sender;
Locker storage lockerDetails = lockerByUser[sender][lockerNumber_];
if (lockerDetails.lockTime > block.timestamp) {
revert InvalidLocktime();
}
if (!_isValidLockDuration(newLockTime_)) {
revert InvalidLocktime();
}
lockerDetails.lockTime = newLockTime_;
}
/**
* @dev Allows the contract owner to remove the lock duration for an existing locker.
* @param lockerNumber_ The number of the locker.
* @param lockerOwner_ The address of the locker owner.
*/
function removeLock(
uint256 lockerNumber_,
address lockerOwner_
) public onlyOwner {
Locker storage lockerDetails = lockerByUser[lockerOwner_][
lockerNumber_
];
lockerDetails.lockTime = 0;
}
/**
* @dev Collects Ether that was sent to the contract.
*
* Note: This function is intended to recover any Ether sent to the contract by mistake and should only be called by the contract owner.
*/
function withdraw() public payable onlyOwner {
payable(owner()).transfer(address(this).balance);
}
//=============================================================
//==================== INTERNAL FUNCTIONS ====================
//=============================================================
/**
* @dev Internal function for adding an amount to an existing locker associated with a specified sender.
*/
function _addToLocker(
address sender,
uint256 amount_,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) private validAmount(amount_) {
uint256 defikidsTokenBalance = defikidsToken.balanceOf(sender);
if (defikidsTokenBalance < amount_) {
revert InsufficentFunds();
}
defikidsToken.permit(sender, address(this), amount_, deadline, v, r, s);
// transfer defidollars from user to contract
if (!defikidsToken.transferFrom(sender, address(this), amount_)) {
revert TransferFailed();
}
}
/**
* @dev Internal function: Checks if the provided lock duration is valid.
* @param lockDuration_ The lock duration to be checked for validity.
*/
function _isValidLockDuration(
uint256 lockDuration_
) internal view returns (bool) {
return lockDurations[lockDuration_];
}
receive() external payable {}
fallback() external payable {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment