Contract Source Code:
File 1 of 1 : Locker
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(owner);
owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(address _newOwner) public onlyOwner {
_transferOwnership(_newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function _transferOwnership(address _newOwner) internal {
require(_newOwner != address(0));
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender)
public view returns (uint256);
function transferFrom(address from, address to, uint256 value)
public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
function safeTransfer(ERC20Basic token, address to, uint256 value) internal {
require(token.transfer(to, value));
}
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 value
)
internal
{
require(token.transferFrom(from, to, value));
}
function safeApprove(ERC20 token, address spender, uint256 value) internal {
require(token.approve(spender, value));
}
}
/**
* @title Locker
* @notice Locker holds tokens and releases them at a certain time.
*/
contract Locker is Ownable {
using SafeMath for uint;
using SafeERC20 for ERC20Basic;
/**
* It is init state only when adding release info is possible.
* beneficiary only can release tokens when Locker is active.
* After all tokens are released, locker is drawn.
*/
enum State { Init, Ready, Active, Drawn }
struct Beneficiary {
uint ratio; // ratio based on Locker's initial balance.
uint withdrawAmount; // accumulated tokens beneficiary released
bool releaseAllTokens;
}
/**
* @notice Release has info to release tokens.
* If lock type is straight, only two release infos is required.
*
* |
* 100 | _______________
* | _/
* 50 | _/
* | . |
* | . |
* | . |
* +===+=======+----*----------> time
* Locker First Last
* Activated Release Release
*
*
* If lock type is variable, the release graph will be
*
* |
* 100 | _________
* | |
* 70 | __________|
* | |
* 30 | _________|
* | |
* +===+=======+---------+----------*------> time
* Locker First Second Last
* Activated Release Release Release
*
*
*
* For the first straight release graph, parameters would be
* coeff: 100
* releaseTimes: [
* first release time,
* second release time
* ]
* releaseRatios: [
* 50,
* 100,
* ]
*
* For the second variable release graph, parameters would be
* coeff: 100
* releaseTimes: [
* first release time,
* second release time,
* last release time
* ]
* releaseRatios: [
* 30,
* 70,
* 100,
* ]
*
*/
struct Release {
bool isStraight; // lock type : straight or variable
uint[] releaseTimes; //
uint[] releaseRatios; //
}
uint public activeTime;
// ERC20 basic token contract being held
ERC20Basic public token;
uint public coeff;
uint public initialBalance;
uint public withdrawAmount; // total amount of tokens released
mapping (address => Beneficiary) public beneficiaries;
mapping (address => Release) public releases; // beneficiary's lock
mapping (address => bool) public locked; // whether beneficiary's lock is instantiated
uint public numBeneficiaries;
uint public numLocks;
State public state;
modifier onlyState(State v) {
require(state == v);
_;
}
modifier onlyBeneficiary(address _addr) {
require(beneficiaries[_addr].ratio > 0);
_;
}
event StateChanged(State _state);
event Locked(address indexed _beneficiary, bool _isStraight);
event Released(address indexed _beneficiary, uint256 _amount);
function Locker(address _token, uint _coeff, address[] _beneficiaries, uint[] _ratios) public {
require(_token != address(0));
require(_beneficiaries.length == _ratios.length);
token = ERC20Basic(_token);
coeff = _coeff;
numBeneficiaries = _beneficiaries.length;
uint accRatio;
for(uint i = 0; i < numBeneficiaries; i++) {
require(_ratios[i] > 0);
beneficiaries[_beneficiaries[i]].ratio = _ratios[i];
accRatio = accRatio.add(_ratios[i]);
}
require(coeff == accRatio);
}
/**
* @notice beneficiary can release their tokens after activated
*/
function activate() external onlyOwner onlyState(State.Ready) {
require(numLocks == numBeneficiaries); // double check : assert all releases are recorded
initialBalance = token.balanceOf(this);
require(initialBalance > 0);
activeTime = now; // solium-disable-line security/no-block-members
// set locker as active state
state = State.Active;
emit StateChanged(state);
}
function getReleaseType(address _beneficiary)
public
view
onlyBeneficiary(_beneficiary)
returns (bool)
{
return releases[_beneficiary].isStraight;
}
function getTotalLockedAmounts(address _beneficiary)
public
view
onlyBeneficiary(_beneficiary)
returns (uint)
{
return getPartialAmount(beneficiaries[_beneficiary].ratio, coeff, initialBalance);
}
function getReleaseTimes(address _beneficiary)
public
view
onlyBeneficiary(_beneficiary)
returns (uint[])
{
return releases[_beneficiary].releaseTimes;
}
function getReleaseRatios(address _beneficiary)
public
view
onlyBeneficiary(_beneficiary)
returns (uint[])
{
return releases[_beneficiary].releaseRatios;
}
/**
* @notice add new release record for beneficiary
*/
function lock(address _beneficiary, bool _isStraight, uint[] _releaseTimes, uint[] _releaseRatios)
external
onlyOwner
onlyState(State.Init)
onlyBeneficiary(_beneficiary)
{
require(!locked[_beneficiary]);
require(_releaseRatios.length != 0);
require(_releaseRatios.length == _releaseTimes.length);
uint i;
uint len = _releaseRatios.length;
// finally should release all tokens
require(_releaseRatios[len - 1] == coeff);
// check two array are ascending sorted
for(i = 0; i < len - 1; i++) {
require(_releaseTimes[i] < _releaseTimes[i + 1]);
require(_releaseRatios[i] < _releaseRatios[i + 1]);
}
// 2 release times for straight locking type
if (_isStraight) {
require(len == 2);
}
numLocks = numLocks.add(1);
// create Release for the beneficiary
releases[_beneficiary].isStraight = _isStraight;
// copy array of uint
releases[_beneficiary].releaseTimes = _releaseTimes;
releases[_beneficiary].releaseRatios = _releaseRatios;
// lock beneficiary
locked[_beneficiary] = true;
emit Locked(_beneficiary, _isStraight);
// if all beneficiaries locked, change Locker state to change
if (numLocks == numBeneficiaries) {
state = State.Ready;
emit StateChanged(state);
}
}
/**
* @notice transfer releasable tokens for beneficiary wrt the release graph
*/
function release() external onlyState(State.Active) onlyBeneficiary(msg.sender) {
require(!beneficiaries[msg.sender].releaseAllTokens);
uint releasableAmount = getReleasableAmount(msg.sender);
beneficiaries[msg.sender].withdrawAmount = beneficiaries[msg.sender].withdrawAmount.add(releasableAmount);
beneficiaries[msg.sender].releaseAllTokens = beneficiaries[msg.sender].withdrawAmount == getPartialAmount(
beneficiaries[msg.sender].ratio,
coeff,
initialBalance);
withdrawAmount = withdrawAmount.add(releasableAmount);
if (withdrawAmount == initialBalance) {
state = State.Drawn;
emit StateChanged(state);
}
token.transfer(msg.sender, releasableAmount);
emit Released(msg.sender, releasableAmount);
}
function getReleasableAmount(address _beneficiary) internal view returns (uint) {
if (releases[_beneficiary].isStraight) {
return getStraightReleasableAmount(_beneficiary);
} else {
return getVariableReleasableAmount(_beneficiary);
}
}
/**
* @notice return releaseable amount for beneficiary in case of straight type of release
*/
function getStraightReleasableAmount(address _beneficiary) internal view returns (uint releasableAmount) {
Beneficiary memory _b = beneficiaries[_beneficiary];
Release memory _r = releases[_beneficiary];
// total amount of tokens beneficiary can release
uint totalReleasableAmount = getTotalLockedAmounts(_beneficiary);
uint firstTime = _r.releaseTimes[0];
uint lastTime = _r.releaseTimes[1];
// solium-disable security/no-block-members
require(now >= firstTime); // pass if can release
// solium-enable security/no-block-members
if(now >= lastTime) { // inclusive to reduce calculation
releasableAmount = totalReleasableAmount;
} else {
// releasable amount at first time
uint firstAmount = getPartialAmount(
_r.releaseRatios[0],
coeff,
totalReleasableAmount);
// partial amount without first amount
releasableAmount = getPartialAmount(
now.sub(firstTime),
lastTime.sub(firstTime),
totalReleasableAmount.sub(firstAmount));
releasableAmount = releasableAmount.add(firstAmount);
}
// subtract already withdrawn amounts
releasableAmount = releasableAmount.sub(_b.withdrawAmount);
}
/**
* @notice return releaseable amount for beneficiary in case of variable type of release
*/
function getVariableReleasableAmount(address _beneficiary) internal view returns (uint releasableAmount) {
Beneficiary memory _b = beneficiaries[_beneficiary];
Release memory _r = releases[_beneficiary];
// total amount of tokens beneficiary will receive
uint totalReleasableAmount = getTotalLockedAmounts(_beneficiary);
uint releaseRatio;
// reverse order for short curcit
for(uint i = _r.releaseTimes.length - 1; i >= 0; i--) {
if (now >= _r.releaseTimes[i]) {
releaseRatio = _r.releaseRatios[i];
break;
}
}
require(releaseRatio > 0);
releasableAmount = getPartialAmount(
releaseRatio,
coeff,
totalReleasableAmount);
releasableAmount = releasableAmount.sub(_b.withdrawAmount);
}
/// https://github.com/0xProject/0x.js/blob/05aae368132a81ddb9fd6a04ac5b0ff1cbb24691/packages/contracts/src/current/protocol/Exchange/Exchange.sol#L497
/// @notice Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmount(uint numerator, uint denominator, uint target) public pure returns (uint) {
return numerator.mul(target).div(denominator);
}
}