/** * Source Code first verified at https://etherscan.io on Thursday, March 28, 2019 (UTC) */ pragma solidity ^0.4.24; /** * @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 private _owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns(address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns(bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _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 ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); event Transfer( address indexed from, address indexed to, uint256 value ); event Approval( address indexed owner, address indexed spender, uint256 value ); } /** * @title ERC20Detailed token * @dev The decimals are only for visualization purposes. * All the operations are done using the smallest and indivisible token unit, * just as on Ethereum all the operations are done in wei. */ contract ERC20Detailed is IERC20 { string private _name; string private _symbol; uint8 private _decimals; constructor(string name, string symbol, uint8 decimals) public { _name = name; _symbol = symbol; _decimals = decimals; } /** * @return the name of the token. */ function name() public view returns(string) { return _name; } /** * @return the symbol of the token. */ function symbol() public view returns(string) { return _symbol; } /** * @return the number of decimals of the token. */ function decimals() public view returns(uint8) { return _decimals; } } /** * @title SafeMath * @dev Math operations with safety checks that revert on error */ library SafeMath { /** * @dev Multiplies two numbers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (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-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0); // Solidity only automatically asserts 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 c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two numbers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md * Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract ERC20 is IERC20 { using SafeMath for uint256; mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowed; uint256 private _totalSupply; /** * @dev Total number of tokens in existence */ function totalSupply() public view returns (uint256) { return _totalSupply; } /** * @dev Gets the balance of the specified address. * @param owner The address to query the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address owner) public view returns (uint256) { return _balances[owner]; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param owner address The address which owns the funds. * @param spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ function allowance( address owner, address spender ) public view returns (uint256) { return _allowed[owner][spender]; } /** * @dev Transfer token for a specified address * @param to The address to transfer to. * @param value The amount to be transferred. */ function transfer(address to, uint256 value) public returns (bool) { _transfer(msg.sender, to, value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * @param spender The address which will spend the funds. * @param value The amount of tokens to be spent. */ function approve(address spender, uint256 value) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } /** * @dev Transfer tokens from one address to another * @param from address The address which you want to send tokens from * @param to address The address which you want to transfer to * @param value uint256 the amount of tokens to be transferred */ function transferFrom( address from, address to, uint256 value ) public returns (bool) { require(value <= _allowed[from][msg.sender]); _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); _transfer(from, to, value); return true; } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * approve should be called when allowed_[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param spender The address which will spend the funds. * @param addedValue The amount of tokens to increase the allowance by. */ function increaseAllowance( address spender, uint256 addedValue ) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = ( _allowed[msg.sender][spender].add(addedValue)); emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * approve should be called when allowed_[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param spender The address which will spend the funds. * @param subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseAllowance( address spender, uint256 subtractedValue ) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = ( _allowed[msg.sender][spender].sub(subtractedValue)); emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } /** * @dev Transfer token for a specified addresses * @param from The address to transfer from. * @param to The address to transfer to. * @param value The amount to be transferred. */ function _transfer(address from, address to, uint256 value) internal { require(value <= _balances[from]); require(to != address(0)); _balances[from] = _balances[from].sub(value); _balances[to] = _balances[to].add(value); emit Transfer(from, to, value); } /** * @dev Internal function that mints an amount of the token and assigns it to * an account. This encapsulates the modification of balances such that the * proper events are emitted. * @param account The account that will receive the created tokens. * @param value The amount that will be created. */ function _mint(address account, uint256 value) internal { require(account != 0); _totalSupply = _totalSupply.add(value); _balances[account] = _balances[account].add(value); emit Transfer(address(0), account, value); } /** * @dev Internal function that burns an amount of the token of a given * account. * @param account The account whose tokens will be burnt. * @param value The amount that will be burnt. */ function _burn(address account, uint256 value) internal { require(account != 0); require(value <= _balances[account]); _totalSupply = _totalSupply.sub(value); _balances[account] = _balances[account].sub(value); emit Transfer(account, address(0), value); } /** * @dev Internal function that burns an amount of the token of a given * account, deducting from the sender's allowance for said account. Uses the * internal burn function. * @param account The account whose tokens will be burnt. * @param value The amount that will be burnt. */ function _burnFrom(address account, uint256 value) internal { require(value <= _allowed[account][msg.sender]); // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, // this function needs to emit an event with the updated approval. _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( value); _burn(account, value); } } /** * @title Roles * @dev Library for managing addresses assigned to a Role. */ library Roles { struct Role { mapping (address => bool) bearer; } /** * @dev give an account access to this role */ function add(Role storage role, address account) internal { require(account != address(0)); require(!has(role, account)); role.bearer[account] = true; } /** * @dev remove an account's access to this role */ function remove(Role storage role, address account) internal { require(account != address(0)); require(has(role, account)); role.bearer[account] = false; } /** * @dev check if an account has this role * @return bool */ function has(Role storage role, address account) internal view returns (bool) { require(account != address(0)); return role.bearer[account]; } } contract PauserRole { using Roles for Roles.Role; event PauserAdded(address indexed account); event PauserRemoved(address indexed account); Roles.Role private pausers; constructor() internal { _addPauser(msg.sender); } modifier onlyPauser() { require(isPauser(msg.sender)); _; } function isPauser(address account) public view returns (bool) { return pausers.has(account); } function addPauser(address account) public onlyPauser { _addPauser(account); } function renouncePauser() public { _removePauser(msg.sender); } function _addPauser(address account) internal { pausers.add(account); emit PauserAdded(account); } function _removePauser(address account) internal { pausers.remove(account); emit PauserRemoved(account); } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is PauserRole { event Paused(address account); event Unpaused(address account); bool private _paused; constructor() internal { _paused = false; } /** * @return true if the contract is paused, false otherwise. */ function paused() public view returns(bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!_paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(_paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() public onlyPauser whenNotPaused { _paused = true; emit Paused(msg.sender); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() public onlyPauser whenPaused { _paused = false; emit Unpaused(msg.sender); } } /** * @title Pausable token * @dev ERC20 modified with pausable transfers. **/ contract ERC20Pausable is ERC20, Pausable { function transfer( address to, uint256 value ) public whenNotPaused returns (bool) { return super.transfer(to, value); } function transferFrom( address from, address to, uint256 value ) public whenNotPaused returns (bool) { return super.transferFrom(from, to, value); } function approve( address spender, uint256 value ) public whenNotPaused returns (bool) { return super.approve(spender, value); } function increaseAllowance( address spender, uint addedValue ) public whenNotPaused returns (bool success) { return super.increaseAllowance(spender, addedValue); } function decreaseAllowance( address spender, uint subtractedValue ) public whenNotPaused returns (bool success) { return super.decreaseAllowance(spender, subtractedValue); } } contract SignkeysToken is ERC20Pausable, ERC20Detailed, Ownable { uint8 public constant DECIMALS = 18; uint256 public constant INITIAL_SUPPLY = 2E9 * (10 ** uint256(DECIMALS)); /* Address where fees will be transferred */ address public feeChargingAddress; /* Nonces */ mapping(address => uint256) public nonces; function setFeeChargingAddress(address _feeChargingAddress) external onlyOwner { feeChargingAddress = _feeChargingAddress; emit FeeChargingAddressChanges(_feeChargingAddress); } /* Fee charging address changed */ event FeeChargingAddressChanges(address newFeeChargingAddress); /** * @dev Constructor that gives msg.sender all of existing tokens. */ constructor() public ERC20Detailed("SignkeysToken", "KEYS", DECIMALS) { _mint(owner(), INITIAL_SUPPLY); } function transferWithSignature( address from, address to, uint256 amount, uint256 feeAmount, uint256 nonce, uint256 expiration, uint8 v, bytes32 r, bytes32 s) public { require(expiration >= now, "Signature expired"); require(feeChargingAddress != 0x0, "Fee charging address must be set"); address receivedSigner = ecrecover( keccak256( abi.encodePacked( from, to, amount, feeAmount, nonce, expiration ) ), v, r, s); require(nonce > nonces[from], "Wrong nonce"); nonces[from] = nonce; require(receivedSigner == from, "Something wrong with signature"); _transfer(from, to, amount); _transfer(from, feeChargingAddress, feeAmount); } function approveAndCall(address _spender, uint256 _value, bytes _data) public payable returns (bool success) { require(_spender != address(this)); require(super.approve(_spender, _value)); require(_spender.call(_data)); return true; } function() payable external { revert(); } } contract SignkeysBonusProgram is Ownable { using SafeMath for uint256; /* Token contract */ SignkeysToken public token; /* Crowdsale contract */ SignkeysCrowdsale public crowdsale; /* SignkeysBonusProgramRewards contract to keep bonus state */ SignkeysBonusProgramRewards public bonusProgramRewards; /* Ranges in which we transfer the given amount of tokens as reward. See arrays below. For example, if this array is [199, 1000, 10000] and referrerRewards array is [5, 50], it considers as follows: [0, 199] - 0 tokens [200, 1000] - 5 tokens [1001, 10000] - 50 tokens, > 10000 - 50 tokens */ uint256[] public referralBonusTokensAmountRanges = [199, 1000, 10000, 100000, 1000000, 10000000]; /* Amount of tokens as bonus for referrer according to referralBonusTokensAmountRanges */ uint256[] public referrerRewards = [5, 50, 500, 5000, 50000]; /* Amount of tokens as bonus for buyer according to referralBonusTokensAmountRanges */ uint256[] public buyerRewards = [5, 50, 500, 5000, 50000]; /* Purchase amount ranges in cents for any purchase. For example, if this array is [2000, 1000000, 10000000] and purchaseRewardsPercents array is [10, 15, 20], it considers as follows: [2000, 1000000) - 10% of tokens [1000000, 10000000) - 15% of tokens > 100000000 - 20% tokens */ uint256[] public purchaseAmountRangesInCents = [2000, 1000000, 10000000]; /* Percetage of reward for any purchase according to purchaseAmountRangesInCents */ uint256[] public purchaseRewardsPercents = [10, 15, 20]; event BonusSent( address indexed referrerAddress, uint256 referrerBonus, address indexed buyerAddress, uint256 buyerBonus, uint256 purchaseBonus, uint256 couponBonus ); constructor(address _token, address _bonusProgramRewards) public { token = SignkeysToken(_token); bonusProgramRewards = SignkeysBonusProgramRewards(_bonusProgramRewards); } function setCrowdsaleContract(address _crowdsale) public onlyOwner { crowdsale = SignkeysCrowdsale(_crowdsale); } function setBonusProgramRewardsContract(address _bonusProgramRewards) public onlyOwner { bonusProgramRewards = SignkeysBonusProgramRewards(_bonusProgramRewards); } /* Calculate bonus for the given amount of tokens according to referralBonusTokensAmountRanges and rewards array which is one of referrerRewards or buyerRewards */ function calcBonus(uint256 tokensAmount, uint256[] rewards) private view returns (uint256) { uint256 multiplier = 10 ** uint256(token.decimals()); if (tokensAmount <= multiplier.mul(referralBonusTokensAmountRanges[0])) { return 0; } for (uint i = 1; i < referralBonusTokensAmountRanges.length; i++) { uint min = referralBonusTokensAmountRanges[i - 1]; uint max = referralBonusTokensAmountRanges[i]; if (tokensAmount > min.mul(multiplier) && tokensAmount <= max.mul(multiplier)) { return multiplier.mul(rewards[i - 1]); } } if (tokensAmount >= referralBonusTokensAmountRanges[referralBonusTokensAmountRanges.length - 1].mul(multiplier)) { return multiplier.mul(rewards[rewards.length - 1]); } } function calcPurchaseBonus(uint256 amountCents, uint256 tokensAmount) private view returns (uint256) { if (amountCents < purchaseAmountRangesInCents[0]) { return 0; } for (uint i = 1; i < purchaseAmountRangesInCents.length; i++) { if (amountCents >= purchaseAmountRangesInCents[i - 1] && amountCents < purchaseAmountRangesInCents[i]) { return tokensAmount.mul(purchaseRewardsPercents[i - 1]).div(100); } } if (amountCents >= purchaseAmountRangesInCents[purchaseAmountRangesInCents.length - 1]) { return tokensAmount.mul(purchaseRewardsPercents[purchaseAmountRangesInCents.length - 1]).div(100); } } /* Having referrer, buyer, amount of purchased tokens, value of purchased tokens in cents and coupon campaign id this method transfer all the required bonuses to referrer and buyer */ function sendBonus(address referrer, address buyer, uint256 _tokensAmount, uint256 _valueCents, uint256 _couponCampaignId) external returns (uint256) { require(msg.sender == address(crowdsale), "Bonus may be sent only by crowdsale contract"); uint256 referrerBonus = 0; uint256 buyerBonus = 0; uint256 purchaseBonus = 0; uint256 couponBonus = 0; uint256 referrerBonusAmount = calcBonus(_tokensAmount, referrerRewards); uint256 buyerBonusAmount = calcBonus(_tokensAmount, buyerRewards); uint256 purchaseBonusAmount = calcPurchaseBonus(_valueCents, _tokensAmount); if (referrer != 0x0 && !bonusProgramRewards.areReferralBonusesSent(buyer)) { if (referrerBonusAmount > 0 && token.balanceOf(this) > referrerBonusAmount) { token.transfer(referrer, referrerBonusAmount); bonusProgramRewards.setReferralBonusesSent(buyer, true); referrerBonus = referrerBonusAmount; } if (buyerBonusAmount > 0 && token.balanceOf(this) > buyerBonusAmount) { bonusProgramRewards.setReferralBonusesSent(buyer, true); buyerBonus = buyerBonusAmount; } } if (token.balanceOf(this) > purchaseBonusAmount.add(buyerBonus)) { purchaseBonus = purchaseBonusAmount; } if (_couponCampaignId > 0 && !bonusProgramRewards.isCouponUsed(buyer, _couponCampaignId)) { if ( token.balanceOf(this) > purchaseBonusAmount .add(buyerBonus) .add(bonusProgramRewards.getCouponCampaignBonusTokensAmount(_couponCampaignId)) ) { bonusProgramRewards.setCouponUsed(buyer, _couponCampaignId, true); couponBonus = bonusProgramRewards.getCouponCampaignBonusTokensAmount(_couponCampaignId); } } if (buyerBonus > 0 || purchaseBonus > 0 || couponBonus > 0) { token.transfer(buyer, buyerBonus.add(purchaseBonus).add(couponBonus)); } emit BonusSent(referrer, referrerBonus, buyer, buyerBonus, purchaseBonus, couponBonus); } function getReferralBonusTokensAmountRanges() public view returns (uint256[]) { return referralBonusTokensAmountRanges; } function getReferrerRewards() public view returns (uint256[]) { return referrerRewards; } function getBuyerRewards() public view returns (uint256[]) { return buyerRewards; } function getPurchaseRewardsPercents() public view returns (uint256[]) { return purchaseRewardsPercents; } function getPurchaseAmountRangesInCents() public view returns (uint256[]) { return purchaseAmountRangesInCents; } function setReferralBonusTokensAmountRanges(uint[] ranges) public onlyOwner { referralBonusTokensAmountRanges = ranges; } function setReferrerRewards(uint[] rewards) public onlyOwner { require(rewards.length == referralBonusTokensAmountRanges.length - 1); referrerRewards = rewards; } function setBuyerRewards(uint[] rewards) public onlyOwner { require(rewards.length == referralBonusTokensAmountRanges.length - 1); buyerRewards = rewards; } function setPurchaseAmountRangesInCents(uint[] ranges) public onlyOwner { purchaseAmountRangesInCents = ranges; } function setPurchaseRewardsPercents(uint[] rewards) public onlyOwner { require(rewards.length == purchaseAmountRangesInCents.length); purchaseRewardsPercents = rewards; } /* Withdraw all tokens from contract for any emergence case */ function withdrawTokens() external onlyOwner { uint256 amount = token.balanceOf(this); address tokenOwner = token.owner(); token.transfer(tokenOwner, amount); } } contract SignkeysBonusProgramRewards is Ownable { using SafeMath for uint256; /* Bonus program contract */ SignkeysBonusProgram public bonusProgram; /* How much bonuses to send according for the given coupon campaign */ mapping(uint256 => uint256) private _couponCampaignBonusTokensAmount; /* Check if referrer already got the bonuses from the invited token receiver */ mapping(address => bool) private _areReferralBonusesSent; /* Check if coupon of the given campaign was used by the token receiver */ mapping(address => mapping(uint256 => bool)) private _isCouponUsed; function setBonusProgram(address _bonusProgram) public onlyOwner { bonusProgram = SignkeysBonusProgram(_bonusProgram); } modifier onlyBonusProgramContract() { require(msg.sender == address(bonusProgram), "Bonus program rewards state may be changed only by bonus program contract"); _; } function addCouponCampaignBonusTokensAmount(uint256 _couponCampaignId, uint256 amountOfBonuses) public onlyOwner { _couponCampaignBonusTokensAmount[_couponCampaignId] = amountOfBonuses; } function getCouponCampaignBonusTokensAmount(uint256 _couponCampaignId) public view returns (uint256) { return _couponCampaignBonusTokensAmount[_couponCampaignId]; } function isCouponUsed(address buyer, uint256 couponCampaignId) public view returns (bool) { return _isCouponUsed[buyer][couponCampaignId]; } function setCouponUsed(address buyer, uint256 couponCampaignId, bool isUsed) public onlyBonusProgramContract { _isCouponUsed[buyer][couponCampaignId] = isUsed; } function areReferralBonusesSent(address buyer) public view returns (bool) { return _areReferralBonusesSent[buyer]; } function setReferralBonusesSent(address buyer, bool areBonusesSent) public onlyBonusProgramContract { _areReferralBonusesSent[buyer] = areBonusesSent; } } /** * @title Helps contracts guard against reentrancy attacks. * @author Remco Bloemen <[email protected]π.com>, Eenae <[email protected]> * @dev If you mark a function `nonReentrant`, you should also * mark it `external`. */ contract ReentrancyGuard { /// @dev counter to allow mutex lock with only one SSTORE operation uint256 private _guardCounter; constructor() internal { // The counter starts at one to prevent changing it from zero to a non-zero // value, which is a more expensive operation. _guardCounter = 1; } /** * @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() { _guardCounter += 1; uint256 localCounter = _guardCounter; _; require(localCounter == _guardCounter); } } contract SignkeysCrowdsale is Ownable, ReentrancyGuard { using SafeMath for uint256; uint256 public constant INITIAL_TOKEN_PRICE_CENTS = 10; /* Token contract */ SignkeysToken public signkeysToken; /* Bonus program contract*/ SignkeysBonusProgram public signkeysBonusProgram; /* signer address, can be set by owner only */ address public signer; /* ETH funds will be transferred to this address */ address public wallet; /* Role that provide tokens calling sendTokens method */ address public administrator; /* Current token price in cents */ uint256 public tokenPriceCents; /* Buyer bought the amount of tokens with tokenPrice */ event BuyTokens( address indexed buyer, address indexed tokenReceiver, uint256 tokenPrice, uint256 amount ); /* Admin sent the amount of tokens to the tokenReceiver */ event SendTokens( address indexed tokenReceiver, uint256 amount ); /* Wallet changed */ event WalletChanged(address newWallet); /* Administrator changed */ event AdministratorChanged(address newAdministrator); /* Signer changed */ event CrowdsaleSignerChanged(address newSigner); /* Token price changed */ event TokenPriceChanged(uint256 oldPrice, uint256 newPrice); constructor( address _token, address _bonusProgram, address _wallet, address _signer ) public { require(_token != 0x0, "Token contract for crowdsale must be set"); require(_bonusProgram != 0x0, "Referrer smart contract for crowdsale must be set"); require(_wallet != 0x0, "Wallet for fund transferring must be set"); require(_signer != 0x0, "Signer must be set"); signkeysToken = SignkeysToken(_token); signkeysBonusProgram = SignkeysBonusProgram(_bonusProgram); signer = _signer; wallet = _wallet; tokenPriceCents = INITIAL_TOKEN_PRICE_CENTS; } function setSignerAddress(address _signer) external onlyOwner { signer = _signer; emit CrowdsaleSignerChanged(_signer); } function setWalletAddress(address _wallet) external onlyOwner { wallet = _wallet; emit WalletChanged(_wallet); } function setAdministratorAddress(address _administrator) external onlyOwner { administrator = _administrator; emit AdministratorChanged(_administrator); } function setBonusProgram(address _bonusProgram) external onlyOwner { signkeysBonusProgram = SignkeysBonusProgram(_bonusProgram); } function setTokenPriceCents(uint256 _tokenPriceCents) external onlyOwner { emit TokenPriceChanged(tokenPriceCents, _tokenPriceCents); tokenPriceCents = _tokenPriceCents; } /** * @dev Make an investment. * * @param _tokenReceiver address where the tokens need to be transfered * @param _referrer address of user that invited _tokenReceiver for this purchase * @param _tokenPrice price per one token including decimals * @param _minWei minimal amount of wei buyer should invest * @param _expiration expiration on token */ function buyTokens( address _tokenReceiver, address _referrer, uint256 _couponCampaignId, // starts with 1 if there is some, 0 means no coupon uint256 _tokenPrice, uint256 _minWei, uint256 _expiration, uint8 _v, bytes32 _r, bytes32 _s ) payable external nonReentrant { require(_expiration >= now, "Signature expired"); require(_tokenReceiver != 0x0, "Token receiver must be provided"); require(_minWei > 0, "Minimal amount to purchase must be greater than 0"); require(wallet != 0x0, "Wallet must be set"); require(msg.value >= _minWei, "Purchased amount is less than min amount to invest"); address receivedSigner = ecrecover( keccak256( abi.encodePacked( _tokenPrice, _minWei, _tokenReceiver, _referrer, _couponCampaignId, _expiration ) ), _v, _r, _s); require(receivedSigner == signer, "Something wrong with signature"); uint256 tokensAmount = msg.value.mul(10 ** uint256(signkeysToken.decimals())).div(_tokenPrice); require(signkeysToken.balanceOf(this) >= tokensAmount, "Not enough tokens in sale contract"); // Pocket the money, or fail the crowdsale if we for some reason cannot send the money to our wallet wallet.transfer(msg.value); _sendTokens(_tokenReceiver, _referrer, _couponCampaignId, tokensAmount); emit BuyTokens(msg.sender, _tokenReceiver, _tokenPrice, tokensAmount); } function sendTokens( address _tokenReceiver, address _referrer, uint256 _couponCampaignId, uint256 tokensAmount ) external { require(msg.sender == administrator, "sendTokens() method may be called only by administrator "); _sendTokens(_tokenReceiver, _referrer, _couponCampaignId, tokensAmount); emit SendTokens(_tokenReceiver, tokensAmount); } function _sendTokens( address _tokenReceiver, address _referrer, uint256 _couponCampaignId, uint256 tokensAmount ) private { signkeysToken.transfer(_tokenReceiver, tokensAmount); // send bonuses according to signkeys bonus program signkeysBonusProgram.sendBonus( _referrer, _tokenReceiver, tokensAmount, (tokensAmount.mul(tokenPriceCents).div(10 ** uint256(signkeysToken.decimals()))), _couponCampaignId); } /** * Don't expect to just send in money and get tokens. */ function() payable external { revert(); } /* Withdraw all tokens from contract for any emergence case */ function withdrawTokens() external onlyOwner { uint256 amount = signkeysToken.balanceOf(this); address tokenOwner = signkeysToken.owner(); signkeysToken.transfer(tokenOwner, amount); } }