Contract Name:
SynapseVesting
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import { ReentrancyGuard } from "./external/openzeppelin/ReentrancyGuard.sol";
import { StableMath } from "./libraries/StableMath.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
import { Ownable } from "./abstract/Ownable.sol";
import { Lockable } from "./abstract/Lockable.sol";
/**
* @title SynapseVesting
* @notice Synapse Network Vesting contract
* @dev Vesting is constantly releasing tokens every block every second
*/
contract SynapseVesting is Ownable, Lockable, ReentrancyGuard {
using StableMath for uint256;
/// @notice address of Synapse Network token
address public snpToken;
/// @notice total tokens vested in contract
/// @dev tokens from not initialized sale contracts are not included
uint256 public totalVested;
/// @notice total tokens already claimed form vesting
uint256 public totalClaimed;
/// @notice staking contract address
/// @dev set by Owner, for claimAndStake
address public stakingAddress;
struct Vest {
uint256 dateStart; // start of claiming, can claim startTokens
uint256 dateEnd; // after it all tokens can be claimed
uint256 totalTokens; // total tokens to claim
uint256 startTokens; // tokens to claim on start
uint256 claimedTokens; // tokens already claimed
}
/// @notice storage of vestings
Vest[] internal vestings;
/// @notice map of vestings for user
mapping(address => uint256[]) internal user2vesting;
struct SaleContract {
address[] contractAddresses; // list of cross sale contracts from sale round
uint256 tokensPerCent; // amount of tokens per cent for sale round
uint256 maxAmount; // max amount in USD cents for sale round
uint256 percentOnStart; // percent of tokens to claim on start
uint256 startDate; // start of claiming, can claim start tokens
uint256 endDate; // after it all tokens can be claimed
}
/// @notice list of sale contract that will be checked
SaleContract[] internal saleContracts;
/// @notice map of users that initialized vestings from sale contracts
mapping(address => bool) public vestingAdded;
/// @notice map of users that were refunded after sales
mapping(address => bool) public refunded;
/// @dev events
event Claimed(address indexed user, uint256 amount);
event Vested(address indexed user, uint256 totalAmount, uint256 endDate);
/**
* @dev Contract initiator
* @param _token address of SNP token
*/
function init(address _token) external onlyOwner {
require(_token != address(0), "_token address cannot be 0");
require(snpToken == address(0), "Init already done");
snpToken = _token;
}
/**
* @dev Add multiple vesting to contract by arrays of data
* @param _users[] addresses of holders
* @param _startTokens[] tokens that can be withdrawn at startDate
* @param _totalTokens[] total tokens in vesting
* @param _startDate date from when tokens can be claimed
* @param _endDate date after which all tokens can be claimed
*/
function massAddHolders(
address[] calldata _users,
uint256[] calldata _startTokens,
uint256[] calldata _totalTokens,
uint256 _startDate,
uint256 _endDate
) external onlyOwner whenNotLocked {
uint256 len = _users.length; //cheaper to use one variable
require((len == _startTokens.length) && (len == _totalTokens.length), "data size mismatch");
require(_startDate < _endDate, "startDate cannot exceed endDate");
uint256 i;
for (i; i < len; i++) {
_addHolder(_users[i], _startTokens[i], _totalTokens[i], _startDate, _endDate);
}
}
/**
* @dev Add new vesting to contract
* @param _user address of a holder
* @param _startTokens how many tokens are claimable at start date
* @param _totalTokens total number of tokens in added vesting
* @param _startDate date from when tokens can be claimed
* @param _endDate date after which all tokens can be claimed
*/
function _addHolder(
address _user,
uint256 _startTokens,
uint256 _totalTokens,
uint256 _startDate,
uint256 _endDate
) internal {
require(_user != address(0), "user address cannot be 0");
Vest memory v;
v.startTokens = _startTokens;
v.totalTokens = _totalTokens;
v.dateStart = _startDate;
v.dateEnd = _endDate;
totalVested += _totalTokens;
vestings.push(v);
user2vesting[_user].push(vestings.length); // we are skipping index "0" for reasons
emit Vested(_user, _totalTokens, _endDate);
}
/**
* @dev Claim tokens from msg.sender vestings
*/
function claim() external {
_claim(msg.sender, msg.sender);
}
/**
* @dev Claim tokens from msg.sender vestings to external address
* @param _target transfer address for claimed tokens
*/
function claimTo(address _target) external {
_claim(msg.sender, _target);
}
/**
* @dev Claim and stake claimed tokens directly in staking contract
* Ask staking contract if user is not in withdrawing state
*/
function claimAndStake() external {
require(stakingAddress != address(0), "Staking contract not configured");
require(IStaking(stakingAddress).canStakeTokens(msg.sender), "Unable to stake");
uint256 amt = _claim(msg.sender, stakingAddress);
IStaking(stakingAddress).onClaimAndStake(msg.sender, amt);
}
/**
* @dev internal claim function
* @param _user address of holder
* @param _target where tokens should be send
* @return amt number of tokens claimed
*/
function _claim(address _user, address _target) internal nonReentrant returns (uint256 amt) {
require(_target != address(0), "Claim, then burn");
if (!vestingAdded[_user] && !refunded[_user]) {
_addVesting(_user);
}
uint256 len = user2vesting[_user].length;
require(len > 0, "No vestings for user");
uint256 cl;
uint256 i;
for (i; i < len; i++) {
Vest storage v = vestings[user2vesting[_user][i] - 1];
cl = _claimable(v);
v.claimedTokens += cl;
amt += cl;
}
if (amt > 0) {
totalClaimed += amt;
_transfer(_target, amt);
emit Claimed(_user, amt);
} else revert("Nothing to claim");
}
/**
* @dev Internal function to send out claimed tokens
* @param _user address that we send tokens
* @param _amt amount of tokens
*/
function _transfer(address _user, uint256 _amt) internal {
require(IERC20(snpToken).transfer(_user, _amt), "Token transfer failed");
}
/**
* @dev Count how many tokens can be claimed from vesting to date
* @param _vesting Vesting object
* @return canWithdraw number of tokens
*/
function _claimable(Vest memory _vesting) internal view returns (uint256 canWithdraw) {
uint256 currentTime = block.timestamp;
if (_vesting.dateStart > currentTime) return 0;
// we are somewhere in the middle
if (currentTime < _vesting.dateEnd) {
// how much time passed (as fraction * 10^18)
// timeRatio = (time passed * 1e18) / duration
uint256 timeRatio = (currentTime - _vesting.dateStart).divPrecisely(_vesting.dateEnd - _vesting.dateStart);
// how much tokens we can get in total to date
canWithdraw = (_vesting.totalTokens - _vesting.startTokens).mulTruncate(timeRatio) + _vesting.startTokens;
}
// time has passed, we can take all tokens
else {
canWithdraw = _vesting.totalTokens;
}
// but maybe we take something earlier?
canWithdraw -= _vesting.claimedTokens;
}
/**
* @dev Read number of claimable tokens by user and vesting no
* @param _user address of holder
* @param _id his vesting number (starts from 0)
* @return amount number of tokens
*/
function getClaimable(address _user, uint256 _id) external view returns (uint256 amount) {
amount = _claimable(vestings[user2vesting[_user][_id] - 1]);
}
/**
* @dev Read total amount of tokens that user can claim to date from all vestings
* Function also includes tokens to claim from sale contracts that were not
* yet initiated for user.
* @param _user address of holder
* @return amount number of tokens
*/
function getAllClaimable(address _user) public view returns (uint256 amount) {
uint256 len = user2vesting[_user].length;
uint256 i;
for (i; i < len; i++) {
amount += _claimable(vestings[user2vesting[_user][i] - 1]);
}
if (!vestingAdded[_user]) {
amount += _claimableFromSaleContracts(_user);
}
}
/**
* @dev Extract all the vestings for the user
* Also extract not initialized vestings from
* sale contracts.
* @param _user address of holder
* @return v array of Vest objects
*/
function getVestings(address _user) external view returns (Vest[] memory) {
// array of pending vestings
Vest[] memory pV;
if (!vestingAdded[_user]) {
pV = _vestingsFromSaleContracts(_user);
}
uint256 pLen = pV.length;
uint256 len = user2vesting[_user].length;
Vest[] memory v = new Vest[](len + pLen);
// copy normal vestings
uint256 i;
for (i; i < len; i++) {
v[i] = vestings[user2vesting[_user][i] - 1];
}
// copy not initialized vestings
if (!vestingAdded[_user]) {
uint256 j;
for (j; j < pLen; j++) {
v[i + j] = pV[j];
}
}
return v;
}
/**
* @dev Read total number of vestings registered
* @return number of registered vestings on contract
*/
function getVestingsCount() external view returns (uint256) {
return vestings.length;
}
/**
* @dev Read single registered vesting entry
* @param _id index of vesting in storage
* @return Vest object
*/
function getVestingByIndex(uint256 _id) external view returns (Vest memory) {
return vestings[_id];
}
/**
* @dev Read registered vesting list by range from-to
* @param _start first index
* @param _end last index
* @return array of Vest objects
*/
function getVestingsByRange(uint256 _start, uint256 _end) external view returns (Vest[] memory) {
uint256 cnt = _end - _start + 1;
uint256 len = vestings.length;
require(_end < len, "range error");
Vest[] memory v = new Vest[](cnt);
uint256 i;
for (i; i < cnt; i++) {
v[i] = vestings[_start + i];
}
return v;
}
/**
* @dev Extract all sale contracts
* @return array of SaleContract objects
*/
function getSaleContracts() external view returns (SaleContract[] memory) {
return saleContracts;
}
/**
* @dev Read total number of sale contracts
* @return number of SaleContracts
*/
function getSaleContractsCount() external view returns (uint256) {
return saleContracts.length;
}
/**
* @dev Read single sale contract entry
* @param _id index of sale contract in storage
* @return SaleContract object
*/
function getSaleContractByIndex(uint256 _id) external view returns (SaleContract memory) {
return saleContracts[_id];
}
/**
* @dev Register sale contract
* @param _contractAddresses addresses of sale contracts
* @param _tokensPerCent sale price
* @param _maxAmount the maximum amount in USD cents for which user could buy
* @param _percentOnStart percentage of vested coins that can be claimed on start date
* @param _startDate date when initial vesting can be released
* @param _endDate final date of vesting, where all tokens can be claimed
*/
function addSaleContract(
address[] memory _contractAddresses,
uint256 _tokensPerCent,
uint256 _maxAmount,
uint256 _percentOnStart,
uint256 _startDate,
uint256 _endDate
) external onlyOwner whenNotLocked {
require(_contractAddresses.length > 0, "data is missing");
require(_startDate < _endDate, "startDate cannot exceed endDate");
SaleContract memory s;
s.contractAddresses = _contractAddresses;
s.tokensPerCent = _tokensPerCent;
s.maxAmount = _maxAmount;
s.startDate = _startDate;
s.percentOnStart = _percentOnStart;
s.endDate = _endDate;
saleContracts.push(s);
}
/**
* @dev Initialize vestings from sale contracts for msg.sender
*/
function addMyVesting() external {
_addVesting(msg.sender);
}
/**
* @dev Initialize vestings from sale contracts for target user
* @param _user address of user that will be initialized
*/
function addVesting(address _user) external {
require(_user != address(0), "User address cannot be 0");
_addVesting(_user);
}
/**
* @dev Function iterate sale contracts and initialize corresponding
* vesting for user.
* @param _user address that will be initialized
*/
function _addVesting(address _user) internal {
require(!refunded[_user], "User refunded");
require(!vestingAdded[_user], "Already done");
uint256 len = saleContracts.length;
uint256 i;
for (i; i < len; i++) {
SaleContract memory s = saleContracts[i];
uint256 sLen = s.contractAddresses.length;
uint256 j;
uint256 amt;
for (j; j < sLen; j++) {
amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
}
// amt is in cents, so $100 = 10000
if (amt > 0) {
if (amt > s.maxAmount) {
amt = s.maxAmount;
}
// create Vest object
Vest memory v = _vestFromSaleContractAndAmount(s, amt);
// update contract data
totalVested += v.totalTokens;
vestings.push(v);
user2vesting[_user].push(vestings.length);
emit Vested(_user, v.totalTokens, v.dateEnd);
}
}
vestingAdded[_user] = true;
}
/**
* @dev Function iterate sale contracts and count claimable amounts for given user.
* Used to calculate claimable amounts from not initialized vestings.
* @param _user address of user to count claimable
* @return claimable amount of tokens
*/
function _claimableFromSaleContracts(address _user) internal view returns (uint256 claimable) {
if (refunded[_user]) return 0;
uint256 len = saleContracts.length;
if (len == 0) return 0;
uint256 i;
for (i; i < len; i++) {
SaleContract memory s = saleContracts[i];
uint256 sLen = s.contractAddresses.length;
uint256 j;
uint256 amt;
for (j; j < sLen; j++) {
amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
}
// amt is in cents, so $100 = 10000
if (amt > 0) {
if (amt > s.maxAmount) {
amt = s.maxAmount;
}
claimable += _claimable(_vestFromSaleContractAndAmount(s, amt));
}
}
}
/**
* @dev Function iterate sale contracts and extract not initialized user vestings.
* Used to return all stored and not initialized vestings.
* @param _user address of user to extract vestings
* @return v vesting array
*/
function _vestingsFromSaleContracts(address _user) internal view returns (Vest[] memory) {
uint256 len = saleContracts.length;
if (refunded[_user] || len == 0) return new Vest[](0);
Vest[] memory v = new Vest[](_numberOfVestingsFromSaleContracts(_user));
uint256 i;
uint256 idx;
for (i; i < len; i++) {
SaleContract memory s = saleContracts[i];
uint256 sLen = s.contractAddresses.length;
uint256 j;
uint256 amt;
for (j; j < sLen; j++) {
amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
}
// amt is in cents, so $100 = 10000
if (amt > 0) {
if (amt > s.maxAmount) {
amt = s.maxAmount;
}
v[idx] = _vestFromSaleContractAndAmount(s, amt);
idx++;
}
}
return v;
}
/**
* @dev Function iterate sale contracts and return number of not initialized vestings for user.
* @param _user address of user to extract vestings
* @return number of not not initialized user vestings
*/
function _numberOfVestingsFromSaleContracts(address _user) internal view returns (uint256 number) {
uint256 len = saleContracts.length;
uint256 i;
for (i; i < len; i++) {
SaleContract memory s = saleContracts[i];
uint256 sLen = s.contractAddresses.length;
uint256 j;
uint256 amt;
for (j; j < sLen; j++) {
amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
}
// amt is in cents, so $100 = 10000
if (amt > 0) {
number++;
}
}
}
/**
* @dev Return vesting created from given sale and usd cent amount.
* @param _sale address of user to extract vestings
* @param _amount address of user to extract vestings
* @return v vesting from given parameters
*/
function _vestFromSaleContractAndAmount(SaleContract memory _sale, uint256 _amount) internal pure returns (Vest memory v) {
v.dateStart = _sale.startDate;
v.dateEnd = _sale.endDate;
uint256 total = _amount * _sale.tokensPerCent;
v.totalTokens = total;
v.startTokens = (total * _sale.percentOnStart) / 100;
}
/**
* @dev Set staking contract address for Claim and Stake.
* Only contract owner can set.
* @param _staking address
*/
function setStakingAddress(address _staking) external onlyOwner {
stakingAddress = _staking;
}
/**
* @dev Mark user as refunded
* @param _user address of user
* @param _refunded true=refunded
*/
function setRefunded(address _user, bool _refunded) external onlyOwner whenNotLocked {
require(_user != address(0), "user address cannot be 0");
refunded[_user] = _refunded;
}
/**
* @dev Mark multiple refunded users
* @param _users[] addresses of refunded users
*/
function massSetRefunded(address[] calldata _users) external onlyOwner whenNotLocked {
uint256 i;
for (i; i < _users.length; i++) {
require(_users[i] != address(0), "user address cannot be 0");
refunded[_users[i]] = true;
}
}
/**
* @dev Recover ETH from contract to owner address.
*/
function recoverETH() external {
payable(owner).transfer(address(this).balance);
}
/**
* @dev Recover given ERC20 token from contract to owner address.
* Can't recover SNP tokens.
* @param _token address of ERC20 token to recover
*/
function recoverErc20(address _token) external {
require(_token != snpToken, "Not permitted");
uint256 amt = IERC20(_token).balanceOf(address(this));
require(amt > 0, "Nothing to recover");
IBadErc20(_token).transfer(owner, amt);
}
}
/**
* @title IStaking
* @dev Interface for claim and stake
*/
interface IStaking {
function canStakeTokens(address _account) external view returns (bool);
function onClaimAndStake(address _from, uint256 _amount) external;
}
/**
* @title ISaleContract
* @dev Interface for sale contract
*/
interface ISaleContract {
function balanceOf(address _account) external view returns (uint256);
}
/**
* @title IBadErc20
* @dev Interface for emergency recover any ERC20-tokens,
* even non-erc20-compliant like USDT not returning boolean
*/
interface IBadErc20 {
function transfer(address _recipient, uint256 _amount) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import { Ownable } from "./Ownable.sol";
abstract contract LockableData {
bool public locked;
}
abstract contract Lockable is LockableData, Ownable {
/**
* @dev Locks functions with whenNotLocked modifier
*/
function lock() external onlyOwner {
locked = true;
}
/**
* @dev Throws if called after it was locked.
*/
modifier whenNotLocked {
require(!locked, "Lockable: locked");
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
abstract contract OwnableData {
address public owner;
address public pendingOwner;
}
abstract contract Ownable is OwnableData {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev `owner` defaults to msg.sender on construction.
*/
constructor() {
_setOwner(msg.sender);
}
/**
* @dev Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
* Can only be invoked by the current `owner`.
* @param _newOwner Address of the new owner.
* @param _direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
*/
function transferOwnership(address _newOwner, bool _direct) external onlyOwner {
if (_direct) {
require(_newOwner != address(0), "zero address");
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
pendingOwner = address(0);
} else {
pendingOwner = _newOwner;
}
}
/**
* @dev Needs to be called by `pendingOwner` to claim ownership.
*/
function claimOwnership() external {
address _pendingOwner = pendingOwner;
require(msg.sender == _pendingOwner, "caller != pending owner");
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = address(0);
}
/**
* @dev Throws if called by any account other than the Owner.
*/
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function _setOwner(address newOwner) internal {
address oldOwner = owner;
owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
// EIP 2612
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
function nonces(address owner) external view returns (uint256);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
function transferWithPermit(address target, address to, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
// Based on StableMath from mStable
// https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol
library StableMath {
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x) internal pure returns (uint256) {
return x * FULL_SCALE;
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(
uint256 x,
uint256 y,
uint256 scale
) internal pure returns (uint256) {
// e.g. assume scale = fullScale
// z = 10e18 * 9e17 = 9e36
// return 9e36 / 1e18 = 9e18
return (x * y) / scale;
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y) internal pure returns (uint256) {
// e.g. 8e17 * 17268172638 = 138145381104e17
uint256 scaled = x * y;
// e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
uint256 ceil = scaled + FULL_SCALE - 1;
// e.g. 13814538111.399...e18 / 1e18 = 13814538111
return ceil / FULL_SCALE;
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y) internal pure returns (uint256) {
// e.g. 8e18 * 1e18 = 8e36
// e.g. 8e36 / 10e18 = 8e17
return (x * FULL_SCALE) / y;
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound) internal pure returns (uint256) {
return x > upperBound ? upperBound : x;
}
}