Created
August 12, 2022 16:09
-
-
Save vsmelov/7d3a26e56534049990d37d620a59b0f7 to your computer and use it in GitHub Desktop.
This file contains hidden or 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.6.12; | |
pragma experimental ABIEncoderV2; | |
interface IDexioLock { | |
function lock( | |
address owner, | |
address token, | |
uint256 amount, | |
uint256 unlockDate | |
) external payable returns (uint256 id); | |
function unlock(uint256 lockId) external; | |
function editLock( | |
uint256 lockId, | |
uint256 newAmount, | |
uint256 newUnlockDate | |
) external payable; | |
} | |
library Address { | |
function isContract(address account) internal view returns (bool) { | |
uint256 size; | |
assembly { size := extcodesize(account) } | |
return size > 0; | |
} | |
function sendValue(address payable recipient, uint256 amount) internal { | |
require(address(this).balance >= amount, "Address: insufficient balance"); | |
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value | |
(bool success, ) = recipient.call{ value: amount }(""); | |
require(success, "Address: unable to send value, recipient may have reverted"); | |
} | |
function functionCall(address target, bytes memory data) internal returns (bytes memory) { | |
return functionCall(target, data, "Address: low-level call failed"); | |
} | |
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { | |
return functionCallWithValue(target, data, 0, errorMessage); | |
} | |
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { | |
return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); | |
} | |
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { | |
require(address(this).balance >= value, "Address: insufficient balance for call"); | |
require(isContract(target), "Address: call to non-contract"); | |
// solhint-disable-next-line avoid-low-level-calls | |
(bool success, bytes memory returndata) = target.call{ value: value }(data); | |
return _verifyCallResult(success, returndata, errorMessage); | |
} | |
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { | |
return functionStaticCall(target, data, "Address: low-level static call failed"); | |
} | |
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { | |
require(isContract(target), "Address: static call to non-contract"); | |
// solhint-disable-next-line avoid-low-level-calls | |
(bool success, bytes memory returndata) = target.staticcall(data); | |
return _verifyCallResult(success, returndata, errorMessage); | |
} | |
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { | |
return functionDelegateCall(target, data, "Address: low-level delegate call failed"); | |
} | |
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { | |
require(isContract(target), "Address: delegate call to non-contract"); | |
// solhint-disable-next-line avoid-low-level-calls | |
(bool success, bytes memory returndata) = target.delegatecall(data); | |
return _verifyCallResult(success, returndata, errorMessage); | |
} | |
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { | |
if (success) { | |
return returndata; | |
} else { | |
// Look for revert reason and bubble it up if present | |
if (returndata.length > 0) { | |
// The easiest way to bubble the revert reason is using memory via assembly | |
// solhint-disable-next-line no-inline-assembly | |
assembly { | |
let returndata_size := mload(returndata) | |
revert(add(32, returndata), returndata_size) | |
} | |
} else { | |
revert(errorMessage); | |
} | |
} | |
} | |
} | |
library SafeMath { | |
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { | |
uint256 c = a + b; | |
if (c < a) return (false, 0); | |
return (true, c); | |
} | |
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { | |
if (b > a) return (false, 0); | |
return (true, a - b); | |
} | |
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { | |
if (a == 0) return (true, 0); | |
uint256 c = a * b; | |
if (c / a != b) return (false, 0); | |
return (true, c); | |
} | |
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { | |
if (b == 0) return (false, 0); | |
return (true, a / b); | |
} | |
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { | |
if (b == 0) return (false, 0); | |
return (true, a % b); | |
} | |
function add(uint256 a, uint256 b) internal pure returns (uint256) { | |
uint256 c = a + b; | |
require(c >= a, "SafeMath: addition overflow"); | |
return c; | |
} | |
function sub(uint256 a, uint256 b) internal pure returns (uint256) { | |
require(b <= a, "SafeMath: subtraction overflow"); | |
return a - b; | |
} | |
function mul(uint256 a, uint256 b) internal pure returns (uint256) { | |
if (a == 0) return 0; | |
uint256 c = a * b; | |
require(c / a == b, "SafeMath: multiplication overflow"); | |
return c; | |
} | |
function div(uint256 a, uint256 b) internal pure returns (uint256) { | |
require(b > 0, "SafeMath: division by zero"); | |
return a / b; | |
} | |
function mod(uint256 a, uint256 b) internal pure returns (uint256) { | |
require(b > 0, "SafeMath: modulo by zero"); | |
return a % b; | |
} | |
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b <= a, errorMessage); | |
return a - b; | |
} | |
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b > 0, errorMessage); | |
return a / b; | |
} | |
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b > 0, errorMessage); | |
return a % b; | |
} | |
} | |
abstract contract Context { | |
function _msgSender() internal view virtual returns (address payable) { | |
return msg.sender; | |
} | |
function _msgData() internal view virtual returns (bytes memory) { | |
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 | |
return msg.data; | |
} | |
} | |
abstract contract Ownable is Context { | |
address private _owner; | |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
constructor () internal { | |
address msgSender = _msgSender(); | |
_owner = msgSender; | |
emit OwnershipTransferred(address(0), msgSender); | |
} | |
function owner() public view virtual returns (address) { | |
return _owner; | |
} | |
modifier onlyOwner() { | |
require(owner() == _msgSender(), "Ownable: caller is not the owner"); | |
_; | |
} | |
function renounceOwnership() public virtual onlyOwner { | |
emit OwnershipTransferred(_owner, address(0)); | |
_owner = address(0); | |
} | |
function transferOwnership(address newOwner) public virtual onlyOwner { | |
require(newOwner != address(0), "Ownable: new owner is the zero address"); | |
emit OwnershipTransferred(_owner, newOwner); | |
_owner = newOwner; | |
} | |
} | |
interface IERC20 { | |
function totalSupply() external view returns (uint256); | |
function balanceOf(address account) external view returns (uint256); | |
function transfer(address recipient, uint256 amount) external returns (bool); | |
function allowance(address owner, address spender) external view returns (uint256); | |
function approve(address spender, uint256 amount) external returns (bool); | |
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
} | |
library SafeERC20 { | |
using Address for address; | |
using SafeMath for uint256; | |
function safeTransfer(IERC20 token, address to, uint256 value) internal { | |
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); | |
} | |
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { | |
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); | |
} | |
function safeApprove(IERC20 token, address spender, uint256 value) internal { | |
require((value == 0) || (token.allowance(address(this), spender) == 0), | |
"SafeERC20: approve from non-zero to non-zero allowance" | |
); | |
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); | |
} | |
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { | |
uint256 newAllowance = token.allowance(address(this), spender).add(value); | |
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); | |
} | |
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { | |
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); | |
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); | |
} | |
function _callOptionalReturn(IERC20 token, bytes memory data) private { | |
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); | |
if (returndata.length > 0) { // Return data is optional | |
// solhint-disable-next-line max-line-length | |
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); | |
} | |
} | |
} | |
library EnumerableSet { | |
struct Set { | |
// Storage of set values | |
bytes32[] _values; | |
mapping (bytes32 => uint256) _indexes; | |
} | |
function _add(Set storage set, bytes32 value) private returns (bool) { | |
if (!_contains(set, value)) { | |
set._values.push(value); | |
set._indexes[value] = set._values.length; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function _remove(Set storage set, bytes32 value) private returns (bool) { | |
uint256 valueIndex = set._indexes[value]; | |
if (valueIndex != 0) { // Equivalent to contains(set, value) | |
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in | |
// the array, and then remove the last element (sometimes called as 'swap and pop'). | |
// This modifies the order of the array, as noted in {at}. | |
uint256 toDeleteIndex = valueIndex - 1; | |
uint256 lastIndex = set._values.length - 1; | |
// When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs | |
// so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. | |
bytes32 lastvalue = set._values[lastIndex]; | |
// Move the last value to the index where the value to delete is | |
set._values[toDeleteIndex] = lastvalue; | |
// Update the index for the moved value | |
set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based | |
// Delete the slot where the moved value was stored | |
set._values.pop(); | |
// Delete the index for the deleted slot | |
delete set._indexes[value]; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function _contains(Set storage set, bytes32 value) private view returns (bool) { | |
return set._indexes[value] != 0; | |
} | |
function _length(Set storage set) private view returns (uint256) { | |
return set._values.length; | |
} | |
function _at(Set storage set, uint256 index) private view returns (bytes32) { | |
require(set._values.length > index, "EnumerableSet: index out of bounds"); | |
return set._values[index]; | |
} | |
struct Bytes32Set { | |
Set _inner; | |
} | |
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { | |
return _add(set._inner, value); | |
} | |
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { | |
return _remove(set._inner, value); | |
} | |
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { | |
return _contains(set._inner, value); | |
} | |
function length(Bytes32Set storage set) internal view returns (uint256) { | |
return _length(set._inner); | |
} | |
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { | |
return _at(set._inner, index); | |
} | |
// AddressSet | |
struct AddressSet { | |
Set _inner; | |
} | |
function add(AddressSet storage set, address value) internal returns (bool) { | |
return _add(set._inner, bytes32(uint256(uint160(value)))); | |
} | |
function remove(AddressSet storage set, address value) internal returns (bool) { | |
return _remove(set._inner, bytes32(uint256(uint160(value)))); | |
} | |
function contains(AddressSet storage set, address value) internal view returns (bool) { | |
return _contains(set._inner, bytes32(uint256(uint160(value)))); | |
} | |
function length(AddressSet storage set) internal view returns (uint256) { | |
return _length(set._inner); | |
} | |
function at(AddressSet storage set, uint256 index) internal view returns (address) { | |
return address(uint160(uint256(_at(set._inner, index)))); | |
} | |
struct UintSet { | |
Set _inner; | |
} | |
function add(UintSet storage set, uint256 value) internal returns (bool) { | |
return _add(set._inner, bytes32(value)); | |
} | |
function remove(UintSet storage set, uint256 value) internal returns (bool) { | |
return _remove(set._inner, bytes32(value)); | |
} | |
function contains(UintSet storage set, uint256 value) internal view returns (bool) { | |
return _contains(set._inner, bytes32(value)); | |
} | |
function length(UintSet storage set) internal view returns (uint256) { | |
return _length(set._inner); | |
} | |
function at(UintSet storage set, uint256 index) internal view returns (uint256) { | |
return uint256(_at(set._inner, index)); | |
} | |
} | |
contract DexioLock is IDexioLock, Ownable { | |
using Address for address payable; | |
using EnumerableSet for EnumerableSet.AddressSet; | |
using EnumerableSet for EnumerableSet.UintSet; | |
using SafeERC20 for IERC20; | |
struct Lock { | |
uint256 id; | |
address token; | |
address owner; | |
uint256 amount; | |
uint256 lockDate; | |
uint256 unlockDate; | |
} | |
struct CumulativeLockInfo { | |
address token; | |
uint256 amount; | |
} | |
Lock[] private _locks; | |
mapping(address => EnumerableSet.UintSet) | |
private _userNormalLockIds; | |
EnumerableSet.AddressSet private _normalLockedTokens; | |
mapping(address => CumulativeLockInfo) public cumulativeLockInfo; | |
mapping(address => EnumerableSet.UintSet) private _tokenToLockIds; | |
event LockAdded( | |
uint256 indexed id, | |
address indexed token, | |
address indexed owner, | |
uint256 amount, | |
uint256 unlockDate | |
); | |
event LockUpdated( | |
uint256 indexed id, | |
address indexed token, | |
address indexed owner, | |
uint256 newAmount, | |
uint256 newUnlockDate | |
); | |
event LockRemoved( | |
uint256 indexed id, | |
address indexed token, | |
address indexed owner, | |
uint256 amount, | |
uint256 unlockedAt | |
); | |
modifier validLock(uint256 lockId) { | |
require(lockId < _locks.length, "Invalid lock id"); | |
_; | |
} | |
function withdrawFee() external onlyOwner { | |
payable(owner()).sendValue(address(this).balance); | |
} | |
function lock( | |
address owner, | |
address token, | |
uint256 amount, | |
uint256 unlockDate | |
) external payable override returns (uint256 id) { | |
require( | |
unlockDate > block.timestamp, | |
"Unlock date should be after current time" | |
); | |
require(amount > 0, "Amount should be greater than 0"); | |
id = _lockNormalToken(owner, token, amount, unlockDate); | |
safeTransferFromEnsureExactAmount(token, msg.sender, address(this), amount); | |
emit LockAdded(id, token, owner, amount, unlockDate); | |
return id; | |
} | |
function _lockNormalToken( | |
address owner, | |
address token, | |
uint256 amount, | |
uint256 unlockDate | |
) private returns (uint256 id) { | |
id = _addLock(owner, token, amount, unlockDate); | |
_userNormalLockIds[owner].add(id); | |
_normalLockedTokens.add(token); | |
CumulativeLockInfo storage tokenInfo = cumulativeLockInfo[token]; | |
tokenInfo.amount = tokenInfo.amount + amount; | |
_tokenToLockIds[token].add(id); | |
} | |
function _addLock( | |
address owner, | |
address token, | |
uint256 amount, | |
uint256 unlockDate | |
) private returns (uint256 id) { | |
id = _locks.length; | |
Lock memory newLock = Lock({ | |
id: id, | |
token: token, | |
owner: owner, | |
amount: amount, | |
lockDate: block.timestamp, | |
unlockDate: unlockDate | |
}); | |
_locks.push(newLock); | |
} | |
function unlock(uint256 lockId) external override validLock(lockId) { | |
Lock storage userLock = _locks[lockId]; | |
require(userLock.owner == msg.sender, "You are not the owner of this lock"); | |
require(block.timestamp >= userLock.unlockDate, "It is not time to unlock"); | |
require(userLock.amount > 0, "Nothing to unlock"); | |
_userNormalLockIds[msg.sender].remove(lockId); | |
uint256 unlockAmount = userLock.amount; | |
if (IERC20(userLock.token).balanceOf(address(this)) < unlockAmount) { | |
unlockAmount = IERC20(userLock.token).balanceOf(address(this)); | |
} | |
CumulativeLockInfo storage tokenInfo = cumulativeLockInfo[userLock.token]; | |
if (tokenInfo.amount <= unlockAmount) { | |
tokenInfo.amount = 0; | |
} else { | |
tokenInfo.amount = tokenInfo.amount - unlockAmount; | |
} | |
if (tokenInfo.amount == 0) { | |
_normalLockedTokens.remove(userLock.token); | |
} | |
userLock.amount = 0; | |
_tokenToLockIds[userLock.token].remove(userLock.id); | |
IERC20(userLock.token).safeTransfer(msg.sender, unlockAmount); | |
emit LockRemoved( | |
userLock.id, | |
userLock.token, | |
msg.sender, | |
unlockAmount, | |
block.timestamp | |
); | |
} | |
function editLock( | |
uint256 lockId, | |
uint256 newAmount, | |
uint256 newUnlockDate | |
) external payable override validLock(lockId) { | |
Lock storage userLock = _locks[lockId]; | |
require(userLock.owner == msg.sender, "You are not the owner of this lock"); | |
require(userLock.amount > 0, "Lock was unlocked"); | |
if (newUnlockDate > 0) { | |
require( | |
newUnlockDate >= userLock.unlockDate && newUnlockDate > block.timestamp, | |
"New unlock time should not be before old unlock time or current time" | |
); | |
userLock.unlockDate = newUnlockDate; | |
} | |
if (newAmount > 0) { | |
require( | |
newAmount >= userLock.amount, | |
"New amount should not be less than current amount" | |
); | |
uint256 diff = newAmount - userLock.amount; | |
if (diff > 0) { | |
safeTransferFromEnsureExactAmount( | |
userLock.token, | |
msg.sender, | |
address(this), | |
diff | |
); | |
userLock.amount = newAmount; | |
CumulativeLockInfo storage tokenInfo = cumulativeLockInfo[ | |
userLock.token | |
]; | |
tokenInfo.amount = tokenInfo.amount + diff; | |
} | |
} | |
emit LockUpdated( | |
userLock.id, | |
userLock.token, | |
userLock.owner, | |
userLock.amount, | |
userLock.unlockDate | |
); | |
} | |
function safeTransferFromEnsureExactAmount( | |
address token, | |
address sender, | |
address recipient, | |
uint256 amount | |
) internal { | |
uint256 oldRecipientBalance = IERC20(token).balanceOf(recipient); | |
IERC20(token).safeTransferFrom(sender, recipient, amount); | |
uint256 newRecipientBalance = IERC20(token).balanceOf(recipient); | |
require( | |
newRecipientBalance - oldRecipientBalance == amount, | |
"token amount is not enough for transfer" | |
); | |
} | |
function allLocks() external view returns (Lock[] memory) { | |
return _locks; | |
} | |
function getTotalLockCount() external view returns (uint256) { | |
return _locks.length; | |
} | |
function getLock(uint256 index) external view returns (Lock memory) { | |
return _locks[index]; | |
} | |
function allNormalTokenLockedCount() public view returns (uint256) { | |
return _normalLockedTokens.length(); | |
} | |
function getCumulativeNormalTokenLockInfoAt(uint256 index) | |
external | |
view | |
returns (CumulativeLockInfo memory) | |
{ | |
return cumulativeLockInfo[_normalLockedTokens.at(index)]; | |
} | |
function getCumulativeNormalTokenLockInfo(uint256 start, uint256 end) | |
external | |
view | |
returns (CumulativeLockInfo[] memory) | |
{ | |
if (end >= _normalLockedTokens.length()) { | |
end = _normalLockedTokens.length() - 1; | |
} | |
require(start < end, "Start Time is not ahead of End Time"); | |
uint256 length = end - start + 1; | |
CumulativeLockInfo[] memory lockInfo = new CumulativeLockInfo[](length); | |
uint256 currentIndex = 0; | |
for (uint256 i = start; i <= end; i++) { | |
lockInfo[currentIndex] = cumulativeLockInfo[_normalLockedTokens.at(i)]; | |
currentIndex++; | |
} | |
return lockInfo; | |
} | |
function totalTokenLockedCount() external view returns (uint256) { | |
return allNormalTokenLockedCount(); | |
} | |
function normalLockCountForUser(address user) public view returns (uint256) { | |
return _userNormalLockIds[user].length(); | |
} | |
function normalLocksForUser(address user) | |
external | |
view | |
returns (Lock[] memory) | |
{ | |
uint256 length = _userNormalLockIds[user].length(); | |
Lock[] memory userLocks = new Lock[](length); | |
for (uint256 i = 0; i < length; i++) { | |
userLocks[i] = _locks[_userNormalLockIds[user].at(i)]; | |
} | |
return userLocks; | |
} | |
function normalLockForUserAtIndex(address user, uint256 index) | |
external | |
view | |
returns (Lock memory) | |
{ | |
require(normalLockCountForUser(user) > index, "Invalid index"); | |
return _locks[_userNormalLockIds[user].at(index)]; | |
} | |
function totalLockCountForToken(address token) external view returns (uint256) { | |
return _tokenToLockIds[token].length(); | |
} | |
function getLocksForToken( | |
address token, | |
uint256 start, | |
uint256 end | |
) external view returns (Lock[] memory) { | |
if (end >= _tokenToLockIds[token].length()) { | |
end = _tokenToLockIds[token].length() - 1; | |
} | |
require(start < end, "Start time is not ahead of End Time"); | |
uint256 length = end - start + 1; | |
Lock[] memory locks = new Lock[](length); | |
uint256 currentIndex = 0; | |
for (uint256 i = start; i <= end; i++) { | |
locks[currentIndex] = _locks[_tokenToLockIds[token].at(i)]; | |
currentIndex++; | |
} | |
return locks; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment