Created
February 12, 2024 21:25
-
-
Save passandscore/5f06bc34bdc491f92f7079ce7380b8d0 to your computer and use it in GitHub Desktop.
Manages the timelock logic for external tokens.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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