Transaction Hash:
Block:
10333145 at Jun-25-2020 05:54:56 AM +UTC
Transaction Fee:
0.004005634 ETH
$10.60
Gas Used:
87,079 Gas / 46 Gwei
Emitted Events:
13 |
HEX.Transfer( from=[Receiver] 0xc3a6842458fa68ce01ae9fe68d206755dbed3dc0, to=[Sender] 0x68b008f2c06fa83c8cb8bb170227ae35264d1b66, value=5701421629309 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x2b591e99...8c40Eeb39 | |||||
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 52.765772786485139203 Eth | 52.769778420485139203 Eth | 0.004005634 | |
0x68B008f2...5264d1B66 |
0.056104183217612311 Eth
Nonce: 76
|
0.242310077155259551 Eth
Nonce: 77
| 0.18620589393764724 | ||
0xc3a68424...5dBEd3Dc0 | 14.319367148783973893 Eth | 14.129155620846326653 Eth | 0.19021152793764724 |
Execution Trace
0xc3a6842458fa68ce01ae9fe68d206755dbed3dc0.CALL( )
-
HXY.freezingBalanceOf( account=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66 ) => ( balance=683675697033 )
-
HXY.latestFreezeTimeOf( account=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66 ) => ( 1592511379 )
-
HEX.transfer( recipient=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66, amount=5701421629309 ) => ( True )
- ETH 0.19021152793764724
0x68b008f2c06fa83c8cb8bb170227ae35264d1b66.CALL( )
File 1 of 2: HEX
File 2 of 2: HXY
pragma solidity 0.5.13; /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ contract Context { // Empty internal constructor, to prevent people from mistakenly deploying // an instance of this contract, which should be used via inheritance. constructor () internal { } // solhint-disable-previous-line no-empty-blocks function _msgSender() internal view returns (address payable) { return msg.sender; } function _msgData() internal view returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } /** * @dev Interface of the ERC20 standard as defined in the EIP. Does not include * the optional functions; to access them see {ERC20Detailed}. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: 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 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @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. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. * * _Available since v2.4.0._ */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (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. */ 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-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); 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. */ function div(uint256 a, uint256 b) internal pure returns (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. * * _Available since v2.4.0._ */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return 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. */ function mod(uint256 a, uint256 b) internal pure returns (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. * * _Available since v2.4.0._ */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20Mintable}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin guidelines: functions revert instead * of returning `false` on failure. This behavior is nonetheless conventional * and does not conflict with the expectations of ERC20 applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20 { using SafeMath for uint256; mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowances; uint256 private _totalSupply; /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public returns (bool) { _transfer(_msgSender(), recipient, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public returns (bool) { _approve(_msgSender(), spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}; * * Requirements: * - `sender` and `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. * - the caller must have allowance for `sender`'s tokens of at least * `amount`. */ function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { _transfer(sender, recipient, amount); _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); return true; } /** * @dev Moves tokens `amount` from `sender` to `recipient`. * * This is internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `sender` cannot be the zero address. * - `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. */ function _transfer(address sender, address recipient, uint256 amount) internal { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); _balances[recipient] = _balances[recipient].add(amount); emit Transfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements * * - `to` cannot be the zero address. */ function _mint(address account, uint256 amount) internal { require(account != address(0), "ERC20: mint to the zero address"); _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); emit Transfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal { require(account != address(0), "ERC20: burn from the zero address"); _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); _totalSupply = _totalSupply.sub(amount); emit Transfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. * * This is internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Destroys `amount` tokens from `account`.`amount` is then deducted * from the caller's allowance. * * See {_burn} and {_approve}. */ function _burnFrom(address account, uint256 amount) internal { _burn(account, amount); _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance")); } } contract GlobalsAndUtility is ERC20 { /* XfLobbyEnter (auto-generated event) uint40 timestamp --> data0 [ 39: 0] address indexed memberAddr uint256 indexed entryId uint96 rawAmount --> data0 [135: 40] address indexed referrerAddr */ event XfLobbyEnter( uint256 data0, address indexed memberAddr, uint256 indexed entryId, address indexed referrerAddr ); /* XfLobbyExit (auto-generated event) uint40 timestamp --> data0 [ 39: 0] address indexed memberAddr uint256 indexed entryId uint72 xfAmount --> data0 [111: 40] address indexed referrerAddr */ event XfLobbyExit( uint256 data0, address indexed memberAddr, uint256 indexed entryId, address indexed referrerAddr ); /* DailyDataUpdate (auto-generated event) uint40 timestamp --> data0 [ 39: 0] uint16 beginDay --> data0 [ 55: 40] uint16 endDay --> data0 [ 71: 56] bool isAutoUpdate --> data0 [ 79: 72] address indexed updaterAddr */ event DailyDataUpdate( uint256 data0, address indexed updaterAddr ); /* Claim (auto-generated event) uint40 timestamp --> data0 [ 39: 0] bytes20 indexed btcAddr uint56 rawSatoshis --> data0 [ 95: 40] uint56 adjSatoshis --> data0 [151: 96] address indexed claimToAddr uint8 claimFlags --> data0 [159:152] uint72 claimedHearts --> data0 [231:160] address indexed referrerAddr address senderAddr --> data1 [159: 0] */ event Claim( uint256 data0, uint256 data1, bytes20 indexed btcAddr, address indexed claimToAddr, address indexed referrerAddr ); /* ClaimAssist (auto-generated event) uint40 timestamp --> data0 [ 39: 0] bytes20 btcAddr --> data0 [199: 40] uint56 rawSatoshis --> data0 [255:200] uint56 adjSatoshis --> data1 [ 55: 0] address claimToAddr --> data1 [215: 56] uint8 claimFlags --> data1 [223:216] uint72 claimedHearts --> data2 [ 71: 0] address referrerAddr --> data2 [231: 72] address indexed senderAddr */ event ClaimAssist( uint256 data0, uint256 data1, uint256 data2, address indexed senderAddr ); /* StakeStart (auto-generated event) uint40 timestamp --> data0 [ 39: 0] address indexed stakerAddr uint40 indexed stakeId uint72 stakedHearts --> data0 [111: 40] uint72 stakeShares --> data0 [183:112] uint16 stakedDays --> data0 [199:184] bool isAutoStake --> data0 [207:200] */ event StakeStart( uint256 data0, address indexed stakerAddr, uint40 indexed stakeId ); /* StakeGoodAccounting(auto-generated event) uint40 timestamp --> data0 [ 39: 0] address indexed stakerAddr uint40 indexed stakeId uint72 stakedHearts --> data0 [111: 40] uint72 stakeShares --> data0 [183:112] uint72 payout --> data0 [255:184] uint72 penalty --> data1 [ 71: 0] address indexed senderAddr */ event StakeGoodAccounting( uint256 data0, uint256 data1, address indexed stakerAddr, uint40 indexed stakeId, address indexed senderAddr ); /* StakeEnd (auto-generated event) uint40 timestamp --> data0 [ 39: 0] address indexed stakerAddr uint40 indexed stakeId uint72 stakedHearts --> data0 [111: 40] uint72 stakeShares --> data0 [183:112] uint72 payout --> data0 [255:184] uint72 penalty --> data1 [ 71: 0] uint16 servedDays --> data1 [ 87: 72] bool prevUnlocked --> data1 [ 95: 88] */ event StakeEnd( uint256 data0, uint256 data1, address indexed stakerAddr, uint40 indexed stakeId ); /* ShareRateChange (auto-generated event) uint40 timestamp --> data0 [ 39: 0] uint40 shareRate --> data0 [ 79: 40] uint40 indexed stakeId */ event ShareRateChange( uint256 data0, uint40 indexed stakeId ); /* Origin address */ address internal constant ORIGIN_ADDR = 0x9A6a414D6F3497c05E3b1De90520765fA1E07c03; /* Flush address */ address payable internal constant FLUSH_ADDR = 0xDEC9f2793e3c17cd26eeFb21C4762fA5128E0399; /* ERC20 constants */ string public constant name = "HEX"; string public constant symbol = "HEX"; uint8 public constant decimals = 8; /* Hearts per Satoshi = 10,000 * 1e8 / 1e8 = 1e4 */ uint256 private constant HEARTS_PER_HEX = 10 ** uint256(decimals); // 1e8 uint256 private constant HEX_PER_BTC = 1e4; uint256 private constant SATOSHIS_PER_BTC = 1e8; uint256 internal constant HEARTS_PER_SATOSHI = HEARTS_PER_HEX / SATOSHIS_PER_BTC * HEX_PER_BTC; /* Time of contract launch (2019-12-03T00:00:00Z) */ uint256 internal constant LAUNCH_TIME = 1575331200; /* Size of a Hearts or Shares uint */ uint256 internal constant HEART_UINT_SIZE = 72; /* Size of a transform lobby entry index uint */ uint256 internal constant XF_LOBBY_ENTRY_INDEX_SIZE = 40; uint256 internal constant XF_LOBBY_ENTRY_INDEX_MASK = (1 << XF_LOBBY_ENTRY_INDEX_SIZE) - 1; /* Seed for WAAS Lobby */ uint256 internal constant WAAS_LOBBY_SEED_HEX = 1e9; uint256 internal constant WAAS_LOBBY_SEED_HEARTS = WAAS_LOBBY_SEED_HEX * HEARTS_PER_HEX; /* Start of claim phase */ uint256 internal constant PRE_CLAIM_DAYS = 1; uint256 internal constant CLAIM_PHASE_START_DAY = PRE_CLAIM_DAYS; /* Length of claim phase */ uint256 private constant CLAIM_PHASE_WEEKS = 50; uint256 internal constant CLAIM_PHASE_DAYS = CLAIM_PHASE_WEEKS * 7; /* End of claim phase */ uint256 internal constant CLAIM_PHASE_END_DAY = CLAIM_PHASE_START_DAY + CLAIM_PHASE_DAYS; /* Number of words to hold 1 bit for each transform lobby day */ uint256 internal constant XF_LOBBY_DAY_WORDS = (CLAIM_PHASE_END_DAY + 255) >> 8; /* BigPayDay */ uint256 internal constant BIG_PAY_DAY = CLAIM_PHASE_END_DAY + 1; /* Root hash of the UTXO Merkle tree */ bytes32 internal constant MERKLE_TREE_ROOT = 0x4e831acb4223b66de3b3d2e54a2edeefb0de3d7916e2886a4b134d9764d41bec; /* Size of a Satoshi claim uint in a Merkle leaf */ uint256 internal constant MERKLE_LEAF_SATOSHI_SIZE = 45; /* Zero-fill between BTC address and Satoshis in a Merkle leaf */ uint256 internal constant MERKLE_LEAF_FILL_SIZE = 256 - 160 - MERKLE_LEAF_SATOSHI_SIZE; uint256 internal constant MERKLE_LEAF_FILL_BASE = (1 << MERKLE_LEAF_FILL_SIZE) - 1; uint256 internal constant MERKLE_LEAF_FILL_MASK = MERKLE_LEAF_FILL_BASE << MERKLE_LEAF_SATOSHI_SIZE; /* Size of a Satoshi total uint */ uint256 internal constant SATOSHI_UINT_SIZE = 51; uint256 internal constant SATOSHI_UINT_MASK = (1 << SATOSHI_UINT_SIZE) - 1; /* Total Satoshis from all BTC addresses in UTXO snapshot */ uint256 internal constant FULL_SATOSHIS_TOTAL = 1807766732160668; /* Total Satoshis from supported BTC addresses in UTXO snapshot after applying Silly Whale */ uint256 internal constant CLAIMABLE_SATOSHIS_TOTAL = 910087996911001; /* Number of claimable BTC addresses in UTXO snapshot */ uint256 internal constant CLAIMABLE_BTC_ADDR_COUNT = 27997742; /* Largest BTC address Satoshis balance in UTXO snapshot (sanity check) */ uint256 internal constant MAX_BTC_ADDR_BALANCE_SATOSHIS = 25550214098481; /* Percentage of total claimed Hearts that will be auto-staked from a claim */ uint256 internal constant AUTO_STAKE_CLAIM_PERCENT = 90; /* Stake timing parameters */ uint256 internal constant MIN_STAKE_DAYS = 1; uint256 internal constant MIN_AUTO_STAKE_DAYS = 350; uint256 internal constant MAX_STAKE_DAYS = 5555; // Approx 15 years uint256 internal constant EARLY_PENALTY_MIN_DAYS = 90; uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2; uint256 internal constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7; uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100; uint256 internal constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7; /* Stake shares Longer Pays Better bonus constants used by _stakeStartBonusHearts() */ uint256 private constant LPB_BONUS_PERCENT = 20; uint256 private constant LPB_BONUS_MAX_PERCENT = 200; uint256 internal constant LPB = 364 * 100 / LPB_BONUS_PERCENT; uint256 internal constant LPB_MAX_DAYS = LPB * LPB_BONUS_MAX_PERCENT / 100; /* Stake shares Bigger Pays Better bonus constants used by _stakeStartBonusHearts() */ uint256 private constant BPB_BONUS_PERCENT = 10; uint256 private constant BPB_MAX_HEX = 150 * 1e6; uint256 internal constant BPB_MAX_HEARTS = BPB_MAX_HEX * HEARTS_PER_HEX; uint256 internal constant BPB = BPB_MAX_HEARTS * 100 / BPB_BONUS_PERCENT; /* Share rate is scaled to increase precision */ uint256 internal constant SHARE_RATE_SCALE = 1e5; /* Share rate max (after scaling) */ uint256 internal constant SHARE_RATE_UINT_SIZE = 40; uint256 internal constant SHARE_RATE_MAX = (1 << SHARE_RATE_UINT_SIZE) - 1; /* Constants for preparing the claim message text */ uint8 internal constant ETH_ADDRESS_BYTE_LEN = 20; uint8 internal constant ETH_ADDRESS_HEX_LEN = ETH_ADDRESS_BYTE_LEN * 2; uint8 internal constant CLAIM_PARAM_HASH_BYTE_LEN = 12; uint8 internal constant CLAIM_PARAM_HASH_HEX_LEN = CLAIM_PARAM_HASH_BYTE_LEN * 2; uint8 internal constant BITCOIN_SIG_PREFIX_LEN = 24; bytes24 internal constant BITCOIN_SIG_PREFIX_STR = "Bitcoin Signed Message:\n"; bytes internal constant STD_CLAIM_PREFIX_STR = "Claim_HEX_to_0x"; bytes internal constant OLD_CLAIM_PREFIX_STR = "Claim_BitcoinHEX_to_0x"; bytes16 internal constant HEX_DIGITS = "0123456789abcdef"; /* Claim flags passed to btcAddressClaim() */ uint8 internal constant CLAIM_FLAG_MSG_PREFIX_OLD = 1 << 0; uint8 internal constant CLAIM_FLAG_BTC_ADDR_COMPRESSED = 1 << 1; uint8 internal constant CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH = 1 << 2; uint8 internal constant CLAIM_FLAG_BTC_ADDR_BECH32 = 1 << 3; uint8 internal constant CLAIM_FLAG_ETH_ADDR_LOWERCASE = 1 << 4; /* Globals expanded for memory (except _latestStakeId) and compact for storage */ struct GlobalsCache { // 1 uint256 _lockedHeartsTotal; uint256 _nextStakeSharesTotal; uint256 _shareRate; uint256 _stakePenaltyTotal; // 2 uint256 _dailyDataCount; uint256 _stakeSharesTotal; uint40 _latestStakeId; uint256 _unclaimedSatoshisTotal; uint256 _claimedSatoshisTotal; uint256 _claimedBtcAddrCount; // uint256 _currentDay; } struct GlobalsStore { // 1 uint72 lockedHeartsTotal; uint72 nextStakeSharesTotal; uint40 shareRate; uint72 stakePenaltyTotal; // 2 uint16 dailyDataCount; uint72 stakeSharesTotal; uint40 latestStakeId; uint128 claimStats; } GlobalsStore public globals; /* Claimed BTC addresses */ mapping(bytes20 => bool) public btcAddressClaims; /* Daily data */ struct DailyDataStore { uint72 dayPayoutTotal; uint72 dayStakeSharesTotal; uint56 dayUnclaimedSatoshisTotal; } mapping(uint256 => DailyDataStore) public dailyData; /* Stake expanded for memory (except _stakeId) and compact for storage */ struct StakeCache { uint40 _stakeId; uint256 _stakedHearts; uint256 _stakeShares; uint256 _lockedDay; uint256 _stakedDays; uint256 _unlockedDay; bool _isAutoStake; } struct StakeStore { uint40 stakeId; uint72 stakedHearts; uint72 stakeShares; uint16 lockedDay; uint16 stakedDays; uint16 unlockedDay; bool isAutoStake; } mapping(address => StakeStore[]) public stakeLists; /* Temporary state for calculating daily rounds */ struct DailyRoundState { uint256 _allocSupplyCached; uint256 _mintOriginBatch; uint256 _payoutTotal; } struct XfLobbyEntryStore { uint96 rawAmount; address referrerAddr; } struct XfLobbyQueueStore { uint40 headIndex; uint40 tailIndex; mapping(uint256 => XfLobbyEntryStore) entries; } mapping(uint256 => uint256) public xfLobby; mapping(uint256 => mapping(address => XfLobbyQueueStore)) public xfLobbyMembers; /** * @dev PUBLIC FACING: Optionally update daily data for a smaller * range to reduce gas cost for a subsequent operation * @param beforeDay Only update days before this day number (optional; 0 for current day) */ function dailyDataUpdate(uint256 beforeDay) external { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); /* Skip pre-claim period */ require(g._currentDay > CLAIM_PHASE_START_DAY, "HEX: Too early"); if (beforeDay != 0) { require(beforeDay <= g._currentDay, "HEX: beforeDay cannot be in the future"); _dailyDataUpdate(g, beforeDay, false); } else { /* Default to updating before current day */ _dailyDataUpdate(g, g._currentDay, false); } _globalsSync(g, gSnapshot); } /** * @dev PUBLIC FACING: External helper to return multiple values of daily data with * a single call. Ugly implementation due to limitations of the standard ABI encoder. * @param beginDay First day of data range * @param endDay Last day (non-inclusive) of data range * @return Fixed array of packed values */ function dailyDataRange(uint256 beginDay, uint256 endDay) external view returns (uint256[] memory list) { require(beginDay < endDay && endDay <= globals.dailyDataCount, "HEX: range invalid"); list = new uint256[](endDay - beginDay); uint256 src = beginDay; uint256 dst = 0; uint256 v; do { v = uint256(dailyData[src].dayUnclaimedSatoshisTotal) << (HEART_UINT_SIZE * 2); v |= uint256(dailyData[src].dayStakeSharesTotal) << HEART_UINT_SIZE; v |= uint256(dailyData[src].dayPayoutTotal); list[dst++] = v; } while (++src < endDay); return list; } /** * @dev PUBLIC FACING: External helper to return most global info with a single call. * Ugly implementation due to limitations of the standard ABI encoder. * @return Fixed array of values */ function globalInfo() external view returns (uint256[13] memory) { uint256 _claimedBtcAddrCount; uint256 _claimedSatoshisTotal; uint256 _unclaimedSatoshisTotal; (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal) = _claimStatsDecode( globals.claimStats ); return [ // 1 globals.lockedHeartsTotal, globals.nextStakeSharesTotal, globals.shareRate, globals.stakePenaltyTotal, // 2 globals.dailyDataCount, globals.stakeSharesTotal, globals.latestStakeId, _unclaimedSatoshisTotal, _claimedSatoshisTotal, _claimedBtcAddrCount, // block.timestamp, totalSupply(), xfLobby[_currentDay()] ]; } /** * @dev PUBLIC FACING: ERC20 totalSupply() is the circulating supply and does not include any * staked Hearts. allocatedSupply() includes both. * @return Allocated Supply in Hearts */ function allocatedSupply() external view returns (uint256) { return totalSupply() + globals.lockedHeartsTotal; } /** * @dev PUBLIC FACING: External helper for the current day number since launch time * @return Current day number (zero-based) */ function currentDay() external view returns (uint256) { return _currentDay(); } function _currentDay() internal view returns (uint256) { return (block.timestamp - LAUNCH_TIME) / 1 days; } function _dailyDataUpdateAuto(GlobalsCache memory g) internal { _dailyDataUpdate(g, g._currentDay, true); } function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot) internal view { // 1 g._lockedHeartsTotal = globals.lockedHeartsTotal; g._nextStakeSharesTotal = globals.nextStakeSharesTotal; g._shareRate = globals.shareRate; g._stakePenaltyTotal = globals.stakePenaltyTotal; // 2 g._dailyDataCount = globals.dailyDataCount; g._stakeSharesTotal = globals.stakeSharesTotal; g._latestStakeId = globals.latestStakeId; (g._claimedBtcAddrCount, g._claimedSatoshisTotal, g._unclaimedSatoshisTotal) = _claimStatsDecode( globals.claimStats ); // g._currentDay = _currentDay(); _globalsCacheSnapshot(g, gSnapshot); } function _globalsCacheSnapshot(GlobalsCache memory g, GlobalsCache memory gSnapshot) internal pure { // 1 gSnapshot._lockedHeartsTotal = g._lockedHeartsTotal; gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal; gSnapshot._shareRate = g._shareRate; gSnapshot._stakePenaltyTotal = g._stakePenaltyTotal; // 2 gSnapshot._dailyDataCount = g._dailyDataCount; gSnapshot._stakeSharesTotal = g._stakeSharesTotal; gSnapshot._latestStakeId = g._latestStakeId; gSnapshot._unclaimedSatoshisTotal = g._unclaimedSatoshisTotal; gSnapshot._claimedSatoshisTotal = g._claimedSatoshisTotal; gSnapshot._claimedBtcAddrCount = g._claimedBtcAddrCount; } function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot) internal { if (g._lockedHeartsTotal != gSnapshot._lockedHeartsTotal || g._nextStakeSharesTotal != gSnapshot._nextStakeSharesTotal || g._shareRate != gSnapshot._shareRate || g._stakePenaltyTotal != gSnapshot._stakePenaltyTotal) { // 1 globals.lockedHeartsTotal = uint72(g._lockedHeartsTotal); globals.nextStakeSharesTotal = uint72(g._nextStakeSharesTotal); globals.shareRate = uint40(g._shareRate); globals.stakePenaltyTotal = uint72(g._stakePenaltyTotal); } if (g._dailyDataCount != gSnapshot._dailyDataCount || g._stakeSharesTotal != gSnapshot._stakeSharesTotal || g._latestStakeId != gSnapshot._latestStakeId || g._unclaimedSatoshisTotal != gSnapshot._unclaimedSatoshisTotal || g._claimedSatoshisTotal != gSnapshot._claimedSatoshisTotal || g._claimedBtcAddrCount != gSnapshot._claimedBtcAddrCount) { // 2 globals.dailyDataCount = uint16(g._dailyDataCount); globals.stakeSharesTotal = uint72(g._stakeSharesTotal); globals.latestStakeId = g._latestStakeId; globals.claimStats = _claimStatsEncode( g._claimedBtcAddrCount, g._claimedSatoshisTotal, g._unclaimedSatoshisTotal ); } } function _stakeLoad(StakeStore storage stRef, uint40 stakeIdParam, StakeCache memory st) internal view { /* Ensure caller's stakeIndex is still current */ require(stakeIdParam == stRef.stakeId, "HEX: stakeIdParam not in stake"); st._stakeId = stRef.stakeId; st._stakedHearts = stRef.stakedHearts; st._stakeShares = stRef.stakeShares; st._lockedDay = stRef.lockedDay; st._stakedDays = stRef.stakedDays; st._unlockedDay = stRef.unlockedDay; st._isAutoStake = stRef.isAutoStake; } function _stakeUpdate(StakeStore storage stRef, StakeCache memory st) internal { stRef.stakeId = st._stakeId; stRef.stakedHearts = uint72(st._stakedHearts); stRef.stakeShares = uint72(st._stakeShares); stRef.lockedDay = uint16(st._lockedDay); stRef.stakedDays = uint16(st._stakedDays); stRef.unlockedDay = uint16(st._unlockedDay); stRef.isAutoStake = st._isAutoStake; } function _stakeAdd( StakeStore[] storage stakeListRef, uint40 newStakeId, uint256 newStakedHearts, uint256 newStakeShares, uint256 newLockedDay, uint256 newStakedDays, bool newAutoStake ) internal { stakeListRef.push( StakeStore( newStakeId, uint72(newStakedHearts), uint72(newStakeShares), uint16(newLockedDay), uint16(newStakedDays), uint16(0), // unlockedDay newAutoStake ) ); } /** * @dev Efficiently delete from an unordered array by moving the last element * to the "hole" and reducing the array length. Can change the order of the list * and invalidate previously held indexes. * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd() * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage * @param stakeIndex Index of the element to delete */ function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex) internal { uint256 lastIndex = stakeListRef.length - 1; /* Skip the copy if element to be removed is already the last element */ if (stakeIndex != lastIndex) { /* Copy last element to the requested element's "hole" */ stakeListRef[stakeIndex] = stakeListRef[lastIndex]; } /* Reduce the array length now that the array is contiguous. Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex' */ stakeListRef.pop(); } function _claimStatsEncode( uint256 _claimedBtcAddrCount, uint256 _claimedSatoshisTotal, uint256 _unclaimedSatoshisTotal ) internal pure returns (uint128) { uint256 v = _claimedBtcAddrCount << (SATOSHI_UINT_SIZE * 2); v |= _claimedSatoshisTotal << SATOSHI_UINT_SIZE; v |= _unclaimedSatoshisTotal; return uint128(v); } function _claimStatsDecode(uint128 v) internal pure returns (uint256 _claimedBtcAddrCount, uint256 _claimedSatoshisTotal, uint256 _unclaimedSatoshisTotal) { _claimedBtcAddrCount = v >> (SATOSHI_UINT_SIZE * 2); _claimedSatoshisTotal = (v >> SATOSHI_UINT_SIZE) & SATOSHI_UINT_MASK; _unclaimedSatoshisTotal = v & SATOSHI_UINT_MASK; return (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal); } /** * @dev Estimate the stake payout for an incomplete day * @param g Cache of stored globals * @param stakeSharesParam Param from stake to calculate bonuses for * @param day Day to calculate bonuses for * @return Payout in Hearts */ function _estimatePayoutRewardsDay(GlobalsCache memory g, uint256 stakeSharesParam, uint256 day) internal view returns (uint256 payout) { /* Prevent updating state for this estimation */ GlobalsCache memory gTmp; _globalsCacheSnapshot(g, gTmp); DailyRoundState memory rs; rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal; _dailyRoundCalc(gTmp, rs, day); /* Stake is no longer locked so it must be added to total as if it were */ gTmp._stakeSharesTotal += stakeSharesParam; payout = rs._payoutTotal * stakeSharesParam / gTmp._stakeSharesTotal; if (day == BIG_PAY_DAY) { uint256 bigPaySlice = gTmp._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam / gTmp._stakeSharesTotal; payout += bigPaySlice + _calcAdoptionBonus(gTmp, bigPaySlice); } return payout; } function _calcAdoptionBonus(GlobalsCache memory g, uint256 payout) internal pure returns (uint256) { /* VIRAL REWARDS: Add adoption percentage bonus to payout viral = payout * (claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT) */ uint256 viral = payout * g._claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT; /* CRIT MASS REWARDS: Add adoption percentage bonus to payout crit = payout * (claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL) */ uint256 crit = payout * g._claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL; return viral + crit; } function _dailyRoundCalc(GlobalsCache memory g, DailyRoundState memory rs, uint256 day) private pure { /* Calculate payout round Inflation of 3.69% inflation per 364 days (approx 1 year) dailyInterestRate = exp(log(1 + 3.69%) / 364) - 1 = exp(log(1 + 0.0369) / 364) - 1 = exp(log(1.0369) / 364) - 1 = 0.000099553011616349 (approx) payout = allocSupply * dailyInterestRate = allocSupply / (1 / dailyInterestRate) = allocSupply / (1 / 0.000099553011616349) = allocSupply / 10044.899534066692 (approx) = allocSupply * 10000 / 100448995 (* 10000/10000 for int precision) */ rs._payoutTotal = rs._allocSupplyCached * 10000 / 100448995; if (day < CLAIM_PHASE_END_DAY) { uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS; uint256 originBonus = bigPaySlice + _calcAdoptionBonus(g, rs._payoutTotal + bigPaySlice); rs._mintOriginBatch += originBonus; rs._allocSupplyCached += originBonus; rs._payoutTotal += _calcAdoptionBonus(g, rs._payoutTotal); } if (g._stakePenaltyTotal != 0) { rs._payoutTotal += g._stakePenaltyTotal; g._stakePenaltyTotal = 0; } } function _dailyRoundCalcAndStore(GlobalsCache memory g, DailyRoundState memory rs, uint256 day) private { _dailyRoundCalc(g, rs, day); dailyData[day].dayPayoutTotal = uint72(rs._payoutTotal); dailyData[day].dayStakeSharesTotal = uint72(g._stakeSharesTotal); dailyData[day].dayUnclaimedSatoshisTotal = uint56(g._unclaimedSatoshisTotal); } function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay, bool isAutoUpdate) private { if (g._dailyDataCount >= beforeDay) { /* Already up-to-date */ return; } DailyRoundState memory rs; rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal; uint256 day = g._dailyDataCount; _dailyRoundCalcAndStore(g, rs, day); /* Stakes started during this day are added to the total the next day */ if (g._nextStakeSharesTotal != 0) { g._stakeSharesTotal += g._nextStakeSharesTotal; g._nextStakeSharesTotal = 0; } while (++day < beforeDay) { _dailyRoundCalcAndStore(g, rs, day); } _emitDailyDataUpdate(g._dailyDataCount, day, isAutoUpdate); g._dailyDataCount = day; if (rs._mintOriginBatch != 0) { _mint(ORIGIN_ADDR, rs._mintOriginBatch); } } function _emitDailyDataUpdate(uint256 beginDay, uint256 endDay, bool isAutoUpdate) private { emit DailyDataUpdate( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint16(beginDay)) << 40) | (uint256(uint16(endDay)) << 56) | (isAutoUpdate ? (1 << 72) : 0), msg.sender ); } } contract StakeableToken is GlobalsAndUtility { /** * @dev PUBLIC FACING: Open a stake. * @param newStakedHearts Number of Hearts to stake * @param newStakedDays Number of days to stake */ function stakeStart(uint256 newStakedHearts, uint256 newStakedDays) external { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); /* Enforce the minimum stake time */ require(newStakedDays >= MIN_STAKE_DAYS, "HEX: newStakedDays lower than minimum"); /* Check if log data needs to be updated */ _dailyDataUpdateAuto(g); _stakeStart(g, newStakedHearts, newStakedDays, false); /* Remove staked Hearts from balance of staker */ _burn(msg.sender, newStakedHearts); _globalsSync(g, gSnapshot); } /** * @dev PUBLIC FACING: Unlocks a completed stake, distributing the proceeds of any penalty * immediately. The staker must still call stakeEnd() to retrieve their stake return (if any). * @param stakerAddr Address of staker * @param stakeIndex Index of stake within stake list * @param stakeIdParam The stake's id */ function stakeGoodAccounting(address stakerAddr, uint256 stakeIndex, uint40 stakeIdParam) external { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); /* require() is more informative than the default assert() */ require(stakeLists[stakerAddr].length != 0, "HEX: Empty stake list"); require(stakeIndex < stakeLists[stakerAddr].length, "HEX: stakeIndex invalid"); StakeStore storage stRef = stakeLists[stakerAddr][stakeIndex]; /* Get stake copy */ StakeCache memory st; _stakeLoad(stRef, stakeIdParam, st); /* Stake must have served full term */ require(g._currentDay >= st._lockedDay + st._stakedDays, "HEX: Stake not fully served"); /* Stake must still be locked */ require(st._unlockedDay == 0, "HEX: Stake already unlocked"); /* Check if log data needs to be updated */ _dailyDataUpdateAuto(g); /* Unlock the completed stake */ _stakeUnlock(g, st); /* stakeReturn value is unused here */ (, uint256 payout, uint256 penalty, uint256 cappedPenalty) = _stakePerformance( g, st, st._stakedDays ); _emitStakeGoodAccounting( stakerAddr, stakeIdParam, st._stakedHearts, st._stakeShares, payout, penalty ); if (cappedPenalty != 0) { _splitPenaltyProceeds(g, cappedPenalty); } /* st._unlockedDay has changed */ _stakeUpdate(stRef, st); _globalsSync(g, gSnapshot); } /** * @dev PUBLIC FACING: Closes a stake. The order of the stake list can change so * a stake id is used to reject stale indexes. * @param stakeIndex Index of stake within stake list * @param stakeIdParam The stake's id */ function stakeEnd(uint256 stakeIndex, uint40 stakeIdParam) external { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); StakeStore[] storage stakeListRef = stakeLists[msg.sender]; /* require() is more informative than the default assert() */ require(stakeListRef.length != 0, "HEX: Empty stake list"); require(stakeIndex < stakeListRef.length, "HEX: stakeIndex invalid"); /* Get stake copy */ StakeCache memory st; _stakeLoad(stakeListRef[stakeIndex], stakeIdParam, st); /* Check if log data needs to be updated */ _dailyDataUpdateAuto(g); uint256 servedDays = 0; bool prevUnlocked = (st._unlockedDay != 0); uint256 stakeReturn; uint256 payout = 0; uint256 penalty = 0; uint256 cappedPenalty = 0; if (g._currentDay >= st._lockedDay) { if (prevUnlocked) { /* Previously unlocked in stakeGoodAccounting(), so must have served full term */ servedDays = st._stakedDays; } else { _stakeUnlock(g, st); servedDays = g._currentDay - st._lockedDay; if (servedDays > st._stakedDays) { servedDays = st._stakedDays; } else { /* Deny early-unstake before an auto-stake minimum has been served */ if (servedDays < MIN_AUTO_STAKE_DAYS) { require(!st._isAutoStake, "HEX: Auto-stake still locked"); } } } (stakeReturn, payout, penalty, cappedPenalty) = _stakePerformance(g, st, servedDays); } else { /* Deny early-unstake before an auto-stake minimum has been served */ require(!st._isAutoStake, "HEX: Auto-stake still locked"); /* Stake hasn't been added to the total yet, so no penalties or rewards apply */ g._nextStakeSharesTotal -= st._stakeShares; stakeReturn = st._stakedHearts; } _emitStakeEnd( stakeIdParam, st._stakedHearts, st._stakeShares, payout, penalty, servedDays, prevUnlocked ); if (cappedPenalty != 0 && !prevUnlocked) { /* Split penalty proceeds only if not previously unlocked by stakeGoodAccounting() */ _splitPenaltyProceeds(g, cappedPenalty); } /* Pay the stake return, if any, to the staker */ if (stakeReturn != 0) { _mint(msg.sender, stakeReturn); /* Update the share rate if necessary */ _shareRateUpdate(g, st, stakeReturn); } g._lockedHeartsTotal -= st._stakedHearts; _stakeRemove(stakeListRef, stakeIndex); _globalsSync(g, gSnapshot); } /** * @dev PUBLIC FACING: Return the current stake count for a staker address * @param stakerAddr Address of staker */ function stakeCount(address stakerAddr) external view returns (uint256) { return stakeLists[stakerAddr].length; } /** * @dev Open a stake. * @param g Cache of stored globals * @param newStakedHearts Number of Hearts to stake * @param newStakedDays Number of days to stake * @param newAutoStake Stake is automatic directly from a new claim */ function _stakeStart( GlobalsCache memory g, uint256 newStakedHearts, uint256 newStakedDays, bool newAutoStake ) internal { /* Enforce the maximum stake time */ require(newStakedDays <= MAX_STAKE_DAYS, "HEX: newStakedDays higher than maximum"); uint256 bonusHearts = _stakeStartBonusHearts(newStakedHearts, newStakedDays); uint256 newStakeShares = (newStakedHearts + bonusHearts) * SHARE_RATE_SCALE / g._shareRate; /* Ensure newStakedHearts is enough for at least one stake share */ require(newStakeShares != 0, "HEX: newStakedHearts must be at least minimum shareRate"); /* The stakeStart timestamp will always be part-way through the current day, so it needs to be rounded-up to the next day to ensure all stakes align with the same fixed calendar days. The current day is already rounded-down, so rounded-up is current day + 1. */ uint256 newLockedDay = g._currentDay < CLAIM_PHASE_START_DAY ? CLAIM_PHASE_START_DAY + 1 : g._currentDay + 1; /* Create Stake */ uint40 newStakeId = ++g._latestStakeId; _stakeAdd( stakeLists[msg.sender], newStakeId, newStakedHearts, newStakeShares, newLockedDay, newStakedDays, newAutoStake ); _emitStakeStart(newStakeId, newStakedHearts, newStakeShares, newStakedDays, newAutoStake); /* Stake is added to total in the next round, not the current round */ g._nextStakeSharesTotal += newStakeShares; /* Track total staked Hearts for inflation calculations */ g._lockedHeartsTotal += newStakedHearts; } /** * @dev Calculates total stake payout including rewards for a multi-day range * @param g Cache of stored globals * @param stakeSharesParam Param from stake to calculate bonuses for * @param beginDay First day to calculate bonuses for * @param endDay Last day (non-inclusive) of range to calculate bonuses for * @return Payout in Hearts */ function _calcPayoutRewards( GlobalsCache memory g, uint256 stakeSharesParam, uint256 beginDay, uint256 endDay ) private view returns (uint256 payout) { for (uint256 day = beginDay; day < endDay; day++) { payout += dailyData[day].dayPayoutTotal * stakeSharesParam / dailyData[day].dayStakeSharesTotal; } /* Less expensive to re-read storage than to have the condition inside the loop */ if (beginDay <= BIG_PAY_DAY && endDay > BIG_PAY_DAY) { uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam / dailyData[BIG_PAY_DAY].dayStakeSharesTotal; payout += bigPaySlice + _calcAdoptionBonus(g, bigPaySlice); } return payout; } /** * @dev Calculate bonus Hearts for a new stake, if any * @param newStakedHearts Number of Hearts to stake * @param newStakedDays Number of days to stake */ function _stakeStartBonusHearts(uint256 newStakedHearts, uint256 newStakedDays) private pure returns (uint256 bonusHearts) { /* LONGER PAYS BETTER: If longer than 1 day stake is committed to, each extra day gives bonus shares of approximately 0.0548%, which is approximately 20% extra per year of increased stake length committed to, but capped to a maximum of 200% extra. extraDays = stakedDays - 1 longerBonus% = (extraDays / 364) * 20% = (extraDays / 364) / 5 = extraDays / 1820 = extraDays / LPB extraDays = longerBonus% * 1820 extraDaysMax = longerBonusMax% * 1820 = 200% * 1820 = 3640 = LPB_MAX_DAYS BIGGER PAYS BETTER: Bonus percentage scaled 0% to 10% for the first 150M HEX of stake. biggerBonus% = (cappedHearts / BPB_MAX_HEARTS) * 10% = (cappedHearts / BPB_MAX_HEARTS) / 10 = cappedHearts / (BPB_MAX_HEARTS * 10) = cappedHearts / BPB COMBINED: combinedBonus% = longerBonus% + biggerBonus% cappedExtraDays cappedHearts = --------------- + ------------ LPB BPB cappedExtraDays * BPB cappedHearts * LPB = --------------------- + ------------------ LPB * BPB LPB * BPB cappedExtraDays * BPB + cappedHearts * LPB = -------------------------------------------- LPB * BPB bonusHearts = hearts * combinedBonus% = hearts * (cappedExtraDays * BPB + cappedHearts * LPB) / (LPB * BPB) */ uint256 cappedExtraDays = 0; /* Must be more than 1 day for Longer-Pays-Better */ if (newStakedDays > 1) { cappedExtraDays = newStakedDays <= LPB_MAX_DAYS ? newStakedDays - 1 : LPB_MAX_DAYS; } uint256 cappedStakedHearts = newStakedHearts <= BPB_MAX_HEARTS ? newStakedHearts : BPB_MAX_HEARTS; bonusHearts = cappedExtraDays * BPB + cappedStakedHearts * LPB; bonusHearts = newStakedHearts * bonusHearts / (LPB * BPB); return bonusHearts; } function _stakeUnlock(GlobalsCache memory g, StakeCache memory st) private pure { g._stakeSharesTotal -= st._stakeShares; st._unlockedDay = g._currentDay; } function _stakePerformance(GlobalsCache memory g, StakeCache memory st, uint256 servedDays) private view returns (uint256 stakeReturn, uint256 payout, uint256 penalty, uint256 cappedPenalty) { if (servedDays < st._stakedDays) { (payout, penalty) = _calcPayoutAndEarlyPenalty( g, st._lockedDay, st._stakedDays, servedDays, st._stakeShares ); stakeReturn = st._stakedHearts + payout; } else { // servedDays must == stakedDays here payout = _calcPayoutRewards( g, st._stakeShares, st._lockedDay, st._lockedDay + servedDays ); stakeReturn = st._stakedHearts + payout; penalty = _calcLatePenalty(st._lockedDay, st._stakedDays, st._unlockedDay, stakeReturn); } if (penalty != 0) { if (penalty > stakeReturn) { /* Cannot have a negative stake return */ cappedPenalty = stakeReturn; stakeReturn = 0; } else { /* Remove penalty from the stake return */ cappedPenalty = penalty; stakeReturn -= cappedPenalty; } } return (stakeReturn, payout, penalty, cappedPenalty); } function _calcPayoutAndEarlyPenalty( GlobalsCache memory g, uint256 lockedDayParam, uint256 stakedDaysParam, uint256 servedDays, uint256 stakeSharesParam ) private view returns (uint256 payout, uint256 penalty) { uint256 servedEndDay = lockedDayParam + servedDays; /* 50% of stakedDays (rounded up) with a minimum applied */ uint256 penaltyDays = (stakedDaysParam + 1) / 2; if (penaltyDays < EARLY_PENALTY_MIN_DAYS) { penaltyDays = EARLY_PENALTY_MIN_DAYS; } if (servedDays == 0) { /* Fill penalty days with the estimated average payout */ uint256 expected = _estimatePayoutRewardsDay(g, stakeSharesParam, lockedDayParam); penalty = expected * penaltyDays; return (payout, penalty); // Actual payout was 0 } if (penaltyDays < servedDays) { /* Simplified explanation of intervals where end-day is non-inclusive: penalty: [lockedDay ... penaltyEndDay) delta: [penaltyEndDay ... servedEndDay) payout: [lockedDay ....................... servedEndDay) */ uint256 penaltyEndDay = lockedDayParam + penaltyDays; penalty = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, penaltyEndDay); uint256 delta = _calcPayoutRewards(g, stakeSharesParam, penaltyEndDay, servedEndDay); payout = penalty + delta; return (payout, penalty); } /* penaltyDays >= servedDays */ payout = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, servedEndDay); if (penaltyDays == servedDays) { penalty = payout; } else { /* (penaltyDays > servedDays) means not enough days served, so fill the penalty days with the average payout from only the days that were served. */ penalty = payout * penaltyDays / servedDays; } return (payout, penalty); } function _calcLatePenalty( uint256 lockedDayParam, uint256 stakedDaysParam, uint256 unlockedDayParam, uint256 rawStakeReturn ) private pure returns (uint256) { /* Allow grace time before penalties accrue */ uint256 maxUnlockedDay = lockedDayParam + stakedDaysParam + LATE_PENALTY_GRACE_DAYS; if (unlockedDayParam <= maxUnlockedDay) { return 0; } /* Calculate penalty as a percentage of stake return based on time */ return rawStakeReturn * (unlockedDayParam - maxUnlockedDay) / LATE_PENALTY_SCALE_DAYS; } function _splitPenaltyProceeds(GlobalsCache memory g, uint256 penalty) private { /* Split a penalty 50:50 between Origin and stakePenaltyTotal */ uint256 splitPenalty = penalty / 2; if (splitPenalty != 0) { _mint(ORIGIN_ADDR, splitPenalty); } /* Use the other half of the penalty to account for an odd-numbered penalty */ splitPenalty = penalty - splitPenalty; g._stakePenaltyTotal += splitPenalty; } function _shareRateUpdate(GlobalsCache memory g, StakeCache memory st, uint256 stakeReturn) private { if (stakeReturn > st._stakedHearts) { /* Calculate the new shareRate that would yield the same number of shares if the user re-staked this stakeReturn, factoring in any bonuses they would receive in stakeStart(). */ uint256 bonusHearts = _stakeStartBonusHearts(stakeReturn, st._stakedDays); uint256 newShareRate = (stakeReturn + bonusHearts) * SHARE_RATE_SCALE / st._stakeShares; if (newShareRate > SHARE_RATE_MAX) { /* Realistically this can't happen, but there are contrived theoretical scenarios that can lead to extreme values of newShareRate, so it is capped to prevent them anyway. */ newShareRate = SHARE_RATE_MAX; } if (newShareRate > g._shareRate) { g._shareRate = newShareRate; _emitShareRateChange(newShareRate, st._stakeId); } } } function _emitStakeStart( uint40 stakeId, uint256 stakedHearts, uint256 stakeShares, uint256 stakedDays, bool isAutoStake ) private { emit StakeStart( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint72(stakedHearts)) << 40) | (uint256(uint72(stakeShares)) << 112) | (uint256(uint16(stakedDays)) << 184) | (isAutoStake ? (1 << 200) : 0), msg.sender, stakeId ); } function _emitStakeGoodAccounting( address stakerAddr, uint40 stakeId, uint256 stakedHearts, uint256 stakeShares, uint256 payout, uint256 penalty ) private { emit StakeGoodAccounting( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint72(stakedHearts)) << 40) | (uint256(uint72(stakeShares)) << 112) | (uint256(uint72(payout)) << 184), uint256(uint72(penalty)), stakerAddr, stakeId, msg.sender ); } function _emitStakeEnd( uint40 stakeId, uint256 stakedHearts, uint256 stakeShares, uint256 payout, uint256 penalty, uint256 servedDays, bool prevUnlocked ) private { emit StakeEnd( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint72(stakedHearts)) << 40) | (uint256(uint72(stakeShares)) << 112) | (uint256(uint72(payout)) << 184), uint256(uint72(penalty)) | (uint256(uint16(servedDays)) << 72) | (prevUnlocked ? (1 << 88) : 0), msg.sender, stakeId ); } function _emitShareRateChange(uint256 shareRate, uint40 stakeId) private { emit ShareRateChange( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint40(shareRate)) << 40), stakeId ); } } /** * @dev These functions deal with verification of Merkle trees (hash trees), */ library MerkleProof { /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash < proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } // Check if the computed hash (root) is equal to the provided root return computedHash == root; } } contract UTXOClaimValidation is StakeableToken { /** * @dev PUBLIC FACING: Verify a BTC address and balance are unclaimed and part of the Merkle tree * @param btcAddr Bitcoin address (binary; no base58-check encoding) * @param rawSatoshis Raw BTC address balance in Satoshis * @param proof Merkle tree proof * @return True if can be claimed */ function btcAddressIsClaimable(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof) external view returns (bool) { uint256 day = _currentDay(); require(day >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started"); require(day < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended"); /* Don't need to check Merkle proof if UTXO BTC address has already been claimed */ if (btcAddressClaims[btcAddr]) { return false; } /* Verify the Merkle tree proof */ return _btcAddressIsValid(btcAddr, rawSatoshis, proof); } /** * @dev PUBLIC FACING: Verify a BTC address and balance are part of the Merkle tree * @param btcAddr Bitcoin address (binary; no base58-check encoding) * @param rawSatoshis Raw BTC address balance in Satoshis * @param proof Merkle tree proof * @return True if valid */ function btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof) external pure returns (bool) { return _btcAddressIsValid(btcAddr, rawSatoshis, proof); } /** * @dev PUBLIC FACING: Verify a Merkle proof using the UTXO Merkle tree * @param merkleLeaf Leaf asserted to be present in the Merkle tree * @param proof Generated Merkle tree proof * @return True if valid */ function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] calldata proof) external pure returns (bool) { return _merkleProofIsValid(merkleLeaf, proof); } /** * @dev PUBLIC FACING: Verify that a Bitcoin signature matches the claim message containing * the Ethereum address and claim param hash * @param claimToAddr Eth address within the signed claim message * @param claimParamHash Param hash within the signed claim message * @param pubKeyX First half of uncompressed ECDSA public key * @param pubKeyY Second half of uncompressed ECDSA public key * @param claimFlags Claim flags specifying address and message formats * @param v v parameter of ECDSA signature * @param r r parameter of ECDSA signature * @param s s parameter of ECDSA signature * @return True if matching */ function claimMessageMatchesSignature( address claimToAddr, bytes32 claimParamHash, bytes32 pubKeyX, bytes32 pubKeyY, uint8 claimFlags, uint8 v, bytes32 r, bytes32 s ) public pure returns (bool) { require(v >= 27 && v <= 30, "HEX: v invalid"); /* ecrecover() returns an Eth address rather than a public key, so we must do the same to compare. */ address pubKeyEthAddr = pubKeyToEthAddress(pubKeyX, pubKeyY); /* Create and hash the claim message text */ bytes32 messageHash = _hash256( _claimMessageCreate(claimToAddr, claimParamHash, claimFlags) ); /* Verify the public key */ return ecrecover(messageHash, v, r, s) == pubKeyEthAddr; } /** * @dev PUBLIC FACING: Derive an Ethereum address from an ECDSA public key * @param pubKeyX First half of uncompressed ECDSA public key * @param pubKeyY Second half of uncompressed ECDSA public key * @return Derived Eth address */ function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY) public pure returns (address) { return address(uint160(uint256(keccak256(abi.encodePacked(pubKeyX, pubKeyY))))); } /** * @dev PUBLIC FACING: Derive a Bitcoin address from an ECDSA public key * @param pubKeyX First half of uncompressed ECDSA public key * @param pubKeyY Second half of uncompressed ECDSA public key * @param claimFlags Claim flags specifying address and message formats * @return Derived Bitcoin address (binary; no base58-check encoding) */ function pubKeyToBtcAddress(bytes32 pubKeyX, bytes32 pubKeyY, uint8 claimFlags) public pure returns (bytes20) { /* Helpful references: - https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses - https://github.com/cryptocoinjs/ecurve/blob/master/lib/point.js */ uint8 startingByte; bytes memory pubKey; bool compressed = (claimFlags & CLAIM_FLAG_BTC_ADDR_COMPRESSED) != 0; bool nested = (claimFlags & CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH) != 0; bool bech32 = (claimFlags & CLAIM_FLAG_BTC_ADDR_BECH32) != 0; if (compressed) { /* Compressed public key format */ require(!(nested && bech32), "HEX: claimFlags invalid"); startingByte = (pubKeyY[31] & 0x01) == 0 ? 0x02 : 0x03; pubKey = abi.encodePacked(startingByte, pubKeyX); } else { /* Uncompressed public key format */ require(!nested && !bech32, "HEX: claimFlags invalid"); startingByte = 0x04; pubKey = abi.encodePacked(startingByte, pubKeyX, pubKeyY); } bytes20 pubKeyHash = _hash160(pubKey); if (nested) { return _hash160(abi.encodePacked(hex"0014", pubKeyHash)); } return pubKeyHash; } /** * @dev Verify a BTC address and balance are part of the Merkle tree * @param btcAddr Bitcoin address (binary; no base58-check encoding) * @param rawSatoshis Raw BTC address balance in Satoshis * @param proof Merkle tree proof * @return True if valid */ function _btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] memory proof) internal pure returns (bool) { /* Ensure the proof does not attempt to treat a Merkle leaf as if it were an internal Merkle tree node. A leaf will always have the zero-fill. An internal node will never have the zero-fill, as guaranteed by HEX's Merkle tree construction. The first element, proof[0], will always be a leaf because it is the pair of the leaf being validated. The rest of the elements, proof[1..length-1], must be internal nodes. The number of leaves (CLAIMABLE_BTC_ADDR_COUNT) is even, as guaranteed by HEX's Merkle tree construction, which eliminates the only edge-case where this validation would not apply. */ require((uint256(proof[0]) & MERKLE_LEAF_FILL_MASK) == 0, "HEX: proof invalid"); for (uint256 i = 1; i < proof.length; i++) { require((uint256(proof[i]) & MERKLE_LEAF_FILL_MASK) != 0, "HEX: proof invalid"); } /* Calculate the 32 byte Merkle leaf associated with this BTC address and balance 160 bits: BTC address 52 bits: Zero-fill 45 bits: Satoshis (limited by MAX_BTC_ADDR_BALANCE_SATOSHIS) */ bytes32 merkleLeaf = bytes32(btcAddr) | bytes32(rawSatoshis); /* Verify the Merkle tree proof */ return _merkleProofIsValid(merkleLeaf, proof); } /** * @dev Verify a Merkle proof using the UTXO Merkle tree * @param merkleLeaf Leaf asserted to be present in the Merkle tree * @param proof Generated Merkle tree proof * @return True if valid */ function _merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof) private pure returns (bool) { return MerkleProof.verify(proof, MERKLE_TREE_ROOT, merkleLeaf); } function _claimMessageCreate(address claimToAddr, bytes32 claimParamHash, uint8 claimFlags) private pure returns (bytes memory) { bytes memory prefixStr = (claimFlags & CLAIM_FLAG_MSG_PREFIX_OLD) != 0 ? OLD_CLAIM_PREFIX_STR : STD_CLAIM_PREFIX_STR; bool includeAddrChecksum = (claimFlags & CLAIM_FLAG_ETH_ADDR_LOWERCASE) == 0; bytes memory addrStr = _addressStringCreate(claimToAddr, includeAddrChecksum); if (claimParamHash == 0) { return abi.encodePacked( BITCOIN_SIG_PREFIX_LEN, BITCOIN_SIG_PREFIX_STR, uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN, prefixStr, addrStr ); } bytes memory claimParamHashStr = new bytes(CLAIM_PARAM_HASH_HEX_LEN); _hexStringFromData(claimParamHashStr, claimParamHash, CLAIM_PARAM_HASH_BYTE_LEN); return abi.encodePacked( BITCOIN_SIG_PREFIX_LEN, BITCOIN_SIG_PREFIX_STR, uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN + 1 + CLAIM_PARAM_HASH_HEX_LEN, prefixStr, addrStr, "_", claimParamHashStr ); } function _addressStringCreate(address addr, bool includeAddrChecksum) private pure returns (bytes memory addrStr) { addrStr = new bytes(ETH_ADDRESS_HEX_LEN); _hexStringFromData(addrStr, bytes32(bytes20(addr)), ETH_ADDRESS_BYTE_LEN); if (includeAddrChecksum) { bytes32 addrStrHash = keccak256(addrStr); uint256 offset = 0; for (uint256 i = 0; i < ETH_ADDRESS_BYTE_LEN; i++) { uint8 b = uint8(addrStrHash[i]); _addressStringChecksumChar(addrStr, offset++, b >> 4); _addressStringChecksumChar(addrStr, offset++, b & 0x0f); } } return addrStr; } function _addressStringChecksumChar(bytes memory addrStr, uint256 offset, uint8 hashNybble) private pure { bytes1 ch = addrStr[offset]; if (ch >= "a" && hashNybble >= 8) { addrStr[offset] = ch ^ 0x20; } } function _hexStringFromData(bytes memory hexStr, bytes32 data, uint256 dataLen) private pure { uint256 offset = 0; for (uint256 i = 0; i < dataLen; i++) { uint8 b = uint8(data[i]); hexStr[offset++] = HEX_DIGITS[b >> 4]; hexStr[offset++] = HEX_DIGITS[b & 0x0f]; } } /** * @dev sha256(sha256(data)) * @param data Data to be hashed * @return 32-byte hash */ function _hash256(bytes memory data) private pure returns (bytes32) { return sha256(abi.encodePacked(sha256(data))); } /** * @dev ripemd160(sha256(data)) * @param data Data to be hashed * @return 20-byte hash */ function _hash160(bytes memory data) private pure returns (bytes20) { return ripemd160(abi.encodePacked(sha256(data))); } } contract UTXORedeemableToken is UTXOClaimValidation { /** * @dev PUBLIC FACING: Claim a BTC address and its Satoshi balance in Hearts * crediting the appropriate amount to a specified Eth address. Bitcoin ECDSA * signature must be from that BTC address and must match the claim message * for the Eth address. * @param rawSatoshis Raw BTC address balance in Satoshis * @param proof Merkle tree proof * @param claimToAddr Destination Eth address to credit Hearts to * @param pubKeyX First half of uncompressed ECDSA public key for the BTC address * @param pubKeyY Second half of uncompressed ECDSA public key for the BTC address * @param claimFlags Claim flags specifying address and message formats * @param v v parameter of ECDSA signature * @param r r parameter of ECDSA signature * @param s s parameter of ECDSA signature * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer) * @return Total number of Hearts credited, if successful */ function btcAddressClaim( uint256 rawSatoshis, bytes32[] calldata proof, address claimToAddr, bytes32 pubKeyX, bytes32 pubKeyY, uint8 claimFlags, uint8 v, bytes32 r, bytes32 s, uint256 autoStakeDays, address referrerAddr ) external returns (uint256) { /* Sanity check */ require(rawSatoshis <= MAX_BTC_ADDR_BALANCE_SATOSHIS, "HEX: CHK: rawSatoshis"); /* Enforce the minimum stake time for the auto-stake from this claim */ require(autoStakeDays >= MIN_AUTO_STAKE_DAYS, "HEX: autoStakeDays lower than minimum"); /* Ensure signature matches the claim message containing the Eth address and claimParamHash */ { bytes32 claimParamHash = 0; if (claimToAddr != msg.sender) { /* Claimer did not send this, so claim params must be signed */ claimParamHash = keccak256( abi.encodePacked(MERKLE_TREE_ROOT, autoStakeDays, referrerAddr) ); } require( claimMessageMatchesSignature( claimToAddr, claimParamHash, pubKeyX, pubKeyY, claimFlags, v, r, s ), "HEX: Signature mismatch" ); } /* Derive BTC address from public key */ bytes20 btcAddr = pubKeyToBtcAddress(pubKeyX, pubKeyY, claimFlags); /* Ensure BTC address has not yet been claimed */ require(!btcAddressClaims[btcAddr], "HEX: BTC address balance already claimed"); /* Ensure BTC address is part of the Merkle tree */ require( _btcAddressIsValid(btcAddr, rawSatoshis, proof), "HEX: BTC address or balance unknown" ); /* Mark BTC address as claimed */ btcAddressClaims[btcAddr] = true; return _satoshisClaimSync( rawSatoshis, claimToAddr, btcAddr, claimFlags, autoStakeDays, referrerAddr ); } function _satoshisClaimSync( uint256 rawSatoshis, address claimToAddr, bytes20 btcAddr, uint8 claimFlags, uint256 autoStakeDays, address referrerAddr ) private returns (uint256 totalClaimedHearts) { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); totalClaimedHearts = _satoshisClaim( g, rawSatoshis, claimToAddr, btcAddr, claimFlags, autoStakeDays, referrerAddr ); _globalsSync(g, gSnapshot); return totalClaimedHearts; } /** * @dev Credit an Eth address with the Hearts value of a raw Satoshis balance * @param g Cache of stored globals * @param rawSatoshis Raw BTC address balance in Satoshis * @param claimToAddr Destination Eth address for the claimed Hearts to be sent * @param btcAddr Bitcoin address (binary; no base58-check encoding) * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer) * @return Total number of Hearts credited, if successful */ function _satoshisClaim( GlobalsCache memory g, uint256 rawSatoshis, address claimToAddr, bytes20 btcAddr, uint8 claimFlags, uint256 autoStakeDays, address referrerAddr ) private returns (uint256 totalClaimedHearts) { /* Allowed only during the claim phase */ require(g._currentDay >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started"); require(g._currentDay < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended"); /* Check if log data needs to be updated */ _dailyDataUpdateAuto(g); /* Sanity check */ require( g._claimedBtcAddrCount < CLAIMABLE_BTC_ADDR_COUNT, "HEX: CHK: _claimedBtcAddrCount" ); (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts) = _calcClaimValues( g, rawSatoshis ); /* Increment claim count to track viral rewards */ g._claimedBtcAddrCount++; totalClaimedHearts = _remitBonuses( claimToAddr, btcAddr, claimFlags, rawSatoshis, adjSatoshis, claimedHearts, claimBonusHearts, referrerAddr ); /* Auto-stake a percentage of the successful claim */ uint256 autoStakeHearts = totalClaimedHearts * AUTO_STAKE_CLAIM_PERCENT / 100; _stakeStart(g, autoStakeHearts, autoStakeDays, true); /* Mint remaining claimed Hearts to claim address */ _mint(claimToAddr, totalClaimedHearts - autoStakeHearts); return totalClaimedHearts; } function _remitBonuses( address claimToAddr, bytes20 btcAddr, uint8 claimFlags, uint256 rawSatoshis, uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts, address referrerAddr ) private returns (uint256 totalClaimedHearts) { totalClaimedHearts = claimedHearts + claimBonusHearts; uint256 originBonusHearts = claimBonusHearts; if (referrerAddr == address(0)) { /* No referrer */ _emitClaim( claimToAddr, btcAddr, claimFlags, rawSatoshis, adjSatoshis, totalClaimedHearts, referrerAddr ); } else { /* Referral bonus of 10% of total claimed Hearts to claimer */ uint256 referralBonusHearts = totalClaimedHearts / 10; totalClaimedHearts += referralBonusHearts; /* Then a cumulative referrer bonus of 20% to referrer */ uint256 referrerBonusHearts = totalClaimedHearts / 5; originBonusHearts += referralBonusHearts + referrerBonusHearts; if (referrerAddr == claimToAddr) { /* Self-referred */ totalClaimedHearts += referrerBonusHearts; _emitClaim( claimToAddr, btcAddr, claimFlags, rawSatoshis, adjSatoshis, totalClaimedHearts, referrerAddr ); } else { /* Referred by different address */ _emitClaim( claimToAddr, btcAddr, claimFlags, rawSatoshis, adjSatoshis, totalClaimedHearts, referrerAddr ); _mint(referrerAddr, referrerBonusHearts); } } _mint(ORIGIN_ADDR, originBonusHearts); return totalClaimedHearts; } function _emitClaim( address claimToAddr, bytes20 btcAddr, uint8 claimFlags, uint256 rawSatoshis, uint256 adjSatoshis, uint256 claimedHearts, address referrerAddr ) private { emit Claim( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint56(rawSatoshis)) << 40) | (uint256(uint56(adjSatoshis)) << 96) | (uint256(claimFlags) << 152) | (uint256(uint72(claimedHearts)) << 160), uint256(uint160(msg.sender)), btcAddr, claimToAddr, referrerAddr ); if (claimToAddr == msg.sender) { return; } emit ClaimAssist( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint160(btcAddr)) << 40) | (uint256(uint56(rawSatoshis)) << 200), uint256(uint56(adjSatoshis)) | (uint256(uint160(claimToAddr)) << 56) | (uint256(claimFlags) << 216), uint256(uint72(claimedHearts)) | (uint256(uint160(referrerAddr)) << 72), msg.sender ); } function _calcClaimValues(GlobalsCache memory g, uint256 rawSatoshis) private pure returns (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts) { /* Apply Silly Whale reduction */ adjSatoshis = _adjustSillyWhale(rawSatoshis); require( g._claimedSatoshisTotal + adjSatoshis <= CLAIMABLE_SATOSHIS_TOTAL, "HEX: CHK: _claimedSatoshisTotal" ); g._claimedSatoshisTotal += adjSatoshis; uint256 daysRemaining = CLAIM_PHASE_END_DAY - g._currentDay; /* Apply late-claim reduction */ adjSatoshis = _adjustLateClaim(adjSatoshis, daysRemaining); g._unclaimedSatoshisTotal -= adjSatoshis; /* Convert to Hearts and calculate speed bonus */ claimedHearts = adjSatoshis * HEARTS_PER_SATOSHI; claimBonusHearts = _calcSpeedBonus(claimedHearts, daysRemaining); return (adjSatoshis, claimedHearts, claimBonusHearts); } /** * @dev Apply Silly Whale adjustment * @param rawSatoshis Raw BTC address balance in Satoshis * @return Adjusted BTC address balance in Satoshis */ function _adjustSillyWhale(uint256 rawSatoshis) private pure returns (uint256) { if (rawSatoshis < 1000e8) { /* For < 1,000 BTC: no penalty */ return rawSatoshis; } if (rawSatoshis >= 10000e8) { /* For >= 10,000 BTC: penalty is 75%, leaving 25% */ return rawSatoshis / 4; } /* For 1,000 <= BTC < 10,000: penalty scales linearly from 50% to 75% penaltyPercent = (btc - 1000) / (10000 - 1000) * (75 - 50) + 50 = (btc - 1000) / 9000 * 25 + 50 = (btc - 1000) / 360 + 50 appliedPercent = 100 - penaltyPercent = 100 - ((btc - 1000) / 360 + 50) = 100 - (btc - 1000) / 360 - 50 = 50 - (btc - 1000) / 360 = (18000 - (btc - 1000)) / 360 = (18000 - btc + 1000) / 360 = (19000 - btc) / 360 adjustedBtc = btc * appliedPercent / 100 = btc * ((19000 - btc) / 360) / 100 = btc * (19000 - btc) / 36000 adjustedSat = 1e8 * adjustedBtc = 1e8 * (btc * (19000 - btc) / 36000) = 1e8 * ((sat / 1e8) * (19000 - (sat / 1e8)) / 36000) = 1e8 * (sat / 1e8) * (19000 - (sat / 1e8)) / 36000 = (sat / 1e8) * 1e8 * (19000 - (sat / 1e8)) / 36000 = (sat / 1e8) * (19000e8 - sat) / 36000 = sat * (19000e8 - sat) / 36000e8 */ return rawSatoshis * (19000e8 - rawSatoshis) / 36000e8; } /** * @dev Apply late-claim adjustment to scale claim to zero by end of claim phase * @param adjSatoshis Adjusted BTC address balance in Satoshis (after Silly Whale) * @param daysRemaining Number of reward days remaining in claim phase * @return Adjusted BTC address balance in Satoshis (after Silly Whale and Late-Claim) */ function _adjustLateClaim(uint256 adjSatoshis, uint256 daysRemaining) private pure returns (uint256) { /* Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time. adjustedSat = sat * (daysRemaining / CLAIM_PHASE_DAYS) * 100% = sat * daysRemaining / CLAIM_PHASE_DAYS */ return adjSatoshis * daysRemaining / CLAIM_PHASE_DAYS; } /** * @dev Calculates speed bonus for claiming earlier in the claim phase * @param claimedHearts Hearts claimed from adjusted BTC address balance Satoshis * @param daysRemaining Number of claim days remaining in claim phase * @return Speed bonus in Hearts */ function _calcSpeedBonus(uint256 claimedHearts, uint256 daysRemaining) private pure returns (uint256) { /* Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time. Speed bonus is 20% ... 0% inclusive. bonusHearts = claimedHearts * ((daysRemaining - 1) / (CLAIM_PHASE_DAYS - 1)) * 20% = claimedHearts * ((daysRemaining - 1) / (CLAIM_PHASE_DAYS - 1)) * 20/100 = claimedHearts * ((daysRemaining - 1) / (CLAIM_PHASE_DAYS - 1)) / 5 = claimedHearts * (daysRemaining - 1) / ((CLAIM_PHASE_DAYS - 1) * 5) */ return claimedHearts * (daysRemaining - 1) / ((CLAIM_PHASE_DAYS - 1) * 5); } } contract TransformableToken is UTXORedeemableToken { /** * @dev PUBLIC FACING: Enter the tranform lobby for the current round * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer) */ function xfLobbyEnter(address referrerAddr) external payable { uint256 enterDay = _currentDay(); require(enterDay < CLAIM_PHASE_END_DAY, "HEX: Lobbies have ended"); uint256 rawAmount = msg.value; require(rawAmount != 0, "HEX: Amount required"); XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender]; uint256 entryIndex = qRef.tailIndex++; qRef.entries[entryIndex] = XfLobbyEntryStore(uint96(rawAmount), referrerAddr); xfLobby[enterDay] += rawAmount; _emitXfLobbyEnter(enterDay, entryIndex, rawAmount, referrerAddr); } /** * @dev PUBLIC FACING: Leave the transform lobby after the round is complete * @param enterDay Day number when the member entered * @param count Number of queued-enters to exit (optional; 0 for all) */ function xfLobbyExit(uint256 enterDay, uint256 count) external { require(enterDay < _currentDay(), "HEX: Round is not complete"); XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender]; uint256 headIndex = qRef.headIndex; uint256 endIndex; if (count != 0) { require(count <= qRef.tailIndex - headIndex, "HEX: count invalid"); endIndex = headIndex + count; } else { endIndex = qRef.tailIndex; require(headIndex < endIndex, "HEX: count invalid"); } uint256 waasLobby = _waasLobby(enterDay); uint256 _xfLobby = xfLobby[enterDay]; uint256 totalXfAmount = 0; uint256 originBonusHearts = 0; do { uint256 rawAmount = qRef.entries[headIndex].rawAmount; address referrerAddr = qRef.entries[headIndex].referrerAddr; delete qRef.entries[headIndex]; uint256 xfAmount = waasLobby * rawAmount / _xfLobby; if (referrerAddr == address(0)) { /* No referrer */ _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr); } else { /* Referral bonus of 10% of xfAmount to member */ uint256 referralBonusHearts = xfAmount / 10; xfAmount += referralBonusHearts; /* Then a cumulative referrer bonus of 20% to referrer */ uint256 referrerBonusHearts = xfAmount / 5; if (referrerAddr == msg.sender) { /* Self-referred */ xfAmount += referrerBonusHearts; _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr); } else { /* Referred by different address */ _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr); _mint(referrerAddr, referrerBonusHearts); } originBonusHearts += referralBonusHearts + referrerBonusHearts; } totalXfAmount += xfAmount; } while (++headIndex < endIndex); qRef.headIndex = uint40(headIndex); if (originBonusHearts != 0) { _mint(ORIGIN_ADDR, originBonusHearts); } if (totalXfAmount != 0) { _mint(msg.sender, totalXfAmount); } } /** * @dev PUBLIC FACING: Release any value that has been sent to the contract */ function xfLobbyFlush() external { require(address(this).balance != 0, "HEX: No value"); FLUSH_ADDR.transfer(address(this).balance); } /** * @dev PUBLIC FACING: External helper to return multiple values of xfLobby[] with * a single call * @param beginDay First day of data range * @param endDay Last day (non-inclusive) of data range * @return Fixed array of values */ function xfLobbyRange(uint256 beginDay, uint256 endDay) external view returns (uint256[] memory list) { require( beginDay < endDay && endDay <= CLAIM_PHASE_END_DAY && endDay <= _currentDay(), "HEX: invalid range" ); list = new uint256[](endDay - beginDay); uint256 src = beginDay; uint256 dst = 0; do { list[dst++] = uint256(xfLobby[src++]); } while (src < endDay); return list; } /** * @dev PUBLIC FACING: Return a current lobby member queue entry. * Only needed due to limitations of the standard ABI encoder. * @param memberAddr Eth address of the lobby member * @param entryId 49 bit compound value. Top 9 bits: enterDay, Bottom 40 bits: entryIndex * @return 1: Raw amount that was entered with; 2: Referring Eth addr (optional; 0x0 for no referrer) */ function xfLobbyEntry(address memberAddr, uint256 entryId) external view returns (uint256 rawAmount, address referrerAddr) { uint256 enterDay = entryId >> XF_LOBBY_ENTRY_INDEX_SIZE; uint256 entryIndex = entryId & XF_LOBBY_ENTRY_INDEX_MASK; XfLobbyEntryStore storage entry = xfLobbyMembers[enterDay][memberAddr].entries[entryIndex]; require(entry.rawAmount != 0, "HEX: Param invalid"); return (entry.rawAmount, entry.referrerAddr); } /** * @dev PUBLIC FACING: Return the lobby days that a user is in with a single call * @param memberAddr Eth address of the user * @return Bit vector of lobby day numbers */ function xfLobbyPendingDays(address memberAddr) external view returns (uint256[XF_LOBBY_DAY_WORDS] memory words) { uint256 day = _currentDay() + 1; if (day > CLAIM_PHASE_END_DAY) { day = CLAIM_PHASE_END_DAY; } while (day-- != 0) { if (xfLobbyMembers[day][memberAddr].tailIndex > xfLobbyMembers[day][memberAddr].headIndex) { words[day >> 8] |= 1 << (day & 255); } } return words; } function _waasLobby(uint256 enterDay) private returns (uint256 waasLobby) { if (enterDay >= CLAIM_PHASE_START_DAY) { GlobalsCache memory g; GlobalsCache memory gSnapshot; _globalsLoad(g, gSnapshot); _dailyDataUpdateAuto(g); uint256 unclaimed = dailyData[enterDay].dayUnclaimedSatoshisTotal; waasLobby = unclaimed * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS; _globalsSync(g, gSnapshot); } else { waasLobby = WAAS_LOBBY_SEED_HEARTS; } return waasLobby; } function _emitXfLobbyEnter( uint256 enterDay, uint256 entryIndex, uint256 rawAmount, address referrerAddr ) private { emit XfLobbyEnter( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint96(rawAmount)) << 40), msg.sender, (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex, referrerAddr ); } function _emitXfLobbyExit( uint256 enterDay, uint256 entryIndex, uint256 xfAmount, address referrerAddr ) private { emit XfLobbyExit( // (auto-generated event) uint256(uint40(block.timestamp)) | (uint256(uint72(xfAmount)) << 40), msg.sender, (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex, referrerAddr ); } } contract HEX is TransformableToken { constructor() public { /* Initialize global shareRate to 1 */ globals.shareRate = uint40(1 * SHARE_RATE_SCALE); /* Initialize dailyDataCount to skip pre-claim period */ globals.dailyDataCount = uint16(PRE_CLAIM_DAYS); /* Add all Satoshis from UTXO snapshot to contract */ globals.claimStats = _claimStatsEncode( 0, // _claimedBtcAddrCount 0, // _claimedSatoshisTotal FULL_SATOSHIS_TOTAL // _unclaimedSatoshisTotal ); } function() external payable {} }
File 2 of 2: HXY
pragma solidity ^0.6.2; // SPDX-License-Identifier: MIT /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ contract Context { // Empty internal constructor, to prevent people from mistakenly deploying // an instance of this contract, which should be used via inheritance. constructor () internal { } function _msgSender() internal view virtual returns (address payable) { return msg.sender; } function _msgData() internal view virtual returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } // SPDX-License-Identifier: MIT /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: 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 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: MIT /** * @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. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (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. */ 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-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); 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. */ function div(uint256 a, uint256 b) internal pure returns (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. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return 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. */ function mod(uint256 a, uint256 b) internal pure returns (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. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // SPDX-License-Identifier: MIT /** * @dev Collection of functions related to the address type */ library Address { /** * @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 * ==== */ function isContract(address account) internal view returns (bool) { // According to EIP-1052, 0x0 is the value returned for not-yet created accounts // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned // for accounts without code, i.e. `keccak256('')` bytes32 codehash; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } /** * @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]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } } /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20MinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin guidelines: functions revert instead * of returning `false` on failure. This behavior is nonetheless conventional * and does not conflict with the expectations of ERC20 applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20 { using SafeMath for uint256; using Address for address; mapping (address => uint256) internal _balances; mapping (address => mapping (address => uint256)) private _allowances; uint256 internal _totalSupply; string private _name; string private _symbol; uint8 private _decimals; uint256 private _cap; /** * @dev Sets the values for {name} and {symbol}, initializes {decimals} with * a default value of 18. * * To select a different value for {decimals}, use {_setupDecimals}. * * All three of these values are immutable: they can only be set once during * construction. */ constructor (string memory name, string memory symbol) public { _name = name; _symbol = symbol; _decimals = 18; } /** * @dev Returns the name of the token. */ function name() public view returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5,05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is * called. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view returns (uint8) { return _decimals; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { require(recipient != address(this), "ERC20: Cannot transfer to self"); _transfer(_msgSender(), recipient, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { _approve(_msgSender(), spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}; * * Requirements: * - `sender` and `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. * - the caller must have allowance for ``sender``'s tokens of at least * `amount`. */ function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { _transfer(sender, recipient, amount); _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); return true; } /** * @dev Moves tokens `amount` from `sender` to `recipient`. * * This is internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc.S * * Emits a {Transfer} event. * * Requirements: * * - `sender` cannot be the zero address. * - `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. */ function _transfer(address sender, address recipient, uint256 amount) internal virtual { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(sender, recipient, amount); _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); _balances[recipient] = _balances[recipient].add(amount); emit Transfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements * * - `to` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); emit Transfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); _totalSupply = _totalSupply.sub(amount); emit Transfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. * * This is internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Sets {decimals} to a value other than the default one of 18. * * WARNING: This function should only be called from the constructor. Most * applications that interact with token contracts will not expect * {decimals} to ever change, and may work incorrectly if it does. */ function _setupDecimals(uint8 decimals_) internal { _decimals = decimals_; } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be to transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } } // SPDX-License-Identifier: MIT /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ``` * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256` * (`UintSet`) are supported. */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping (bytes32 => uint256) _indexes; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. bytes32 lastvalue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastvalue; // Update the index for the moved value set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { require(set._values.length > index, "EnumerableSet: index out of bounds"); return set._values[index]; } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(value))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(value))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(value))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint256(_at(set._inner, index))); } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values on the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } } // SPDX-License-Identifier: MIT /** * @dev Contract module that allows children to implement role-based access * control mechanisms. * * Roles are referred to by their `bytes32` identifier. These should be exposed * in the external API and be unique. The best way to achieve this is by * using `public constant` hash digests: * * ``` * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); * ``` * * Roles can be used to represent a set of permissions. To restrict access to a * function call, use {hasRole}: * * ``` * function foo() public { * require(hasRole(MY_ROLE, msg.sender)); * ... * } * ``` * * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRole}. * * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means * that only accounts with this role will be able to grant or revoke other * roles. More complex role relationships can be created by using * {_setRoleAdmin}. * * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. */ abstract contract AccessControl is Context { using EnumerableSet for EnumerableSet.AddressSet; using Address for address; struct RoleData { EnumerableSet.AddressSet members; bytes32 adminRole; } mapping (bytes32 => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Emitted when `account` is granted `role`. * * `sender` is the account that originated the contract call, an admin role * bearer except when using {_setupRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Emitted when `account` is revoked `role`. * * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) public view returns (bool) { return _roles[role].members.contains(account); } /** * @dev Returns the number of accounts that have `role`. Can be used * together with {getRoleMember} to enumerate all bearers of a role. */ function getRoleMemberCount(bytes32 role) public view returns (uint256) { return _roles[role].members.length(); } /** * @dev Returns one of the accounts that have `role`. `index` must be a * value between 0 and {getRoleMemberCount}, non-inclusive. * * Role bearers are not sorted in any particular way, and their ordering may * change at any point. * * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure * you perform all queries on the same block. See the following * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] * for more information. */ function getRoleMember(bytes32 role, uint256 index) public view returns (address) { return _roles[role].members.at(index); } /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) public view returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) public virtual { require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant"); _grantRole(role, account); } /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) public virtual { require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke"); _revokeRole(role, account); } /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been granted `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `account`. */ function renounceRole(bytes32 role, address account) public virtual { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); _revokeRole(role, account); } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. Note that unlike {grantRole}, this function doesn't perform any * checks on the calling account. * * [WARNING] * ==== * This function should only be called from the constructor when setting * up the initial roles for the system. * * Using this function in any other way is effectively circumventing the admin * system imposed by {AccessControl}. * ==== */ function _setupRole(bytes32 role, address account) internal virtual { _grantRole(role, account); } /** * @dev Sets `adminRole` as ``role``'s admin role. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { _roles[role].adminRole = adminRole; } function _grantRole(bytes32 role, address account) private { if (_roles[role].members.add(account)) { emit RoleGranted(role, account, _msgSender()); } } function _revokeRole(bytes32 role, address account) private { if (_roles[role].members.remove(account)) { emit RoleRevoked(role, account, _msgSender()); } } } // SPDX-License-Identifier: MIT /** * @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]. */ contract ReentrancyGuard { bool private _notEntered; constructor () internal { // Storing an initial 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 percetange 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. _notEntered = true; } /** * @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(_notEntered, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _notEntered = false; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _notEntered = true; } } library WhitelistLib { struct AllowedAddress { bool tradeable; uint256 lockPeriod; uint256 dailyLimit; uint256 dailyLimitToday; uint256 addedAt; uint256 recordTime; } } contract HexWhitelist is AccessControl, ReentrancyGuard { bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); uint256 public constant SECONDS_IN_DAY = 86400; using WhitelistLib for WhitelistLib.AllowedAddress; mapping(address => WhitelistLib.AllowedAddress) internal exchanges; mapping(address => WhitelistLib.AllowedAddress) internal dapps; mapping(address => WhitelistLib.AllowedAddress) internal referrals; uint256 internal whitelistRecordTime; modifier onlyAdminOrDeployerRole() { bool hasAdminRole = hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); bool hasDeployerRole = hasRole(DEPLOYER_ROLE, _msgSender()); require(hasAdminRole || hasDeployerRole, "Must have admin or deployer role"); _; } constructor (address _adminAddress) public { _setupRole(DEPLOYER_ROLE, _msgSender()); _setupRole(DEFAULT_ADMIN_ROLE, _adminAddress); whitelistRecordTime = SafeMath.add(block.timestamp, SafeMath.mul(1, SECONDS_IN_DAY)); } function registerExchangeTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole { _registerExchange(_address, true, 0, dailyLimit); } function registerDappTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole { _registerDapp(_address, true, 0, dailyLimit); } function registerReferralTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole { _registerReferral(_address, true, 0, dailyLimit); } function registerExchangeNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole { _registerExchange(_address, false, lockPeriod, dailyLimit); } function registerDappNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole { _registerDapp(_address, false, lockPeriod, dailyLimit); } function registerReferralNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole { _registerReferral(_address, false, lockPeriod, dailyLimit); } function unregisterExchange(address _address) public onlyAdminOrDeployerRole { delete exchanges[_address]; } function unregisterDapp(address _address) public onlyAdminOrDeployerRole { delete dapps[_address]; } function unregisterReferral(address _address) public onlyAdminOrDeployerRole { delete referrals[_address]; } function setExchangepDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole { exchanges[_address].dailyLimit = _dailyLimit; } function setDappDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole { dapps[_address].dailyLimit = _dailyLimit; } function setReferralDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole { referrals[_address].dailyLimit = _dailyLimit; } function setExchangeLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole { require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address"); exchanges[_address].lockPeriod = _lockPeriod; } function setDappLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole { require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address"); dapps[_address].lockPeriod = _lockPeriod; } function setReferralLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole { require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address"); dapps[_address].lockPeriod = _lockPeriod; } function addToExchangeDailyLimit(address _address, uint256 amount) public { if (exchanges[_address].dailyLimit > 0) { if (isNewDayStarted(exchanges[_address].recordTime)) { exchanges[_address].dailyLimitToday = 0; exchanges[_address].recordTime = getNewRecordTime(); } uint256 limitToday = dapps[_address].dailyLimitToday; require(SafeMath.add(limitToday, amount) < exchanges[_address].dailyLimit, "daily limit exceeded"); exchanges[_address].dailyLimitToday = SafeMath.add(limitToday, amount); } } function addToDappDailyLimit(address _address, uint256 amount) public { if (dapps[_address].dailyLimit > 0) { if (isNewDayStarted(dapps[_address].recordTime)) { dapps[_address].dailyLimitToday = 0; dapps[_address].recordTime = getNewRecordTime(); } uint256 limitToday = dapps[_address].dailyLimitToday; require(SafeMath.add(limitToday, amount) < dapps[_address].dailyLimit, "daily limit exceeded"); dapps[_address].dailyLimitToday = SafeMath.add(limitToday, amount); } } function addToReferralDailyLimit(address _address, uint256 amount) public { if (referrals[_address].dailyLimit > 0) { if (isNewDayStarted(referrals[_address].recordTime)) { referrals[_address].dailyLimitToday = 0; referrals[_address].recordTime = getNewRecordTime(); } uint256 limitToday = referrals[_address].dailyLimitToday; require(SafeMath.add(limitToday, amount) < referrals[_address].dailyLimit, "daily limit exceeded"); referrals[_address].dailyLimitToday = SafeMath.add(limitToday, amount); } } function isRegisteredDapp(address _address) public view returns (bool) { return (dapps[_address].addedAt != 0) ? true : false; } function isRegisteredReferral(address _address) public view returns (bool) { if (dapps[_address].addedAt != 0) { return true; } else { return false; } } function isRegisteredDappOrReferral(address executionAddress) public view returns (bool) { if (isRegisteredDapp(executionAddress) || isRegisteredReferral(executionAddress)) { return true; } else { return false; } } function isRegisteredExchange(address _address) public view returns (bool) { if (exchanges[_address].addedAt != 0) { return true; } else { return false; } } function getExchangeTradeable(address _address) public view returns (bool) { return exchanges[_address].tradeable; } function getDappTradeable(address _address) public view returns (bool) { return dapps[_address].tradeable; } function getReferralTradeable(address _address) public view returns (bool) { return referrals[_address].tradeable; } function getDappOrReferralTradeable(address _address) public view returns (bool) { if (isRegisteredDapp(_address)) { return dapps[_address].tradeable; } else { return referrals[_address].tradeable; } } function getExchangeLockPeriod(address _address) public view returns (uint256) { return exchanges[_address].lockPeriod; } function getDappLockPeriod(address _address) public view returns (uint256) { return dapps[_address].lockPeriod; } function getReferralLockPeriod(address _address) public view returns (uint256) { return referrals[_address].lockPeriod; } function getDappOrReferralLockPeriod(address _address) public view returns (uint256) { if (isRegisteredDapp(_address)) { return dapps[_address].lockPeriod; } else { return referrals[_address].lockPeriod; } } function getDappDailyLimit(address _address) public view returns (uint256) { return dapps[_address].dailyLimit; } function getReferralDailyLimit(address _address) public view returns (uint256) { return referrals[_address].dailyLimit; } function getDappOrReferralDailyLimit(address _address) public view returns (uint256) { if (isRegisteredDapp(_address)) { return dapps[_address].dailyLimit; } else { return referrals[_address].dailyLimit; } } function getExchangeTodayMinted(address _address) public view returns (uint256) { return exchanges[_address].dailyLimitToday; } function getDappTodayMinted(address _address) public view returns (uint256) { return dapps[_address].dailyLimitToday; } function getReferralTodayMinted(address _address) public view returns (uint256) { return referrals[_address].dailyLimitToday; } function getExchangeRecordTimed(address _address) public view returns (uint256) { return exchanges[_address].recordTime; } function getDappRecordTimed(address _address) public view returns (uint256) { return dapps[_address].recordTime; } function getReferralRecordTimed(address _address) public view returns (uint256) { return referrals[_address].recordTime; } function getNewRecordTime() internal view returns (uint256) { return SafeMath.add(block.timestamp, SafeMath.mul(1, SECONDS_IN_DAY)); } function isNewDayStarted(uint256 oldRecordTime) internal view returns (bool) { return block.timestamp > oldRecordTime ? true : false; } function _registerExchange(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal { require(!isRegisteredDappOrReferral(_address), "address already registered as dapp or referral"); require(!isRegisteredExchange(_address), "exchange already registered"); exchanges[_address] = WhitelistLib.AllowedAddress({ tradeable: tradeable, lockPeriod: lockPeriod, dailyLimit: dailyLimit, dailyLimitToday: 0, addedAt: block.timestamp, recordTime: getNewRecordTime() }); } function _registerDapp(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal { require(!isRegisteredExchange(_address) && !isRegisteredReferral(_address), "address already registered as exchange or referral"); require(!isRegisteredDapp(_address), "address already registered"); dapps[_address] = WhitelistLib.AllowedAddress({ tradeable: tradeable, lockPeriod: lockPeriod, dailyLimit: dailyLimit, dailyLimitToday: 0, addedAt: block.timestamp, recordTime: getNewRecordTime() }); } function _registerReferral(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal { require(!isRegisteredExchange(_address) && !isRegisteredDapp(_address), "address already registered as exchange or referral"); require(!isRegisteredReferral(_address), "address already registered"); referrals[_address] = WhitelistLib.AllowedAddress({ tradeable: tradeable, lockPeriod: lockPeriod, dailyLimit: dailyLimit, dailyLimitToday: 0, addedAt: block.timestamp, recordTime: getNewRecordTime() }); } } contract HexMoneyInternal is AccessControl, ReentrancyGuard { bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); // production uint256 public constant SECONDS_IN_DAY = 86400; HexWhitelist internal whitelist; modifier onlyAdminOrDeployerRole() { bool hasAdminRole = hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); bool hasDeployerRole = hasRole(DEPLOYER_ROLE, _msgSender()); require(hasAdminRole || hasDeployerRole, "Must have admin or deployer role"); _; } function getWhitelistAddress() public view returns (address) { return address(whitelist); } } /** * @dev Extension of {ERC20} that adds a cap to the supply of tokens. */ abstract contract ERC20FreezableCapped is ERC20, HexMoneyInternal { uint256 public constant MINIMAL_FREEZE_PERIOD = 7; // 7 days // freezing chains mapping (bytes32 => uint256) internal chains; // freezing amounts for each chain //mapping (bytes32 => uint) internal freezings; mapping(bytes32 => Freezing) internal freezings; // total freezing balance per address mapping (address => uint) internal freezingBalance; mapping(address => bytes32[]) internal freezingsByUser; mapping (address => uint256) internal latestFreezingTime; struct Freezing { address user; uint256 startDate; uint256 freezeDays; uint256 freezeAmount; bool capitalized; } event Freezed(address indexed to, uint256 release, uint amount); event Released(address indexed owner, uint amount); uint256 private _cap; /** * @dev Sets the value of the `cap`. This value is immutable, it can only be * set once during construction. */ constructor (uint256 cap) public { require(cap > 0, "ERC20Capped: cap is 0"); _cap = cap; } /** * @dev Gets the balance of the specified address include freezing tokens. * @param account The address to query the the balance of. * @return balance An uint256 representing the amount owned by the passed address. */ function balanceOf(address account) public view virtual override returns (uint256) { return super.balanceOf(account) + freezingBalance[account]; } /** * @dev Gets the balance of the specified address without freezing tokens. * @param account The address to query the the balance of. * @return balance An uint256 representing the amount owned by the passed address. */ function actualBalanceOf(address account) public view returns (uint256 balance) { return super.balanceOf(account); } function freezingBalanceOf(address account) public view returns (uint256 balance) { return freezingBalance[account]; } function latestFreezeTimeOf(address account) public view returns (uint256) { return latestFreezingTime[account]; } /** * @dev Returns the cap on the token's total supply. */ function cap() public view returns (uint256) { return _cap; } function getUserFreezings(address _user) public view returns (bytes32[] memory userFreezings) { return freezingsByUser[_user]; } function getFreezingById(bytes32 freezingId) public view returns (address user, uint256 startDate, uint256 freezeDays, uint256 freezeAmount, bool capitalized) { Freezing memory userFreeze = freezings[freezingId]; user = userFreeze.user; startDate = userFreeze.startDate; freezeDays = userFreeze.freezeDays; freezeAmount = userFreeze.freezeAmount; capitalized = userFreeze.capitalized; } function freeze(address _to, uint256 _start, uint256 _freezeDays, uint256 _amount) internal { require(_to != address(0x0), "FreezeContract: address cannot be zero"); require(_start >= block.timestamp, "FreezeContract: start date cannot be in past"); require(_freezeDays >= 0, "FreezeContract: amount of freeze days cannot be zero"); require(_amount <= _balances[_msgSender()], "FreezeContract: freeze amount exceeds unfrozen balance"); Freezing memory userFreeze = Freezing({ user: _to, startDate: _start, freezeDays: _freezeDays, freezeAmount: _amount, capitalized: false }); bytes32 freezeId = _toFreezeKey(_to, _start); _balances[_msgSender()] = _balances[_msgSender()].sub(_amount); freezingBalance[_to] = freezingBalance[_to].add(_amount); freezings[freezeId] = userFreeze; freezingsByUser[_to].push(freezeId); latestFreezingTime[_to] = _start; emit Transfer(_msgSender(), _to, _amount); emit Freezed(_to, _start, _amount); } function mintAndFreeze(address _to, uint256 _start, uint256 _freezeDays, uint256 _amount) internal { require(_to != address(0x0), "FreezeContract: address cannot be zero"); require(_start >= block.timestamp, "FreezeContract: start date cannot be in past"); require(_freezeDays >= 0, "FreezeContract: amount of freeze days cannot be zero"); Freezing memory userFreeze = Freezing({ user: _to, startDate: _start, freezeDays: _freezeDays, freezeAmount: _amount, capitalized: false }); bytes32 freezeId = _toFreezeKey(_to, _start); freezingBalance[_to] = freezingBalance[_to].add(_amount); freezings[freezeId] = userFreeze; freezingsByUser[_to].push(freezeId); latestFreezingTime[_to] = _start; _totalSupply = _totalSupply.add(_amount); emit Transfer(_msgSender(), _to, _amount); emit Freezed(_to, _start, _amount); } function _toFreezeKey(address _user, uint256 _startDate) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_user, _startDate)); } function release(uint256 _startTime) internal { bytes32 freezeId = _toFreezeKey(_msgSender(), _startTime); Freezing memory userFreeze = freezings[freezeId]; uint256 lockUntil = _daysToTimestampFrom(userFreeze.startDate, userFreeze.freezeDays); require(block.timestamp >= lockUntil, "cannot release before lock"); uint256 amount = userFreeze.freezeAmount; _balances[_msgSender()] = _balances[_msgSender()].add(amount); freezingBalance[_msgSender()] = freezingBalance[_msgSender()].sub(amount); _deleteFreezing(freezeId, freezingsByUser[_msgSender()]); emit Released(_msgSender(), amount); } function refreeze(uint256 _startTime, uint256 addAmount) internal { bytes32 freezeId = _toFreezeKey(_msgSender(), _startTime); Freezing storage userFreeze = freezings[freezeId]; uint256 lockUntil; if (!userFreeze.capitalized) { lockUntil = _daysToTimestampFrom(userFreeze.startDate, userFreeze.freezeDays); } else { lockUntil = _daysToTimestampFrom(userFreeze.startDate, 1); } require(block.timestamp >= lockUntil, "cannot refreeze before lock"); bytes32 newFreezeId = _toFreezeKey(userFreeze.user, block.timestamp); uint256 oldFreezeAmount = userFreeze.freezeAmount; uint256 newFreezeAmount = SafeMath.add(userFreeze.freezeAmount, addAmount); Freezing memory newFreeze = Freezing({ user: userFreeze.user, startDate: block.timestamp, freezeDays: userFreeze.freezeDays, freezeAmount: newFreezeAmount, capitalized: true }); freezingBalance[_msgSender()] = freezingBalance[_msgSender()].add(addAmount); freezings[newFreezeId] = newFreeze; freezingsByUser[userFreeze.user].push(newFreezeId); latestFreezingTime[userFreeze.user] = block.timestamp; _deleteFreezing(freezeId, freezingsByUser[_msgSender()]); delete freezings[freezeId]; emit Released(_msgSender(), oldFreezeAmount); emit Transfer(_msgSender(), _msgSender(), addAmount); emit Freezed(_msgSender(), block.timestamp, newFreezeAmount); } function _deleteFreezing(bytes32 freezingId, bytes32[] storage userFreezings) internal { uint256 freezingIndex; bool freezingFound; for (uint256 i; i < userFreezings.length; i++) { if (userFreezings[i] == freezingId) { freezingIndex = i; freezingFound = true; } } if (freezingFound) { userFreezings[freezingIndex] = userFreezings[userFreezings.length - 1]; delete userFreezings[userFreezings.length - 1]; userFreezings.pop(); } } function _daysToTimestampFrom(uint256 from, uint256 lockDays) internal pure returns(uint256) { return SafeMath.add(from, SafeMath.mul(lockDays, SECONDS_IN_DAY)); } function _daysToTimestamp(uint256 lockDays) internal view returns(uint256) { return _daysToTimestampFrom(block.timestamp, lockDays); } function _getBaseLockDays() internal view returns (uint256) { return _daysToTimestamp(MINIMAL_FREEZE_PERIOD); } function _getBaseLockDaysFrom(uint256 from) internal pure returns (uint256) { return _daysToTimestampFrom(from, MINIMAL_FREEZE_PERIOD); } /** * @dev See {ERC20-_beforeTokenTransfer}. * * Requirements: * * - minted tokens must not cause the total supply to go over the cap. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override { super._beforeTokenTransfer(from, to, amount); if (from == address(0)) { // When minting tokens require(totalSupply().add(amount) <= _cap, "ERC20Capped: cap exceeded"); } } } abstract contract HexMoneyTeam is AccessControl { bytes32 public constant TEAM_ROLE = keccak256("TEAM_ROLE"); address payable internal teamAddress; modifier onlyTeamRole() { require(hasRole(TEAM_ROLE, _msgSender()), "Must have admin role to setup"); _; } function getTeamAddress() public view returns (address) { return teamAddress; } } contract HXY is ERC20FreezableCapped, HexMoneyTeam { using WhitelistLib for WhitelistLib.AllowedAddress; uint256 internal liquidSupply = 694866350105876; uint256 internal lockedSupply = SafeMath.mul(6, 10 ** 14); uint256 internal lockedSupplyFreezingStarted; address internal lockedSupplyAddress; address internal liquidSupplyAddress; struct LockedSupplyAddresses { address firstAddress; address secondAddress; address thirdAddress; address fourthAddress; address fifthAddress; address sixthAddress; } LockedSupplyAddresses internal lockedSupplyAddresses; bool internal lockedSupplyPreminted; // total amounts variables uint256 internal totalMinted; uint256 internal totalFrozen; uint256 internal totalCirculating; uint256 internal totalPayedInterest; // round logic structures uint256 internal hxyMintedMultiplier = 10 ** 6; uint256[] internal hxyRoundMintAmount = [3, 6, 9, 12, 15, 18, 21, 24, 27]; uint256 internal baseHexToHxyRate = 10 ** 3; uint256[] internal hxyRoundBaseRate = [2, 3, 4, 5, 6, 7, 8, 9, 10]; uint256 internal maxHxyRounds = 9; // initial round uint256 internal currentHxyRound; uint256 internal currentHxyRoundRate = SafeMath.mul(hxyRoundBaseRate[0], baseHexToHxyRate); //constructor(address payable _teamAddress, address _liqSupAddress, address _lockSupAddress, address _migratedSupplyAddress) constructor(address _whitelistAddress, address _liqSupAddress, uint256 _liqSupAmount) public ERC20FreezableCapped(SafeMath.mul(60, 10 ** 14)) // cap = 60,000,000 ERC20("HEX Money", "HXY") { require(address(_whitelistAddress) != address(0x0), "whitelist address should not be empty"); require(address(_liqSupAddress) != address(0x0), "liquid supply address should not be empty"); _setupDecimals(8); _setupRole(DEPLOYER_ROLE, _msgSender()); whitelist = HexWhitelist(_whitelistAddress); _premintLiquidSupply(_liqSupAddress, _liqSupAmount); } function getRemainingHxyInRound() public view returns (uint256) { return _getRemainingHxyInRound(currentHxyRound); } function getTotalHxyInRound() public view returns (uint256) { return _getTotalHxyInRound(currentHxyRound); } function getTotalHxyMinted() public view returns (uint256) { return totalMinted; } function getCirculatingSupply() public view returns (uint256) { return totalCirculating; } function getCurrentHxyRound() public view returns (uint256) { return currentHxyRound; } function getCurrentHxyRate() public view returns (uint256) { return currentHxyRoundRate; } function getTotalFrozen() public view returns (uint256) { return totalFrozen; } function getTotalPayedInterest() public view returns (uint256) { return totalPayedInterest; } function getCurrentInterestAmount(address _addr, uint256 _freezeStartDate) public view returns (uint256) { bytes32 freezeId = _toFreezeKey(_addr, _freezeStartDate); Freezing memory userFreeze = freezings[freezeId]; uint256 frozenTokens = userFreeze.freezeAmount; if (frozenTokens != 0) { uint256 startFreezeDate = userFreeze.startDate; uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, startFreezeDate), SECONDS_IN_DAY); return SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays); } else { return 0; } } function mintFromExchange(address account, uint256 amount) public { address executionAddress = _msgSender(); require(whitelist.isRegisteredExchange(executionAddress), "must be executed from whitelisted dapp"); whitelist.addToExchangeDailyLimit(executionAddress, amount); if (whitelist.getExchangeTradeable(executionAddress)) { mint(account, amount); } else { uint256 lockPeriod = whitelist.getExchangeLockPeriod(executionAddress); mintAndFreezeTo(account, amount, lockPeriod); } } function mintFromDappOrReferral(address account, uint256 amount) public { address executionAddress = _msgSender(); require(whitelist.isRegisteredDappOrReferral(executionAddress), "must be executed from whitelisted address"); if (whitelist.isRegisteredDapp(executionAddress)) { whitelist.addToDappDailyLimit(executionAddress, amount); } else { whitelist.addToReferralDailyLimit(executionAddress, amount); } if (whitelist.getDappTradeable(executionAddress)) { _mintDirectly(account, amount); } else { uint256 lockPeriod = whitelist.getDappOrReferralLockPeriod(executionAddress); _mintAndFreezeDirectly(account, amount, lockPeriod); } } function freezeHxy(uint256 lockAmount) public { freeze(_msgSender(), block.timestamp, MINIMAL_FREEZE_PERIOD, lockAmount); totalFrozen = SafeMath.add(totalFrozen, lockAmount); totalCirculating = SafeMath.sub(totalCirculating, lockAmount); } function refreezeHxy(uint256 startDate) public { bytes32 freezeId = _toFreezeKey(_msgSender(), startDate); Freezing memory userFreezing = freezings[freezeId]; uint256 frozenTokens = userFreezing.freezeAmount; uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, userFreezing.startDate), SECONDS_IN_DAY); uint256 interestAmount = SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays); refreeze(startDate, interestAmount); totalFrozen = SafeMath.add(totalFrozen, interestAmount); } function releaseFrozen(uint256 _startDate) public { bytes32 freezeId = _toFreezeKey(_msgSender(), _startDate); Freezing memory userFreezing = freezings[freezeId]; uint256 frozenTokens = userFreezing.freezeAmount; release(_startDate); if (!_isLockedAddress()) { uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, userFreezing.startDate), SECONDS_IN_DAY); uint256 interestAmount = SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays); _mint(_msgSender(), interestAmount); totalFrozen = SafeMath.sub(totalFrozen, frozenTokens); totalCirculating = SafeMath.add(totalCirculating, frozenTokens); totalPayedInterest = SafeMath.add(totalPayedInterest, interestAmount); } } function mint(address _to, uint256 _amount) internal { _preprocessMint(_to, _amount); } function mintAndFreezeTo(address _to, uint _amount, uint256 _lockDays) internal { _preprocessMintWithFreeze(_to, _amount, _lockDays); } function _premintLiquidSupply(address _liqSupAddress, uint256 _liqSupAmount) internal { require(_liqSupAddress != address(0x0), "liquid supply address cannot be zero"); require(_liqSupAmount != 0, "liquid supply amount cannot be zero"); liquidSupplyAddress = _liqSupAddress; liquidSupply = _liqSupAmount; _mint(_liqSupAddress, _liqSupAmount); } function premintLocked(address[6] memory _lockSupAddresses, uint256[10] memory _unlockDates) public { require(hasRole(DEPLOYER_ROLE, _msgSender()), "Must have deployer role"); require(!lockedSupplyPreminted, "cannot premint locked twice"); _premintLockedSupply(_lockSupAddresses, _unlockDates); } function _premintLockedSupply(address[6] memory _lockSupAddresses, uint256[10] memory _unlockDates) internal { lockedSupplyAddresses.firstAddress = _lockSupAddresses[0]; lockedSupplyAddresses.secondAddress = _lockSupAddresses[1]; lockedSupplyAddresses.thirdAddress = _lockSupAddresses[2]; lockedSupplyAddresses.fourthAddress = _lockSupAddresses[3]; lockedSupplyAddresses.fifthAddress = _lockSupAddresses[4]; lockedSupplyAddresses.sixthAddress = _lockSupAddresses[4]; for (uint256 i = 0; i < 10; i++) { uint256 startDate = SafeMath.add(block.timestamp, SafeMath.add(i, 5)); uint256 endFreezeDate = _unlockDates[i]; uint256 lockSeconds = SafeMath.sub(endFreezeDate, startDate); uint256 lockDays = SafeMath.div(lockSeconds, SECONDS_IN_DAY); uint256 firstSecondAmount = SafeMath.mul(180000, 10 ** uint256(decimals())); uint256 thirdAmount = SafeMath.mul(120000, 10 ** uint256(decimals())); uint256 fourthAmount = SafeMath.mul(90000, 10 ** uint256(decimals())); uint256 fifthSixthAmount = SafeMath.mul(15000, 10 ** uint256(decimals())); mintAndFreeze(lockedSupplyAddresses.firstAddress, startDate, lockDays, firstSecondAmount); mintAndFreeze(lockedSupplyAddresses.secondAddress, startDate, lockDays, firstSecondAmount); mintAndFreeze(lockedSupplyAddresses.thirdAddress, startDate, lockDays, thirdAmount); mintAndFreeze(lockedSupplyAddresses.fourthAddress, startDate, lockDays, fourthAmount); mintAndFreeze(lockedSupplyAddresses.fifthAddress, startDate, lockDays, fifthSixthAmount); mintAndFreeze(lockedSupplyAddresses.sixthAddress, startDate, lockDays, fifthSixthAmount); } lockedSupplyPreminted = true; } function _preprocessMint(address _account, uint256 _hexAmount) internal { uint256 currentRoundHxyAmount = SafeMath.div(_hexAmount, currentHxyRoundRate); if (currentRoundHxyAmount < getRemainingHxyInRound()) { uint256 hxyAmount = currentRoundHxyAmount; _mint(_account, hxyAmount); totalMinted = SafeMath.add(totalMinted, hxyAmount); totalCirculating = SafeMath.add(totalCirculating, hxyAmount); } else if (currentRoundHxyAmount == getRemainingHxyInRound()) { uint256 hxyAmount = currentRoundHxyAmount; _mint(_account, hxyAmount); _incrementHxyRateRound(); totalMinted = SafeMath.add(totalMinted, hxyAmount); totalCirculating = SafeMath.add(totalCirculating, hxyAmount); } else { uint256 hxyAmount; uint256 hexPaymentAmount; while (hexPaymentAmount < _hexAmount) { uint256 hxyRoundTotal = SafeMath.mul(_toDecimals(hxyRoundMintAmount[currentHxyRound]), hxyMintedMultiplier); uint256 hxyInCurrentRoundMax = SafeMath.sub(hxyRoundTotal, totalMinted); uint256 hexInCurrentRoundMax = SafeMath.mul(hxyInCurrentRoundMax, currentHxyRoundRate); uint256 hexInCurrentRound; uint256 hxyInCurrentRound; if (SafeMath.sub(_hexAmount, hexPaymentAmount) < hexInCurrentRoundMax) { hexInCurrentRound = SafeMath.sub(_hexAmount, hexPaymentAmount); hxyInCurrentRound = SafeMath.div(hexInCurrentRound, currentHxyRoundRate); } else { hexInCurrentRound = hexInCurrentRoundMax; hxyInCurrentRound = hxyInCurrentRoundMax; _incrementHxyRateRound(); } hxyAmount = SafeMath.add(hxyAmount, hxyInCurrentRound); hexPaymentAmount = SafeMath.add(hexPaymentAmount, hexInCurrentRound); totalMinted = SafeMath.add(totalMinted, hxyInCurrentRound); totalCirculating = SafeMath.add(totalCirculating, hxyAmount); } _mint(_account, hxyAmount); } } function _preprocessMintWithFreeze(address _account, uint256 _hexAmount, uint256 _freezeDays) internal { uint256 currentRoundHxyAmount = SafeMath.div(_hexAmount, currentHxyRoundRate); if (currentRoundHxyAmount < getRemainingHxyInRound()) { uint256 hxyAmount = currentRoundHxyAmount; totalMinted = SafeMath.add(totalMinted, hxyAmount); mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount); } else if (currentRoundHxyAmount == getRemainingHxyInRound()) { uint256 hxyAmount = currentRoundHxyAmount; mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount); totalMinted = SafeMath.add(totalMinted, hxyAmount); _incrementHxyRateRound(); } else { uint256 hxyAmount; uint256 hexPaymentAmount; while (hexPaymentAmount < _hexAmount) { uint256 hxyRoundTotal = SafeMath.mul(_toDecimals(hxyRoundMintAmount[currentHxyRound]), hxyMintedMultiplier); uint256 hxyInCurrentRoundMax = SafeMath.sub(hxyRoundTotal, totalMinted); uint256 hexInCurrentRoundMax = SafeMath.mul(hxyInCurrentRoundMax, currentHxyRoundRate); uint256 hexInCurrentRound; uint256 hxyInCurrentRound; if (SafeMath.sub(_hexAmount, hexPaymentAmount) < hexInCurrentRoundMax) { hexInCurrentRound = SafeMath.sub(_hexAmount, hexPaymentAmount); hxyInCurrentRound = SafeMath.div(hexInCurrentRound, currentHxyRoundRate); } else { hexInCurrentRound = hexInCurrentRoundMax; hxyInCurrentRound = hxyInCurrentRoundMax; _incrementHxyRateRound(); } hxyAmount = SafeMath.add(hxyAmount, hxyInCurrentRound); hexPaymentAmount = SafeMath.add(hexPaymentAmount, hexInCurrentRound); totalMinted = SafeMath.add(totalMinted, hxyInCurrentRound); } mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount); } } function _mintDirectly(address _account, uint256 _hxyAmount) internal { _mint(_account, _hxyAmount); } function _mintAndFreezeDirectly(address _account, uint256 _hxyAmount, uint256 _freezeDays) internal { mintAndFreeze(_account, block.timestamp, _freezeDays, _hxyAmount); } function _isLockedAddress() internal view returns (bool) { if (_msgSender() == lockedSupplyAddresses.firstAddress) { return true; } else if (_msgSender() == lockedSupplyAddresses.secondAddress) { return true; } else if (_msgSender() == lockedSupplyAddresses.thirdAddress) { return true; } else if (_msgSender() == lockedSupplyAddresses.fourthAddress) { return true; } else if (_msgSender() == lockedSupplyAddresses.fifthAddress) { return true; } else if (_msgSender() == lockedSupplyAddresses.sixthAddress) { return true; } else { return false; } } function _getTotalHxyInRound(uint256 _round) public view returns (uint256) { return SafeMath.mul(_toDecimals(hxyRoundMintAmount[_round]),hxyMintedMultiplier); } function _getRemainingHxyInRound(uint256 _round) public view returns (uint256) { return SafeMath.sub(SafeMath.mul(_toDecimals(hxyRoundMintAmount[_round]), hxyMintedMultiplier), totalMinted); } function _incrementHxyRateRound() internal { currentHxyRound = SafeMath.add(currentHxyRound, 1); currentHxyRoundRate = SafeMath.mul(hxyRoundBaseRate[currentHxyRound], baseHexToHxyRate); } function _toDecimals(uint256 amount) internal view returns (uint256) { return SafeMath.mul(amount, 10 ** uint256(decimals())); } }