// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in// construction, since the code is only stored at the end of the// constructor execution.uint256 size;
// solhint-disable-next-line no-inline-assemblyassembly { size :=extcodesize(account) }
return size >0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable 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");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCall(address target, bytesmemory data, stringmemory errorMessage) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value, stringmemory errorMessage) internalreturns (bytesmemory) {
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, bytesmemory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data, stringmemory errorMessage) internalviewreturns (bytesmemory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytesmemory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.3._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.3._
*/functionfunctionDelegateCall(address target, bytesmemory data, stringmemory errorMessage) internalreturns (bytesmemory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytesmemory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function_verifyCallResult(bool success, bytesmemory returndata, stringmemory errorMessage) privatepurereturns(bytesmemory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly// solhint-disable-next-line no-inline-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;import"../interfaces/IERC20.sol";
import"./SafeMath.sol";
import"./Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/librarySafeERC20{
usingSafeMathforuint256;
usingAddressforaddress;
functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
functionsafeTransferFrom(IERC20 token, addressfrom, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/functionsafeApprove(IERC20 token, address spender, uint256 value) internal{
// safeApprove should only be called when setting an initial allowance,// or when resetting it to zero. To increase and decrease it, use// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'// solhint-disable-next-line max-line-lengthrequire((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));
}
functionsafeIncreaseAllowance(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));
}
functionsafeDecreaseAllowance(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));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/function_callOptionalReturn(IERC20 token, bytesmemory data) private{
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that// the target address contains contract code and also asserts for success in the low-level call.bytesmemory returndata =address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length>0) { // Return data is optional// solhint-disable-next-line max-line-lengthrequire(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
Contract Source Code
File 5 of 6: SafeMath.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol// Subject to the MIT license./**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/librarySafeMath{
/**
* @dev Returns the addition of two unsigned integers, reverting on overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/functionadd(uint256 a, uint256 b) internalpurereturns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the addition of two unsigned integers, reverting with custom message on overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/functionadd(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
uint256 c = a + b;
require(c >= a, errorMessage);
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on underflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot underflow.
*/functionsub(uint256 a, uint256 b) internalpurereturns (uint256) {
return sub(a, b, "SafeMath: subtraction underflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on underflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot underflow.
*/functionsub(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/functionmul(uint256 a, uint256 b) internalpurereturns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the// benefit is lost if 'b' is also tested.// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522if (a ==0) {
return0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/functionmul(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the// benefit is lost if 'b' is also tested.// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522if (a ==0) {
return0;
}
uint256 c = a * b;
require(c / a == b, errorMessage);
return c;
}
/**
* @dev Returns the integer division of two unsigned integers.
* Reverts on division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/functiondiv(uint256 a, uint256 b) internalpurereturns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers.
* Reverts with custom message on division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/functiondiv(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
// Solidity only automatically asserts when dividing by 0require(b >0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't holdreturn c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/functionmod(uint256 a, uint256 b) internalpurereturns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/functionmod(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b !=0, errorMessage);
return a % b;
}
}
Contract Source Code
File 6 of 6: Vault.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;pragmaexperimentalABIEncoderV2;import"./interfaces/IERC20.sol";
import"./interfaces/ILockManager.sol";
import"./lib/SafeMath.sol";
import"./lib/SafeERC20.sol";
/**
* @title Vault
* @dev Contract for locking up tokens for set periods of time
* + optionally providing locked tokens with voting power
*/contractVault{
usingSafeMathforuint256;
usingSafeERC20forIERC20;
/// @notice lockManager contract
ILockManager public lockManager;
/// @notice Lock definitionstructLock {
address token;
address receiver;
uint48 startTime;
uint16 vestingDurationInDays;
uint16 cliffDurationInDays;
uint256 amount;
uint256 amountClaimed;
uint256 votingPower;
}
/// @notice Lock balance definitionstructLockBalance {
uint256 id;
uint256 claimableAmount;
Lock lock;
}
///@notice Token balance definitionstructTokenBalance {
uint256 totalAmount;
uint256 claimableAmount;
uint256 claimedAmount;
uint256 votingPower;
}
/// @dev Used to translate lock periods specified in days to secondsuint256constantinternal SECONDS_PER_DAY =86400;
/// @notice Mapping of lock id > token locksmapping (uint256=> Lock) public tokenLocks;
/// @notice Mapping of address to lock idmapping (address=>uint256[]) public lockIds;
///@notice Number of locksuint256public numLocks;
/// @notice Event emitted when a new lock is createdeventLockCreated(addressindexed token, addressindexed locker, addressindexed receiver, uint256 lockId, uint256 amount, uint48 startTime, uint16 durationInDays, uint16 cliffInDays, uint256 votingPower);
/// @notice Event emitted when tokens are claimed by a receiver from an unlocked balanceeventUnlockedTokensClaimed(addressindexed receiver, addressindexed token, uint256indexed lockId, uint256 amountClaimed, uint256 votingPowerRemoved);
/// @notice Event emitted when lock duration extendedeventLockExtended(uint256indexed lockId, uint16indexed oldDuration, uint16indexed newDuration, uint16 oldCliff, uint16 newCliff, uint48 startTime);
/**
* @notice Create a new Vault contract
*/constructor(address _lockManager) {
lockManager = ILockManager(_lockManager);
}
/**
* @notice Lock tokens, optionally providing voting power
* @param locker The account that is locking tokens
* @param receiver The account that will be able to retrieve unlocked tokens
* @param startTime The unix timestamp when the lock period will start
* @param amount The amount of tokens being locked
* @param vestingDurationInDays The vesting period in days
* @param cliffDurationInDays The cliff duration in days
* @param grantVotingPower if true, give user voting power from tokens
*/functionlockTokens(address token,
address locker,
address receiver,
uint48 startTime,
uint256 amount,
uint16 vestingDurationInDays,
uint16 cliffDurationInDays,
bool grantVotingPower
)
external{
require(vestingDurationInDays >0, "Vault::lockTokens: vesting duration must be > 0");
require(vestingDurationInDays <=25*365, "Vault::lockTokens: vesting duration more than 25 years");
require(vestingDurationInDays >= cliffDurationInDays, "Vault::lockTokens: vesting duration < cliff");
require(amount >0, "Vault::lockTokens: amount not > 0");
_lockTokens(token, locker, receiver, startTime, amount, vestingDurationInDays, cliffDurationInDays, grantVotingPower);
}
/**
* @notice Lock tokens, using permit for approval
* @dev It is up to the frontend developer to ensure the token implements permit - otherwise this will fail
* @param token Address of token to lock
* @param locker The account that is locking tokens
* @param receiver The account that will be able to retrieve unlocked tokens
* @param startTime The unix timestamp when the lock period will start
* @param amount The amount of tokens being locked
* @param vestingDurationInDays The lock period in days
* @param cliffDurationInDays The lock cliff duration in days
* @param grantVotingPower if true, give user voting power from tokens
* @param deadline The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/functionlockTokensWithPermit(address token,
address locker,
address receiver,
uint48 startTime,
uint256 amount,
uint16 vestingDurationInDays,
uint16 cliffDurationInDays,
bool grantVotingPower,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external{
require(vestingDurationInDays >0, "Vault::lockTokensWithPermit: vesting duration must be > 0");
require(vestingDurationInDays <=25*365, "Vault::lockTokensWithPermit: vesting duration more than 25 years");
require(vestingDurationInDays >= cliffDurationInDays, "Vault::lockTokensWithPermit: duration < cliff");
require(amount >0, "Vault::lockTokensWithPermit: amount not > 0");
// Set approval using permit signature
IERC20(token).permit(locker, address(this), amount, deadline, v, r, s);
_lockTokens(token, locker, receiver, startTime, amount, vestingDurationInDays, cliffDurationInDays, grantVotingPower);
}
/**
* @notice Get all active token lock ids
* @return the lock ids
*/functionallActiveLockIds() externalviewreturns(uint256[] memory){
uint256 activeCount;
// Get number of active locksfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`uint256[] memory result =newuint256[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
result[j] = i;
j++;
}
}
return result;
}
/**
* @notice Get all active token locks
* @return the locks
*/functionallActiveLocks() externalviewreturns(Lock[] memory){
uint256 activeCount;
// Get number of active locksfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`
Lock[] memory result =new Lock[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
result[j] = lock;
j++;
}
}
return result;
}
/**
* @notice Get all active token lock balances
* @return the active lock balances
*/functionallActiveLockBalances() externalviewreturns(LockBalance[] memory){
uint256 activeCount;
// Get number of active locksfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`
LockBalance[] memory result =new LockBalance[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < numLocks; i++) {
Lock memory lock = tokenLocks[i];
if(lock.amount != lock.amountClaimed) {
result[j] = lockBalance(i);
j++;
}
}
return result;
}
/**
* @notice Get all active token lock ids for receiver
* @param receiver The address that has locked balances
* @return the active lock ids
*/functionactiveLockIds(address receiver) externalviewreturns(uint256[] memory){
uint256 activeCount;
uint256[] memory receiverLockIds = lockIds[receiver];
// Get number of active locksfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`uint256[] memory result =newuint256[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
result[j] = receiverLockIds[i];
j++;
}
}
return result;
}
/**
* @notice Get all token locks for receiver
* @param receiver The address that has locked balances
* @return the locks
*/functionallLocks(address receiver) externalviewreturns(Lock[] memory){
uint256[] memory allLockIds = lockIds[receiver];
Lock[] memory result =new Lock[](allLockIds.length);
for (uint256 i; i < allLockIds.length; i++) {
result[i] = tokenLocks[allLockIds[i]];
}
return result;
}
/**
* @notice Get all active token locks for receiver
* @param receiver The address that has locked balances
* @return the locks
*/functionactiveLocks(address receiver) externalviewreturns(Lock[] memory){
uint256 activeCount;
uint256[] memory receiverLockIds = lockIds[receiver];
// Get number of active locksfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`
Lock[] memory result =new Lock[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
result[j] = tokenLocks[receiverLockIds[i]];
j++;
}
}
return result;
}
/**
* @notice Get all active token lock balances for receiver
* @param receiver The address that has locked balances
* @return the active lock balances
*/functionactiveLockBalances(address receiver) externalviewreturns(LockBalance[] memory){
uint256 activeCount;
uint256[] memory receiverLockIds = lockIds[receiver];
// Get number of active locksfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
activeCount++;
}
}
// Create result array of length `activeCount`
LockBalance[] memory result =new LockBalance[](activeCount);
uint256 j;
// Populate result arrayfor (uint256 i; i < receiverLockIds.length; i++) {
Lock memory lock = tokenLocks[receiverLockIds[i]];
if(lock.amount != lock.amountClaimed) {
result[j] = lockBalance(receiverLockIds[i]);
j++;
}
}
return result;
}
/**
* @notice Get total token balance
* @param token The token to check
* @return balance the total active balance of `token`
*/functiontotalTokenBalance(address token) externalviewreturns(TokenBalance memory balance){
for (uint256 i; i < numLocks; i++) {
Lock memory tokenLock = tokenLocks[i];
if(tokenLock.token == token && tokenLock.amount != tokenLock.amountClaimed){
balance.totalAmount = balance.totalAmount.add(tokenLock.amount);
balance.votingPower = balance.votingPower.add(tokenLock.votingPower);
if(block.timestamp> tokenLock.startTime) {
balance.claimedAmount = balance.claimedAmount.add(tokenLock.amountClaimed);
uint256 elapsedTime =block.timestamp.sub(tokenLock.startTime);
uint256 elapsedDays = elapsedTime.div(SECONDS_PER_DAY);
if (
elapsedDays >= tokenLock.cliffDurationInDays
) {
if (elapsedDays >= tokenLock.vestingDurationInDays) {
balance.claimableAmount = balance.claimableAmount.add(tokenLock.amount).sub(tokenLock.amountClaimed);
} else {
uint256 vestingDurationInSecs =uint256(tokenLock.vestingDurationInDays).mul(SECONDS_PER_DAY);
uint256 vestingAmountPerSec = tokenLock.amount.div(vestingDurationInSecs);
uint256 amountVested = vestingAmountPerSec.mul(elapsedTime);
balance.claimableAmount = balance.claimableAmount.add(amountVested.sub(tokenLock.amountClaimed));
}
}
}
}
}
}
/**
* @notice Get token balance of receiver
* @param token The token to check
* @param receiver The address that has unlocked balances
* @return balance the total active balance of `token` for `receiver`
*/functiontokenBalance(address token, address receiver) externalviewreturns(TokenBalance memory balance){
uint256[] memory receiverLockIds = lockIds[receiver];
for (uint256 i; i < receiverLockIds.length; i++) {
Lock memory receiverLock = tokenLocks[receiverLockIds[i]];
if(receiverLock.token == token && receiverLock.amount != receiverLock.amountClaimed){
balance.totalAmount = balance.totalAmount.add(receiverLock.amount);
balance.votingPower = balance.votingPower.add(receiverLock.votingPower);
if(block.timestamp> receiverLock.startTime) {
balance.claimedAmount = balance.claimedAmount.add(receiverLock.amountClaimed);
uint256 elapsedTime =block.timestamp.sub(receiverLock.startTime);
uint256 elapsedDays = elapsedTime.div(SECONDS_PER_DAY);
if (
elapsedDays >= receiverLock.cliffDurationInDays
) {
if (elapsedDays >= receiverLock.vestingDurationInDays) {
balance.claimableAmount = balance.claimableAmount.add(receiverLock.amount).sub(receiverLock.amountClaimed);
} else {
uint256 vestingDurationInSecs =uint256(receiverLock.vestingDurationInDays).mul(SECONDS_PER_DAY);
uint256 vestingAmountPerSec = receiverLock.amount.div(vestingDurationInSecs);
uint256 amountVested = vestingAmountPerSec.mul(elapsedTime);
balance.claimableAmount = balance.claimableAmount.add(amountVested.sub(receiverLock.amountClaimed));
}
}
}
}
}
}
/**
* @notice Get lock balance for a given lock id
* @param lockId The lock ID
* @return balance the lock balance
*/functionlockBalance(uint256 lockId) publicviewreturns (LockBalance memory balance) {
balance.id = lockId;
balance.claimableAmount = claimableBalance(lockId);
balance.lock = tokenLocks[lockId];
}
/**
* @notice Get claimable balance for a given lock id
* @dev Returns 0 if cliff duration has not ended
* @param lockId The lock ID
* @return The amount that can be claimed
*/functionclaimableBalance(uint256 lockId) publicviewreturns (uint256) {
Lock storage lock = tokenLocks[lockId];
// For locks created with a future start date, that hasn't been reached, return 0if (block.timestamp< lock.startTime) {
return0;
}
uint256 elapsedTime =block.timestamp.sub(lock.startTime);
uint256 elapsedDays = elapsedTime.div(SECONDS_PER_DAY);
if (elapsedDays < lock.cliffDurationInDays) {
return0;
}
if (elapsedDays >= lock.vestingDurationInDays) {
return lock.amount.sub(lock.amountClaimed);
} else {
uint256 vestingDurationInSecs =uint256(lock.vestingDurationInDays).mul(SECONDS_PER_DAY);
uint256 vestingAmountPerSec = lock.amount.div(vestingDurationInSecs);
uint256 amountVested = vestingAmountPerSec.mul(elapsedTime);
return amountVested.sub(lock.amountClaimed);
}
}
/**
* @notice Allows receiver to claim all of their unlocked tokens for a set of locks
* @dev Errors if no tokens are claimable
* @dev It is advised receivers check they are entitled to claim via `claimableBalance` before calling this
* @param locks The lock ids for unlocked token balances
*/functionclaimAllUnlockedTokens(uint256[] memory locks) external{
for (uint i =0; i < locks.length; i++) {
uint256 claimableAmount = claimableBalance(locks[i]);
require(claimableAmount >0, "Vault::claimAllUnlockedTokens: claimableAmount is 0");
_claimTokens(locks[i], claimableAmount);
}
}
/**
* @notice Allows receiver to claim a portion of their unlocked tokens for a given lock
* @dev Errors if token amounts provided are > claimable amounts
* @dev It is advised receivers check they are entitled to claim via `claimableBalance` before calling this
* @param locks The lock ids for unlocked token balances
* @param amounts The amount of each unlocked token to claim
*/functionclaimUnlockedTokenAmounts(uint256[] memory locks, uint256[] memory amounts) external{
require(locks.length== amounts.length, "Vault::claimUnlockedTokenAmounts: arrays must be same length");
for (uint i =0; i < locks.length; i++) {
uint256 claimableAmount = claimableBalance(locks[i]);
require(claimableAmount >= amounts[i], "Vault::claimUnlockedTokenAmounts: claimableAmount < amount");
_claimTokens(locks[i], amounts[i]);
}
}
/**
* @notice Allows receiver extend lock periods for a given lock
* @param lockId The lock id for a locked token balance
* @param vestingDaysToAdd The number of days to add to vesting duration
* @param cliffDaysToAdd The number of days to add to cliff duration
*/functionextendLock(uint256 lockId, uint16 vestingDaysToAdd, uint16 cliffDaysToAdd) external{
Lock storage lock = tokenLocks[lockId];
require(msg.sender== lock.receiver, "Vault::extendLock: msg.sender must be receiver");
uint16 oldVestingDuration = lock.vestingDurationInDays;
uint16 newVestingDuration = _add16(oldVestingDuration, vestingDaysToAdd, "Vault::extendLock: vesting max days exceeded");
uint16 oldCliffDuration = lock.cliffDurationInDays;
uint16 newCliffDuration = _add16(oldCliffDuration, cliffDaysToAdd, "Vault::extendLock: cliff max days exceeded");
require(newCliffDuration <=10*365, "Vault::extendLock: cliff more than 10 years");
require(newVestingDuration <=25*365, "Vault::extendLock: vesting duration more than 25 years");
require(newVestingDuration >= newCliffDuration, "Vault::extendLock: duration < cliff");
lock.vestingDurationInDays = newVestingDuration;
emit LockExtended(lockId, oldVestingDuration, newVestingDuration, oldCliffDuration, newCliffDuration, lock.startTime);
}
/**
* @notice Internal implementation of lockTokens
* @param locker The account that is locking tokens
* @param receiver The account that will be able to retrieve unlocked tokens
* @param startTime The unix timestamp when the lock period will start
* @param amount The amount of tokens being locked
* @param vestingDurationInDays The vesting period in days
* @param cliffDurationInDays The cliff duration in days
* @param grantVotingPower if true, give user voting power from tokens
*/function_lockTokens(address token,
address locker,
address receiver,
uint48 startTime,
uint256 amount,
uint16 vestingDurationInDays,
uint16 cliffDurationInDays,
bool grantVotingPower
) internal{
// Transfer the tokens under the control of the vault contract
IERC20(token).safeTransferFrom(locker, address(this), amount);
uint48 lockStartTime = startTime ==0 ? uint48(block.timestamp) : startTime;
uint256 votingPowerGranted;
// Grant voting power, if specifiedif(grantVotingPower) {
votingPowerGranted = lockManager.grantVotingPower(receiver, token, amount);
}
// Create lock
Lock memory lock = Lock({
token: token,
receiver: receiver,
startTime: lockStartTime,
vestingDurationInDays: vestingDurationInDays,
cliffDurationInDays: cliffDurationInDays,
amount: amount,
amountClaimed: 0,
votingPower: votingPowerGranted
});
tokenLocks[numLocks] = lock;
lockIds[receiver].push(numLocks);
emit LockCreated(token, locker, receiver, numLocks, amount, lockStartTime, vestingDurationInDays, cliffDurationInDays, votingPowerGranted);
// Increment lock id
numLocks++;
}
/**
* @notice Internal implementation of token claims
* @param lockId The lock id for claim
* @param claimAmount The amount to claim
*/function_claimTokens(uint256 lockId, uint256 claimAmount) internal{
Lock storage lock = tokenLocks[lockId];
uint256 votingPowerRemoved;
// Remove voting power, if existsif (lock.votingPower >0) {
votingPowerRemoved = lockManager.removeVotingPower(lock.receiver, lock.token, claimAmount);
lock.votingPower = lock.votingPower.sub(votingPowerRemoved);
}
// Update claimed amount
lock.amountClaimed = lock.amountClaimed.add(claimAmount);
// Release tokens
IERC20(lock.token).safeTransfer(lock.receiver, claimAmount);
emit UnlockedTokensClaimed(lock.receiver, lock.token, lockId, claimAmount, votingPowerRemoved);
}
/**
* @notice Adds uint16 to uint16 safely
* @param a First number
* @param b Second number
* @param errorMessage Error message to use if numbers cannot be added
* @return uint16 number
*/function_add16(uint16 a, uint16 b, stringmemory errorMessage) internalpurereturns (uint16) {
uint16 c = a + b;
require(c >= a, errorMessage);
return c;
}
}