-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds locker contract, deposit caps and tests
- Loading branch information
1 parent
20c97c7
commit db253b4
Showing
10 changed files
with
562 additions
and
30 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { AccessManagedUpgradeable } from | ||
"@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; | ||
import { Address } from "@openzeppelin/contracts/utils/Address.sol"; | ||
import { PufLockerStorage } from "./PufLockerStorage.sol"; | ||
import { IPufLocker } from "./interface/IPufLocker.sol"; | ||
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; | ||
import { Permit } from "./structs/Permit.sol"; | ||
|
||
contract PufLocker is AccessManagedUpgradeable, IPufLocker, PufLockerStorage { | ||
function initialize(address accessManager) external initializer { | ||
__AccessManaged_init(accessManager); | ||
} | ||
|
||
modifier isAllowedToken(address token) { | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
if (!$.allowedTokens[token]) { | ||
revert TokenNotAllowed(); | ||
} | ||
_; | ||
} | ||
|
||
function setAllowedToken(address token, bool allowed) external restricted { | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
$.allowedTokens[token] = allowed; | ||
emit TokenAllowanceChanged(token, allowed); | ||
} | ||
|
||
function setLockPeriods(uint40 minLock, uint40 maxLock) external restricted { | ||
if (minLock > maxLock) { | ||
revert InvalidLockPeriod(); | ||
} | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
$.minLockPeriod = minLock; | ||
$.maxLockPeriod = maxLock; | ||
} | ||
|
||
function deposit(address token, uint40 lockPeriod, Permit calldata permitData) external isAllowedToken(token) { | ||
if (permitData.amount == 0) { | ||
revert InvalidAmount(); | ||
} | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
if (lockPeriod < $.minLockPeriod || lockPeriod > $.maxLockPeriod) { | ||
revert InvalidLockPeriod(); | ||
} | ||
|
||
uint40 releaseTime = uint40(block.timestamp) + lockPeriod; | ||
|
||
// https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#security_considerations | ||
try ERC20Permit(token).permit({ | ||
owner: msg.sender, | ||
spender: address(this), | ||
value: permitData.amount, | ||
deadline: permitData.deadline, | ||
v: permitData.v, | ||
s: permitData.s, | ||
r: permitData.r | ||
}) { } catch { } | ||
|
||
IERC20(token).transferFrom(msg.sender, address(this), permitData.amount); | ||
$.deposits[msg.sender][token].push(Deposit(uint128(permitData.amount), releaseTime)); | ||
|
||
emit Deposited(msg.sender, token, uint128(permitData.amount), releaseTime); | ||
} | ||
|
||
function withdraw(address token, uint256[] calldata depositIndexes, address recipient) external { | ||
if (recipient == address(0)) { | ||
revert InvalidRecipientAddress(); | ||
} | ||
|
||
PufLockerData storage $ = _getPufLockerStorage(); | ||
uint128 totalAmount = 0; | ||
Deposit[] storage userDeposits = $.deposits[msg.sender][token]; | ||
|
||
for (uint256 i = 0; i < depositIndexes.length; i++) { | ||
uint256 index = depositIndexes[i]; | ||
if (index >= userDeposits.length) { | ||
revert InvalidDepositIndex(); | ||
} | ||
|
||
Deposit storage userDeposit = userDeposits[index]; | ||
if (userDeposit.releaseTime > uint40(block.timestamp)) { | ||
revert DepositStillLocked(); | ||
} | ||
|
||
totalAmount += userDeposit.amount; | ||
userDeposit.amount = 0; // Set amount to zero to mark as withdrawn | ||
} | ||
|
||
if (totalAmount == 0) { | ||
revert NoWithdrawableAmount(); | ||
} | ||
|
||
IERC20(token).transfer(recipient, totalAmount); | ||
|
||
emit Withdrawn(msg.sender, token, totalAmount, recipient); | ||
} | ||
|
||
function getDeposits(address user, address token, uint256 start, uint256 limit) | ||
external | ||
view | ||
returns (Deposit[] memory) | ||
{ | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
Deposit[] storage userDeposits = $.deposits[user][token]; | ||
uint256 totalDeposits = userDeposits.length; | ||
Deposit[] memory depositPage; | ||
|
||
if (start >= totalDeposits) { | ||
return depositPage; | ||
} | ||
|
||
uint256 end = start + limit > totalDeposits ? totalDeposits : start + limit; | ||
uint256 count = end - start; | ||
|
||
depositPage = new Deposit[](count); | ||
for (uint256 i = 0; i < count; i++) { | ||
depositPage[i] = userDeposits[start + i]; | ||
} | ||
|
||
return depositPage; | ||
} | ||
|
||
function getLockPeriods() external view returns (uint40, uint40) { | ||
PufLockerData storage $ = _getPufLockerStorage(); | ||
return ($.minLockPeriod, $.maxLockPeriod); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import { IPufLocker } from "./interface/IPufLocker.sol"; | ||
|
||
/** | ||
* @title PufLockerStorage | ||
* @dev Storage contract for PufLocker to support upgradability | ||
*/ | ||
abstract contract PufLockerStorage { | ||
// Storage slot location for PufLocker data | ||
bytes32 private constant _PUF_LOCKER_STORAGE_SLOT = | ||
0xed4b58c94786491f32821dd56ebc03d5f67df2b901c79c3e972343a4fbb3dfed; // keccak256("PufLocker.storage"); | ||
|
||
struct PufLockerData { | ||
mapping(address => bool) allowedTokens; | ||
mapping(address => mapping(address => IPufLocker.Deposit[])) deposits; | ||
uint40 minLockPeriod; | ||
uint40 maxLockPeriod; | ||
} | ||
|
||
function _getPufLockerStorage() internal pure returns (PufLockerData storage $) { | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
$.slot := _PUF_LOCKER_STORAGE_SLOT | ||
} | ||
} | ||
} |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import { Permit } from "../structs/Permit.sol"; | ||
|
||
interface IPufLocker { | ||
// Custom error messages | ||
error NotAdmin(); | ||
error TokenNotAllowed(); | ||
error InvalidAmount(); | ||
error InvalidLockPeriod(); | ||
error InvalidDepositIndex(); | ||
error DepositStillLocked(); | ||
error NoWithdrawableAmount(); | ||
error InvalidRecipientAddress(); | ||
|
||
// Events | ||
event TokenAllowanceChanged(address indexed token, bool allowed); | ||
event Deposited(address indexed user, address indexed token, uint128 amount, uint40 releaseTime); | ||
event Withdrawn(address indexed user, address indexed token, uint128 amount, address recipient); | ||
|
||
// Functions | ||
function setAllowedToken(address token, bool allowed) external; | ||
|
||
function setLockPeriods(uint40 minLockPeriod, uint40 maxLockPeriod) external; | ||
|
||
function deposit(address token, uint40 lockPeriod, Permit calldata permitData) external; | ||
|
||
function withdraw(address token, uint256[] calldata depositIndexes, address recipient) external; | ||
|
||
function getDeposits(address user, address token, uint256 start, uint256 limit) | ||
external | ||
view | ||
returns (Deposit[] memory); | ||
function getLockPeriods() external view returns (uint40, uint40); | ||
|
||
struct Deposit { | ||
uint128 amount; | ||
uint40 releaseTime; | ||
} | ||
} |
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
Oops, something went wrong.