Transactions
Token Transfers
Tokens
Internal Transactions
Coin Balance History
Logs
Code
Read Contract
Write Contract
Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB 0xe9e1340a2b31d5d2a2db28fb854a794e106b430a.
All metadata displayed below is from that contract. In order to verify current contract, click Verify & Publish button
Verify & Publish
All metadata displayed below is from that contract. In order to verify current contract, click Verify & Publish button
- Contract name:
- HEXTimeToken
- Optimization enabled
- true
- Compiler version
- v0.8.24+commit.e11b9ed9
- Optimization runs
- 200
- Verified at
- 2024-10-08T03:41:38.466827Z
contracts/HEXTimeToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
// import "hardhat/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import { Actuator } from "./Actuator.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
contract HEXTimeToken is ERC20 {
uint256 public constant CREATION_FEE_RATE = 100; // 100 basis points tax, i.e., 1%
uint256 private constant ACC_HTT_PRECISION = 1e22; // Original was 1e12. However, HEX has 8 decimals. So 1e22 is used to preserve the relative precision.
// Info of each user.
struct UserInfo {
uint256 amount; // How many ACTR tokens the user has deposited.
uint256 rewardDebt;
uint256 capitalAdded; // deposit start time
}
Actuator public actr;
uint256 public totalDeposits;
uint256 public accHttPerShare;
uint16 public maturity;
address public httManager;
// Info of each user that deposits ACTR.
mapping (address => UserInfo) public userInfo;
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event CollectFees(address indexed user, uint256 amount);
constructor(
uint16 _maturity,
address actuatorAddress
)
ERC20(string.concat("HEX Time Token ", Strings.toString(_maturity)), string.concat("HTT-", Strings.toString(_maturity)))
{
actr = Actuator(actuatorAddress);
maturity = _maturity;
httManager = msg.sender;
}
modifier onlyHttManager() {
require(msg.sender == httManager, "A036");
_;
}
modifier onlyActuator() {
require(msg.sender == address(actr), "A039");
_;
}
function decimals() public view virtual override returns (uint8) {
return 8;
}
/**
* @dev Priveleged function for the HTT Manager to mint HEX Time Tokens (HTT) and collect a fee/tax.
* @param to Recipient address.
* @param amount Amount of HTTs to transfer.
*/
function mint(address to, uint256 amount) external onlyHttManager {
if (totalDeposits == 0) {
_mint(to, amount);
return;
}
uint256 taxAmount = calculateTax(amount);
uint256 amountAfterTax = amount - taxAmount;
accHttPerShare = accHttPerShare + (taxAmount * ACC_HTT_PRECISION / totalDeposits);
_mint(address(this), taxAmount);
_mint(to, amountAfterTax);
}
/**
* @dev Priveleged function for the HTT Manager to burn HEX Time Tokens (HTT).
* @param from Address to burn from.
* @param amount Amount of HTTs to burn.
*/
function burn(address from, uint256 amount) external onlyHttManager {
_burn(from, amount);
}
/**
* @dev Calculates the tax for a given input amount.
* @param amount Amount of HTTs to apply tax.
*/
function calculateTax(uint256 amount) public pure returns (uint256) {
return amount * CREATION_FEE_RATE / 10000; // Assumes CREATION_FEE_RATE is in basis points
}
/**
* @dev Deposit ACTR to collect HEX Time Token (HTT) tax.
* @param account Address of the depositer.
* @param _amount Amount of ACTR to deposit.
*/
function deposit(address account, uint256 _amount) external onlyActuator returns (uint256) {
UserInfo storage user = userInfo[account];
totalDeposits += _amount;
uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;
user.amount = user.amount + _amount;
user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;
user.capitalAdded = block.timestamp;
if (pending > 0) {
safeHttTransfer(account, pending);
emit CollectFees(account, pending);
}
emit Deposit(account, _amount);
return user.amount;
}
/**
* @dev Withdraw ACTR from vault.
* @param account Address of the depositer.
* @param _amount Amount of ACTR to withdraw.
*/
function withdraw(address account, uint256 _amount) external onlyActuator returns (uint256, uint256) {
UserInfo storage user = userInfo[account];
require(user.amount >= _amount, "A037");
totalDeposits -= _amount;
uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;
user.amount = user.amount - _amount;
user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;
if (pending > 0) {
safeHttTransfer(account, pending);
emit CollectFees(account, pending);
}
emit Withdraw(account, _amount);
return (user.amount, user.capitalAdded);
}
/**
* @dev Collect accumulated HTT rewards from tax.
* @return Amount of HTTs collected.
*/
function collectFees() external returns (uint256) {
UserInfo storage user = userInfo[msg.sender];
require(user.amount > 0, "A026");
uint256 pending = (user.amount * accHttPerShare / ACC_HTT_PRECISION) - user.rewardDebt;
user.rewardDebt = user.amount * accHttPerShare / ACC_HTT_PRECISION;
if (pending > 0) {
safeHttTransfer(msg.sender, pending);
emit CollectFees(msg.sender, pending);
}
return pending;
}
/**
* @dev Safe HTT transfer function, just in case if rounding error causes pool to not have enough HTT.
* @param _to Recipient address.
* @param _amount Amount of HTT tokens to transfer.
*/
function safeHttTransfer(address _to, uint256 _amount) private {
uint256 httBal = balanceOf(address(this));
if (_amount > httBal) {
_transfer(address(this), _to, httBal);
} else {
_transfer(address(this), _to, _amount);
}
}
}
@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
@openzeppelin/contracts/utils/Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @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://consensys.net/diligence/blog/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.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
@openzeppelin/contracts/interfaces/draft-IERC6093.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
@openzeppelin/contracts/token/ERC20/ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @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}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead 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.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual 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 default value returned by this function, unless
* it's overridden.
*
* 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 virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
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}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This 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.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
@openzeppelin/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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 Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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 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.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
@openzeppelin/contracts/utils/Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
@openzeppelin/contracts/utils/math/Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// 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 (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
@openzeppelin/contracts/utils/math/SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
contracts/Actuator.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
// import "hardhat/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { HEXTimeToken } from "./HEXTimeToken.sol";
import { HEXTimeTokenManager } from "./HEXTimeTokenManager.sol";
contract Actuator is ERC20 {
uint256 private constant MIN_VAULT_TIME = 90 days;
HEXTimeTokenManager public _httm;
address public masterChef;
mapping(address => uint16[]) public depositedMaturities;
constructor(
address _httmAddress
)
ERC20('Actuator', 'ACTR')
{
_httm = HEXTimeTokenManager(_httmAddress);
masterChef = msg.sender;
}
modifier onlyMasterChef() {
require(msg.sender == masterChef, "A042");
_;
}
function mint(address to, uint256 amount) external onlyMasterChef {
_mint(to, amount);
}
/**
* @dev Retreives the number of vaults the user has deposited into.
* @return Number of vaults.
*/
function vaultCount(
address user
)
external
view
returns (uint256)
{
return depositedMaturities[user].length;
}
/**
* @dev Deposit ACTR into vault to collect the given HEX Time Token (HTT) tax.
* @param maturity HTT maturity day to stake against.
* @param amount Amount of ACTR to deposit.
*/
function deposit(uint16 maturity, uint256 amount) external {
require(amount > 0, "A040");
(, address tokenAddress) = _httm.maturityToInfo(maturity);
require(tokenAddress != address(0), "A033");
uint16[] storage maturities = depositedMaturities[msg.sender];
maturities.push(maturity);
uint256 newAmount = HEXTimeToken(tokenAddress).deposit(msg.sender, amount);
require(newAmount == amount, "A034");
// bypass allowance
_transfer(msg.sender, address(this), amount);
}
/**
* @dev Increase deposited ACTR.
* @param index Index of the user's vaults.
* @param amount Amount of ACTR to deposit.
*/
function increaseDeposit(uint256 index, uint256 amount) external {
require(amount > 0, "A040");
uint16 maturity = depositedMaturities[msg.sender][index];
(, address tokenAddress) = _httm.maturityToInfo(uint16(maturity));
uint256 newAmount = HEXTimeToken(tokenAddress).deposit(msg.sender, amount);
require(newAmount > amount, "A035");
// bypass allowance
_transfer(msg.sender, address(this), amount);
}
/**
* @dev Withdraw ACTR from vault.
* @param index Index of the user's vaults.
* @param amount Amount of ACTR to withdraw.
*/
function withdraw(uint256 index, uint256 amount) external {
require(amount > 0, "A041");
uint16 maturity = depositedMaturities[msg.sender][index];
(, address tokenAddress) = _httm.maturityToInfo(uint16(maturity));
(uint256 newAmount, uint256 capitalAdded) = HEXTimeToken(tokenAddress).withdraw(msg.sender, amount);
if (newAmount == 0) {
_pruneDepositedMaturities(msg.sender, index);
}
uint256 servedTime = block.timestamp - capitalAdded;
if (servedTime < MIN_VAULT_TIME) {
// Penalty for early withdrawal
uint256 remainder = amount * servedTime / MIN_VAULT_TIME;
_burn(address(this), amount - remainder);
_transfer(address(this), msg.sender, remainder);
} else {
_transfer(address(this), msg.sender, amount);
}
}
/**
* @dev Removes a vault from the user's individual vault list.
* @param account The relevant user.
* @param index The index of the vault to remove.
*/
function _pruneDepositedMaturities(
address account,
uint256 index
)
private
{
uint16[] storage list = depositedMaturities[account];
uint256 lastIndex = list.length - 1;
if (index != lastIndex) {
list[index] = list[lastIndex];
}
list.pop();
}
}
contracts/HEXTimeTokenManager.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
// import "hardhat/console.sol";
import { HEXTimeToken } from "./HEXTimeToken.sol";
import { IHEX } from "./interfaces/HEX.sol";
import { HEXStake } from "./declarations/Types.sol";
import { IHEXStakeInstance } from "./interfaces/HEXStakeInstance.sol";
import { IHEXStakeInstanceManager } from "./interfaces/HEXStakeInstanceManager.sol";
import { IHedron } from "./interfaces/Hedron.sol";
import { MasterChef } from "./MasterChef.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Actuator } from "./Actuator.sol";
contract HEXTimeTokenManager {
uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
uint256 private constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7;
uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
uint256 private constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7;
uint256 private constant MAX_REDEMPTION_DEFERMENT = LATE_PENALTY_SCALE_DAYS - (LATE_PENALTY_SCALE_DAYS / 10);
uint256 private constant PAYOUT_RESOLUTION = 1e18;
uint256 private constant RESERVE_FACTOR_SCALE = 1e5;
uint256 private constant BASE_RESERVE_FACTOR = 1e6;
uint256 private constant PAYOUT_START_DAY = 800;
uint256 private constant SUBSIDY_GRACE_DAYS = 3;
uint256 private constant HEX_LAUNCH = 1575331200;
address private constant HEX_ADDRESS = 0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39;
address private constant HSIM_ADDRESS = 0x8BD3d1472A656e312E94fB1BbdD599B8C51D18e3;
address private constant HEDRON_ADDRESS = 0x3819f64f282bf135d62168C1e513280dAF905e06;
uint256 private constant EARLY_PENALTY_MIN_DAYS = 90;
struct Collateral {
uint72 amount;
uint16 maturity;
address owner;
}
struct MaturityInfo {
uint72 hexBalance;
address tokenAddress;
}
struct MultiEndStakeParams {
address hsiAddress;
uint256 hsiIndex;
uint256 collateralIndex;
uint256 hedronHsiIndex;
}
address public actuatorAddress;
address public masterChefAddress;
IHEX private _hx;
IHEXStakeInstanceManager private _hsim;
IHedron private _hedron;
uint72[] public payouts;
mapping(address => address[]) public hsiLists;
mapping(uint16 => MaturityInfo) public maturityToInfo;
mapping(address => Collateral) public hsiToCollateral;
mapping(uint16 => address[]) public maturityToCollateralizedHsiList;
event DailyDataUpdate(address indexed updaterAddr, uint16 beforeDay, bool isAuto);
event CreateHEXTimeToken(uint16 indexed maturity);
event MintHEXTimeTokens(address indexed user, address indexed hsiAddress, uint16 indexed maturity, uint256 amount);
event RetireHEXTimeTokens(address indexed user, address indexed hsiAddress, uint16 indexed maturity, uint256 amount);
event RedeemHEXTimeTokens(address indexed user, uint16 indexed maturity, uint256 amount);
event DelegateHSI(address indexed user, uint256 indexed tokenId, address indexed hsiAddress);
event RevokeHSI(address indexed user, uint256 indexed tokenId, address indexed hsiAddress);
event StartStake(address indexed user, address indexed hsiAddress);
event EndStake(address indexed user, address indexed hsiAddress);
event EndCollateralizedStake(address indexed user, address indexed hsiAddress);
constructor(
address teamAddress,
address factoryAddress,
uint72[] memory initialPayouts,
uint256 farmStartTime,
uint256[3] memory _farmSupplySchedule,
uint256[14] memory _poolPointSchedule
) {
_hx = IHEX(HEX_ADDRESS);
_hsim = IHEXStakeInstanceManager(HSIM_ADDRESS);
_hedron = IHedron(HEDRON_ADDRESS);
MasterChef masterChef = new MasterChef(teamAddress, factoryAddress, farmStartTime, _farmSupplySchedule, _poolPointSchedule);
actuatorAddress = address(masterChef.actr());
masterChefAddress = address(masterChef);
payouts = initialPayouts;
}
/**
* @dev Calculates the current HEX day.
* @return Number representing the current HEX day.
*/
function _currentDay()
private
view
returns (uint256)
{
return (block.timestamp - HEX_LAUNCH) / 1 days;
}
/**
* @dev Update daily payout data.
*/
function updateDailyData(uint256 beforeDay) external {
_updateDailyData(beforeDay, false);
}
/**
* @dev Update daily payout data.
*/
function _updateDailyDataAuto(uint256 beforeDay) private {
_updateDailyData(beforeDay, true);
}
/**
* @dev Update daily payout data.
*/
function _updateDailyData(uint256 beforeDay, bool isAuto) private {
uint256 day = PAYOUT_START_DAY + payouts.length;
if (day >= beforeDay) return;
_hx.dailyDataUpdate(beforeDay);
uint256 dayPayoutPerTShare;
uint256 dayPayoutTotal;
uint256 dayStakeSharesTotal;
uint72 lastPayout = payouts.length > 0 ? payouts[payouts.length - 1] : 0;
while (day < beforeDay) {
(dayPayoutTotal, dayStakeSharesTotal,) = _hx.dailyData(day);
if (dayStakeSharesTotal > 0) {
dayPayoutPerTShare = dayPayoutTotal * PAYOUT_RESOLUTION / dayStakeSharesTotal;
} else {
dayPayoutPerTShare = 0;
}
lastPayout = uint72(dayPayoutPerTShare + lastPayout);
payouts.push(lastPayout);
day++;
}
emit DailyDataUpdate(msg.sender, uint16(beforeDay), isAuto);
}
/**
* @dev Get HEX Time Token or create token if it doesn't already exist.
* @param maturity The HEX day the token is redeemable for HEX.
* @return Address of the HEX Time Token.
*/
function getOrCreateHEXTimeToken(uint16 maturity) public returns (address) {
if (maturityToInfo[maturity].tokenAddress == address(0)) {
ERC20 newToken = new HEXTimeToken(maturity + 1, actuatorAddress);
maturityToInfo[maturity].tokenAddress = address(newToken);
emit CreateHEXTimeToken(maturity);
}
return maturityToInfo[maturity].tokenAddress;
}
/**
* @dev Creates a new HEX stake instance (HSI) and delegates control to the HEXTimeTokenManager.
* @param amount Number of HEX ERC20 tokens to be staked.
* @param length Number of days the HEX ERC20 tokens will be staked.
* @return Address of the newly created HSI contract.
*/
function hexStakeStart(uint256 amount, uint256 length) external returns (address) {
require(_hx.transferFrom(msg.sender, address(this), amount), "A016");
_hx.increaseAllowance(HSIM_ADDRESS, amount);
address hsiAddress = _hsim.hexStakeStart(amount, length);
hsiLists[msg.sender].push(hsiAddress);
emit StartStake(msg.sender, hsiAddress);
return hsiAddress;
}
/**
* @dev Transfers control of the tokenized HSI to the HEXTimeTokenManager and detokenizes.
* @param tokenId ID of the HSI ERC721 token to be converted.
* @return Address of the detokenized HSI contract.
*/
function delegateHSI(uint256 tokenId) external returns (address) {
_hsim.transferFrom(msg.sender, address(this), tokenId);
address hsiAddress = _hsim.hexStakeDetokenize(tokenId);
hsiLists[msg.sender].push(hsiAddress);
emit DelegateHSI(msg.sender, tokenId, hsiAddress);
return hsiAddress;
}
/**
* @dev Mints HEX Maturity tokens (HTTs) against an HSI's underlying HEX.
* When maturity day is after end stake day, extractable HTT quantity is lowered by the maximum possible end stake HEX penalty.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param amount Quanity of HTTs to mint.
* @param maturity The Hex day which the HTTs mature.
* @return Address of the HTT token.
*/
function mintHEXTimeTokens (
uint256 hsiIndex,
uint256 amount,
uint256 maturity
)
external
returns (address)
{
require(amount > 0, "A023");
require(hsiIndex < hsiLists[msg.sender].length, "A012");
address hsiAddress = hsiLists[msg.sender][hsiIndex];
Collateral storage collateral = hsiToCollateral[hsiAddress];
require(collateral.maturity == 0 || collateral.maturity == maturity, "A013");
_updateDailyDataAuto(_currentDay());
uint256 extractableAmount = getExtractableAmount(hsiAddress, maturity);
require(collateral.amount + amount <= extractableAmount, "A002");
if (collateral.amount == 0) {
maturityToCollateralizedHsiList[uint16(maturity)].push(hsiAddress);
collateral.maturity = uint16(maturity);
collateral.owner = msg.sender;
}
collateral.amount += uint72(amount);
address tokenAddress = getOrCreateHEXTimeToken(uint16(maturity));
HEXTimeToken(tokenAddress).mint(msg.sender, amount);
emit MintHEXTimeTokens(msg.sender, hsiAddress, uint16(maturity), amount);
return tokenAddress;
}
/**
* @dev Mints Hedron ERC20 (HDRN) tokens to the sender using a HEX stake instance (HSI) backing.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
* @return Amount of HDRN ERC20 tokens minted.
*/
function mintInstanced(
uint256 hsiIndex,
uint256 hedronHsiIndex
)
external
returns (uint256)
{
require(hsiIndex < hsiLists[msg.sender].length, "A012");
address hsiAddress = hsiLists[msg.sender][hsiIndex];
uint256 amount = _hedron.mintInstanced(hedronHsiIndex, hsiAddress);
_hedron.transfer(msg.sender, amount);
return amount;
}
/**
* @dev Burns Hex Maturity Tokens (HTTs) previously minted against a HSI
* and returns control of the collateralized underlying HEX back to the staker.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param collateralIndex Index of the HSI address in the collateralized HSI list.
* @param amount Number of HTTs to retire.
*/
function retireHEXTimeTokens(
uint256 hsiIndex,
uint256 collateralIndex,
uint256 amount
)
external
{
require(hsiIndex < hsiLists[msg.sender].length, "A012");
require(amount > 0, "A024");
address hsiAddress = hsiLists[msg.sender][hsiIndex];
Collateral storage collateral = hsiToCollateral[hsiAddress];
uint16 maturity = collateral.maturity; // stash before prune
require(hsiAddress == maturityToCollateralizedHsiList[maturity][collateralIndex], "A006");
require(_currentDay() < maturity, "A003");
require(amount <= collateral.amount, "A001");
if (collateral.amount - amount == 0) {
_pruneCollateralizedHSI(maturity, collateralIndex);
delete hsiToCollateral[hsiAddress]; // order matters
} else {
collateral.amount -= uint72(amount);
}
HEXTimeToken(maturityToInfo[maturity].tokenAddress).burn(msg.sender, amount);
emit RetireHEXTimeTokens(msg.sender, hsiAddress, maturity, amount);
}
/**
* @dev Tokenizes the HSI and transfers control of the tokenized HSI to caller.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
* @return Token ID of the HSI ERC721 token.
*/
function revokeHSIDelegation(uint256 hsiIndex, uint256 hedronHsiIndex) external returns (uint256) {
address[] storage hsiList = hsiLists[msg.sender];
require(hsiIndex < hsiList.length, "A012");
address hsiAddress = hsiList[hsiIndex];
require(hsiToCollateral[hsiAddress].amount == 0, "A000");
_pruneHSI(hsiList, hsiIndex);
uint256 tokenId = _hsim.hexStakeTokenize(hedronHsiIndex, hsiAddress);
_hsim.transferFrom(address(this), msg.sender, tokenId);
emit RevokeHSI(msg.sender, tokenId, hsiAddress);
return tokenId;
}
/**
* @dev Unlocks the stake.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
*/
function endHEXStake(
uint256 hsiIndex,
uint256 hedronHsiIndex
)
external
returns (uint256)
{
address[] storage hsiList = hsiLists[msg.sender];
require(hsiIndex < hsiList.length, "A012");
address hsiAddress = hsiList[hsiIndex];
require(hsiToCollateral[hsiAddress].amount == 0, "A019");
_pruneHSI(hsiList, hsiIndex);
uint256 hsiBalance = _hsim.hexStakeEnd(hedronHsiIndex, hsiAddress);
if (hsiBalance > 0) require(_hx.transfer(msg.sender, hsiBalance), "A010");
emit EndStake(msg.sender, hsiAddress);
return hsiBalance;
}
/**
* @dev Allows the stake owner to unlock the collateralized HSI once fully served,
* otherwise anyone can unlock once minted HTTs are redeemable.
* @param hsiAddress Address of the HSI.
* @param hsiIndex Index of the HSI address in the caller's HSI list.
* @param collateralIndex Index of the HSI address in the collateralized HSI list.
* @param hedronHsiIndex Index of the HSI address stored in Hedron's HSI list.
*/
function endCollateralizedHEXStake(
address hsiAddress,
uint256 hsiIndex,
uint256 collateralIndex,
uint256 hedronHsiIndex
)
public
{
Collateral memory collateral = hsiToCollateral[hsiAddress];
uint256 collateralAmount = collateral.amount;
uint16 maturity = collateral.maturity;
address owner = collateral.owner;
require(collateralAmount > 0, "A018");
require(hsiAddress == hsiLists[owner][hsiIndex], "A004");
require(hsiAddress == maturityToCollateralizedHsiList[maturity][collateralIndex], "A006");
uint256 currentDay = _currentDay();
(,, uint256 stakeShares, uint16 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
if (currentDay < maturity) {
// stake owner can end stake once fully served
require(owner == msg.sender && currentDay >= lockedDay + stakedDays, "A022");
}
_pruneHSI(hsiLists[owner], hsiIndex);
_pruneCollateralizedHSI(maturity, collateralIndex);
delete hsiToCollateral[hsiAddress];
_updateDailyDataAuto(currentDay);
uint256 hsiBalance = _hsim.hexStakeEnd(hedronHsiIndex, hsiAddress);
// End staker gets 1st priority of unlocked HEX (in event of late end stake)
if (hsiBalance > 0) {
uint256 effectiveStakedDays = maturity - lockedDay < stakedDays? maturity - lockedDay: stakedDays;
uint256 endStakeSubsidy = calcEndStakeSubsidy(lockedDay, effectiveStakedDays, maturity, currentDay, stakeShares);
if (endStakeSubsidy > 0) {
endStakeSubsidy = endStakeSubsidy < hsiBalance? endStakeSubsidy: hsiBalance;
hsiBalance -= endStakeSubsidy;
require(_hx.transfer(msg.sender, endStakeSubsidy), "A010");
}
}
// HTT holders get 2nd priority of unlocked HEX (in event of late end stake)
if (hsiBalance > 0) {
collateralAmount = collateralAmount < hsiBalance? collateralAmount: hsiBalance;
maturityToInfo[uint16(maturity)].hexBalance += uint72(collateralAmount);
hsiBalance -= collateralAmount;
}
// Stake creator gets last priority of unlocked HEX (in event of late end stake)
if (hsiBalance > 0) require(_hx.transfer(owner, hsiBalance), "A010");
emit EndCollateralizedStake(msg.sender, hsiAddress);
}
/**
* @dev Allows any address to unlock a fully matured collateralized stake and subsequently redeem HTTs.
* @param maturity The maturity of the HTT to redeem.
* @param amount The amount of HTTs to redeem.
*/
function endHEXStakesAndRedeem(
uint256 maturity,
uint256 amount,
MultiEndStakeParams[] memory data
)
external
{
endHEXStakes(data);
redeemHEXTimeTokens(maturity, amount);
}
/**
* @dev Allows any address to unlock fully matured collateralized stakes and subsequently redeem HTTs.
* @param data The relevant stake data needed to unlock.
*/
function endHEXStakes(MultiEndStakeParams[] memory data) public {
for (uint256 i = 0; i < data.length; i++) {
endCollateralizedHEXStake(data[i].hsiAddress, data[i].hsiIndex, data[i].collateralIndex, data[i].hedronHsiIndex);
}
}
/**
* @dev Redeem HEX Maturity tokens (HTT) for HEX.
* @param maturity Maturity day of the HTT.
* @param amount Number of HTTs to redeem.
*/
function redeemHEXTimeTokens(uint256 maturity, uint256 amount) public {
MaturityInfo storage info = maturityToInfo[uint16(maturity)];
HEXTimeToken token = HEXTimeToken(info.tokenAddress);
require(_currentDay() >= maturity, "A009");
require(amount <= token.balanceOf(msg.sender), "A008");
require(amount <= info.hexBalance, "A007");
info.hexBalance -= uint72(amount);
token.burn(msg.sender, amount);
require(_hx.transfer(msg.sender, amount), "A010");
emit RedeemHEXTimeTokens(msg.sender, uint16(maturity), amount);
}
/**
* @dev Removes a HEX stake instance (HSI) address from an individual owner's HSI List.
* @param hsiList A mapped list of HSI contract addresses.
* @param hsiIndex The index of the HSI address which will be removed.
*/
function _pruneHSI(address[] storage hsiList, uint256 hsiIndex) private {
uint256 lastIndex = hsiList.length - 1;
if (hsiIndex != lastIndex) {
hsiList[hsiIndex] = hsiList[lastIndex];
}
hsiList.pop();
}
/**
* @dev Removes a HEX stake instance (HSI) address from the collateralized HSI List.
* @param maturity A mapped list of HSI contract addresses.
* @param collateralIndex Index of the collateralized HSI address which will be removed.
*/
function _pruneCollateralizedHSI(uint16 maturity, uint256 collateralIndex) private {
address[] storage collateralizedHsiList = maturityToCollateralizedHsiList[maturity];
uint256 lastIndex = collateralizedHsiList.length - 1;
if (collateralIndex != lastIndex) {
collateralizedHsiList[collateralIndex] = collateralizedHsiList[lastIndex];
}
collateralizedHsiList.pop();
}
/**
* @dev Calculates the total quantity of extractable HEX Maturity Tokens (HTT) from a stake.
* @param hsiAddress Address of the HSI.
* @param maturity maturity of the HTT to extract.
* @return Total quantity of extractable HTT.
*/
function getExtractableAmount(
address hsiAddress,
uint256 maturity
)
public
view
returns (uint256)
{
(,uint256 stakeValue, uint72 stakeShares, uint256 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
uint256 endStakeDay = lockedDay + stakedDays;
uint256 currentDay = _currentDay();
if (maturity < endStakeDay) {
require(currentDay < maturity, "A045");
uint256 penaltyDays = (stakedDays + 1) / 2;
require(penaltyDays >= EARLY_PENALTY_MIN_DAYS, "A046");
uint256 penaltyEndDay = lockedDay + penaltyDays;
uint256 effectiveStakedDays = maturity - lockedDay;
uint256 reserveDay = getReserveDay(lockedDay, effectiveStakedDays, maturity);
require(reserveDay >= penaltyEndDay, "A047");
stakeValue += calculateRewards(penaltyEndDay, currentDay < reserveDay? currentDay: reserveDay, stakeShares);
} else {
require(currentDay < endStakeDay, "A014");
require(maturity - endStakeDay < MAX_REDEMPTION_DEFERMENT, "A017");
uint256 reserveDay = getReserveDay(lockedDay, stakedDays, maturity);
// only calculate rewards up to the earlier of the current day or the reserve day
stakeValue += calculateRewards(lockedDay, currentDay < reserveDay? currentDay: reserveDay, stakeShares);
if (endStakeDay < maturity) {
// assume worst case scenario and subtract maximal possible late penalty from extractable amount
stakeValue -= calcLatePenalty(lockedDay, stakedDays, maturity + LATE_PENALTY_GRACE_DAYS, stakeValue);
}
}
return stakeValue;
}
/**
* @dev Finds the index of the HSI address in Hedron's HSI list.
* @param hsiAddress Address of the HSI.
* @return Index of the HSI address in Hedron's HSI list.
*/
function findHedronHSIIndex(address hsiAddress) external view returns (int) {
uint256 count = _hsim.hsiCount(address(this));
for (uint256 i = 0; i < count; i++) {
address addr = _hsim.hsiLists(address(this), i);
if (addr == hsiAddress) return int(i);
}
return -1;
}
/**
* @dev Finds the index of the HSI address in the individual owner's HSI list.
* This function only works on collateralized HSIs.
* @param hsiAddress Address of the HSI.
* @return Index of the HSI address in the HSI list.
*/
function findHSIIndex(address hsiAddress) external view returns (int) {
address owner = hsiToCollateral[hsiAddress].owner;
for (uint256 i = 0; i < hsiLists[owner].length; i++) {
address addr = hsiLists[owner][i];
if (addr == hsiAddress) return int(i);
}
return -1;
}
/**
* @dev Finds all underlying HSI addresses backing HTTs for the given maturity.
* @param maturity maturity of the HTT
* @param start range start
* @param end range end (non-inclusive)
* @return list array of HSI addresses
*/
function hsiListRange(
uint256 maturity,
uint256 start,
uint256 end
)
external
view
returns (address[] memory list)
{
address[] memory hsiList = maturityToCollateralizedHsiList[uint16(maturity)];
end = end > hsiList.length? hsiList.length: end;
if (end - start == 0) return list;
list = new address[](end - start);
uint256 dst;
uint256 i = start;
do {
list[dst++] = hsiList[i];
} while (++i < end);
return list;
}
/**
* @dev Finds all underlying HSI data backing HTTs for the given maturity.
* @param maturity Maturity of the HTT
* @param start Range start
* @param end Range end (non-inclusive)
* @return list Array of packed HSI/collateral data
*/
function hsiDataListRange(
uint256 maturity,
uint256 start,
uint256 end
)
external
view
returns (uint256[] memory list)
{
address[] memory hsiList = maturityToCollateralizedHsiList[uint16(maturity)];
end = end > hsiList.length? hsiList.length: end;
if (end - start == 0) return list;
list = new uint256[](end - start);
uint256 i = start;
uint256 dst;
uint256 v;
do {
address hsiAddress = hsiList[i];
(,, uint72 stakeShares, uint16 lockedDay, uint16 stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
Collateral memory collateral = hsiToCollateral[hsiAddress];
v = uint256(collateral.amount) << (72 * 2);
v |= uint256(stakeShares) << 72;
v |= uint256(lockedDay) << 16;
v |= uint256(stakedDays);
list[dst++] = v;
} while (++i < end);
return list;
}
/**
* @dev Finds all cumulative payouts within the range.
* @param beginDay First day of data range
* @param endDay Last day (non-inclusive) of data range
* @return list Array of cumulative payouts
*/
function dailyDataRange(uint256 beginDay, uint256 endDay) external view returns (uint256[] memory list) {
list = new uint256[](endDay - beginDay);
uint256 src = beginDay;
uint256 dst;
do {
list[dst++] = payouts[src - PAYOUT_START_DAY];
} while (++src < endDay);
return list;
}
/**
* @dev function to pull stake and collateral data.
* @param owner Address used to retrieve the HSI list.
* @param hsiIndex Index of the HSI address in the owner's HSI list.
*/
function shareLists(
address owner,
uint256 hsiIndex
)
external
view
returns (
uint40 stakeId,
uint72 stakedHearts,
uint72 stakeShares,
uint16 lockedDay,
uint16 stakedDays,
uint256 collateralAmount,
uint256 maturity,
address hsiAddress
)
{
hsiAddress = hsiLists[owner][hsiIndex];
(stakeId, stakedHearts, stakeShares, lockedDay, stakedDays,,) = _hx.stakeLists(hsiAddress, 0);
collateralAmount = hsiToCollateral[hsiAddress].amount;
maturity = hsiToCollateral[hsiAddress].maturity;
return (
stakeId,
stakedHearts,
stakeShares,
lockedDay,
stakedDays,
collateralAmount,
maturity,
hsiAddress
);
}
/**
* @dev Retreives the number of HSI elements for the given user's HSI list.
* @param user Address used to retrieve the HSI list.
* @return Number of HSI elements found in the user's HSI list.
*/
function hsiCount(
address user
)
public
view
returns (uint256)
{
return hsiLists[user].length;
}
/**
* @dev Wrapper for hsiCount allowing for HEX-based apps to fetch stake data.
* @param user Address used to retrieve the HSI list.
* @return Number of HSI elements found in the user's HSI list.
*/
function stakeCount(
address user
)
external
view
returns (uint256)
{
return hsiCount(user);
}
/**
* @dev Wrapper for hsiLists allowing for HEX-based apps to fetch stake data.
* @param user Address used to retrieve the HSI list.
* @param hsiIndex Index of the HSI address in the user's HSI list.
* @return HEX stake data.
*/
function stakeLists(
address user,
uint256 hsiIndex
)
external
view
returns (HEXStake memory)
{
address[] storage hsiList = hsiLists[user];
IHEXStakeInstance hsi = IHEXStakeInstance(hsiList[hsiIndex]);
return hsi.stakeDataFetch();
}
/**
* @dev Calculates accrued HEX rewards for a given range and share amount.
* @param beginDay begin day (inclusive).
* @param endDay end day (exclusive).
* @param stakeShares Number of shares.
* @return Rewards amount.
*/
function calculateRewards(
uint256 beginDay,
uint256 endDay,
uint256 stakeShares
)
public
view
returns (uint256)
{
if (beginDay >= endDay) return 0;
uint256 start = payouts[beginDay - 1 - PAYOUT_START_DAY];
uint256 end = payouts[endDay - 1 - PAYOUT_START_DAY];
uint256 rewards = (end - start) * stakeShares / PAYOUT_RESOLUTION;
// hex contract has less precision for rewards and results up to 1 heart of precision per day loss when calculating rewards
// thus we assume worst case scenario and subtract the maximal possible precision loss from the rewards (i.e. 1 heart per day)
uint256 precisionLoss = endDay - beginDay;
return precisionLoss < rewards? rewards - precisionLoss: 0;
}
/**
* @dev Calculates the subsidy for unlocking a stake.
* @param lockedDay begin day (inclusive)
* @param stakedDays Number of days staked
* @param maturity Maturity day of the HTTs minted against the stake
* @param unlockedDay Day the stake is unlocked
* @param stakeShares Number of shares
* @return Subsidy amount.
*/
function calcEndStakeSubsidy(
uint256 lockedDay,
uint256 stakedDays,
uint256 maturity,
uint256 unlockedDay,
uint256 stakeShares
)
public
view
returns (uint256)
{
if (unlockedDay <= maturity + SUBSIDY_GRACE_DAYS) return 0;
uint256 endDay = lockedDay + stakedDays;
uint256 reserveDay = getReserveDay(lockedDay, stakedDays, maturity);
uint256 reserves = calculateRewards(reserveDay, endDay, stakeShares);
uint256 maxSubsidy = reserves - calcLatePenalty(lockedDay, stakedDays, unlockedDay, reserves);
uint256 daysLate = unlockedDay - maturity - SUBSIDY_GRACE_DAYS;
uint256 factor = daysLate < 10? daysLate: 10;
return maxSubsidy * factor / 10;
}
/**
* @dev Calculates the first HEX day to begin reserving stake rewards in the event of an unlock subsidy.
* @param lockedDay begin day (inclusive)
* @param stakedDays Number of days staked
* @param maturity Maturity day of the HTTs minted against the stake
* @return Reserve day
*/
function getReserveDay(
uint256 lockedDay,
uint256 stakedDays,
uint256 maturity
)
public
pure
returns (uint256)
{
uint256 endDay = lockedDay + stakedDays;
uint256 factor = BASE_RESERVE_FACTOR; // reserveDays defaults to 10% of stakedDays
if (maturity > endDay) {
// scale up reserveDays as potential late penalty increases
uint256 unpenalizedDays = LATE_PENALTY_SCALE_DAYS - (maturity - endDay);
factor = factor * LATE_PENALTY_SCALE_DAYS / unpenalizedDays;
}
uint256 denominator = 100 * RESERVE_FACTOR_SCALE;
uint256 reserveDays = (stakedDays * factor + denominator - 1) / denominator;
return endDay - reserveDays;
}
/**
* @dev Calculates the late end stake penalty enforced by the HEX protocol late penalty calculation.
* @param lockedDay begin day (inclusive)
* @param stakedDays Number of days staked
* @param stakeValue The value of the stake in HEX
* @return Late penalty
*/
function calcLatePenalty(
uint256 lockedDay,
uint256 stakedDays,
uint256 unlockedDay,
uint256 stakeValue
)
public
pure
returns (uint256)
{
uint256 maxUnlockedDay = lockedDay + stakedDays + LATE_PENALTY_GRACE_DAYS;
if (unlockedDay <= maxUnlockedDay) return 0;
return stakeValue * (unlockedDay - maxUnlockedDay) / LATE_PENALTY_SCALE_DAYS;
}
/**
* @dev Calls the HEX function "stakeGoodAccounting" against the HEX stake held within the HSI.
*/
function stakeGoodAccounting(address hsiAddress)
external
{
IHEXStakeInstance hsi = IHEXStakeInstance(hsiAddress);
hsi.goodAccounting();
}
}
contracts/MasterChef.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
// import "hardhat/console.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import { Actuator } from "./Actuator.sol";
import { HEXTimeTokenManager } from "./HEXTimeTokenManager.sol";
import { IPulseXFactory } from "./interfaces/PulseXFactory.sol";
// Much of the code in this contract has been copied or adapted from SushiSwap's MasterChef contract.
contract MasterChef {
using SafeERC20 for IERC20;
uint256 private constant ACC_ACTR_PRECISION = 1e24;
address private constant HEX_ADDRESS = 0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39;
uint256 constant YEAR = 365 days;
uint256[3] public farmEmissionSchedule;
uint256[14] public poolPointSchedule;
uint256 private _lastMassUpdate;
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
//
// We do some fancy math here. Basically, any point in time, the amount of ACTR
// entitled to a user but is pending to be distributed is:
//
// pending reward = (user.amount * pool.accActrPerShare) - user.rewardDebt
//
// Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
// 1. The pool's `accActrPerShare` (and `lastRewardBlock`) gets updated.
// 2. User receives the pending reward sent to his/her address.
// 3. User's `amount` gets updated.
// 4. User's `rewardDebt` gets updated.
}
// Info of each pool.
struct PoolInfo {
IERC20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. ACTR to distribute per second.
uint256 lastRewardTime; // Last time that ACTR distribution occurs.
uint256 accActrPerShare; // Accumulated ACTR per share, times 1e24. See below.
}
Actuator public actr;
IPulseXFactory public factory;
HEXTimeTokenManager public _httManager;
uint256 public constant MaxAllocPoint = 4000;
// Info of each pool.
PoolInfo[] public poolInfo;
// Info of each user that stakes LP tokens.
mapping (uint256 => mapping (address => UserInfo)) public userInfo;
// Total allocation points. Must be the sum of all allocation points in all pools.
uint256 public totalAllocPoint = 0;
// The block time when ACTR mining starts.
uint256 public immutable startTime;
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event CollectEmissions(address indexed user, uint256 amount);
constructor(
address teamAddress,
address _factoryAddress,
uint256 _startTime,
uint256[3] memory _farmEmissionSchedule,
uint256[14] memory _poolPointSchedule
) {
_httManager = HEXTimeTokenManager(msg.sender);
actr = new Actuator(msg.sender);
startTime = _startTime;
farmEmissionSchedule = _farmEmissionSchedule;
poolPointSchedule = _poolPointSchedule;
factory = IPulseXFactory(_factoryAddress);
actr.mint(teamAddress, 250000000 * 1e18);
}
/**
* @dev Retreives the number of farm pools.
* @return Number of pools.
*/
function poolLength() external view returns (uint256) {
return poolInfo.length;
}
/**
* @dev Add a new lp to the pool.
* @param _allocPoint The allocation points to assign to the pool.
* @param _lpToken LP Address of the pool to add.
*/
function _add(uint256 _allocPoint, IERC20 _lpToken) private {
require(_allocPoint <= MaxAllocPoint, "A029");
uint256 lastRewardTime = block.timestamp > startTime ? block.timestamp : startTime;
totalAllocPoint = totalAllocPoint + _allocPoint;
poolInfo.push(PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardTime: lastRewardTime,
accActrPerShare: 0
}));
}
/**
* @dev Update the given pool's ACTR allocation point.
* @param _pid Internal ID of the pool to update.
* @param _allocPoint Updated allocation points to assign to the pool.
*/
function _set(uint256 _pid, uint256 _allocPoint) private {
if (poolInfo[_pid].allocPoint > _allocPoint) {
require(totalAllocPoint - (poolInfo[_pid].allocPoint - _allocPoint) > 0, "A032");
}
require(_allocPoint <= MaxAllocPoint, "A031");
totalAllocPoint = totalAllocPoint - poolInfo[_pid].allocPoint + _allocPoint;
poolInfo[_pid].allocPoint = _allocPoint;
}
/**
* @dev Public function to fetch the farm ACTR emission amount within 2 timestamp.
* @param _from Start date to calculate emissions from.
* @param _to End date to calculate emissions to.
* @return Total ACTR Emissions.
*/
function getFarmEmissions(uint256 _from, uint256 _to) external view returns (uint256) {
return _getFarmEmissions(_from, _to);
}
/**
* @dev Private function to fetch the farm ACTR emission amount within 2 timestamps.
* @param _from Start date to calculate emissions from.
* @param _to End date to calculate emissions to.
* @return Total ACTR Emissions.
*/
function _getFarmEmissions(uint256 _from, uint256 _to) private view returns (uint256) {
return _getEmissions(_from, _to, farmEmissionSchedule);
}
/**
* @dev Generic function to fetch the ACTR emission amount within 2 dates based on the provided emission schedule.
* @param _from Start date to calculate emissions from.
* @param _to End date to calculate emissions to.
* @param emissionSchedule Array of yearly ACTR emission amount.
* @return Total ACTR Emissions.
*
*/
function _getEmissions(uint256 _from, uint256 _to, uint256[3] memory emissionSchedule) private view returns (uint256) {
uint256 start = _from - startTime;
uint256 end = _to - startTime;
return _getEmissionsInTimeframe(start, end, emissionSchedule);
}
/**
* @dev Generic function to fetch the ACTR emission amount within 2 timestamps based on the provided emission schedule
* and assuming epoch is farm start.
* @param start Start time to calculate emissions from.
* @param end End time to calculate emissions to.
* @param emissionSchedule Array of yearly ACTR emission amount.
* @return Total ACTR Emissions.
*
*/
function _getEmissionsInTimeframe(uint256 start, uint256 end, uint256[3] memory emissionSchedule) private pure returns (uint256) {
uint256 mintAmount = 0;
for (uint256 year = 0; year < emissionSchedule.length; year++) {
uint256 yearStart = year * YEAR;
uint256 yearEnd = (year + 1) * YEAR;
// Check for timeframe overlap
if (end > yearStart && start < yearEnd) {
uint256 effectiveStart = start > yearStart ? start : yearStart;
uint256 effectiveEnd = end < yearEnd ? end : yearEnd;
uint256 elapsed = effectiveEnd - effectiveStart;
mintAmount += (emissionSchedule[year] * elapsed) / YEAR;
}
}
return mintAmount;
}
/**
* @dev View function to see pending ACTR on frontend.
* @return Pending ACTR amount.
*
*/
function pendingActr(uint256 _pid, address _user) external view returns (uint256) {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accActrPerShare = pool.accActrPerShare;
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (block.timestamp > pool.lastRewardTime && lpSupply != 0) {
uint256 totalRewards = _getFarmEmissions(pool.lastRewardTime, block.timestamp);
uint256 actrReward = totalRewards * pool.allocPoint / totalAllocPoint;
accActrPerShare = accActrPerShare + (actrReward * ACC_ACTR_PRECISION / lpSupply);
}
return (user.amount * accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;
}
/**
* @dev Update all pools to the latest predefined allocation points.
*
*/
function massUpdatePools() external {
for (uint256 pid = 0; pid < poolInfo.length; ++pid) {
updatePool(pid);
}
if (_lastMassUpdate == 0) {
address pairAddressHTT3000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(2999));
address pairAddressHTT5000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(4999));
address pairAddressHTT7000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(6999));
require(pairAddressHTT3000 != address(0), "A038");
require(pairAddressHTT5000 != address(0), "A038");
require(pairAddressHTT7000 != address(0), "A038");
_add(poolPointSchedule[0], IERC20(pairAddressHTT3000));
_add(poolPointSchedule[1], IERC20(pairAddressHTT5000));
_add(poolPointSchedule[2], IERC20(pairAddressHTT7000));
} else if (_lastMassUpdate < startTime + YEAR && block.timestamp >= startTime + YEAR) {
address pairAddressHTT4000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(3999));
address pairAddressHTT6000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(5999));
require(pairAddressHTT4000 != address(0), "A038");
require(pairAddressHTT6000 != address(0), "A038");
_set(0, poolPointSchedule[3]);
_add(poolPointSchedule[4], IERC20(pairAddressHTT4000));
_set(1, poolPointSchedule[5]);
_add(poolPointSchedule[6], IERC20(pairAddressHTT6000));
_set(2, poolPointSchedule[7]);
} else if (_lastMassUpdate < startTime + (YEAR * 2) && block.timestamp >= startTime + (YEAR * 2)) {
address pairAddressHTT8000 = factory.getPair(HEX_ADDRESS, _httManager.getOrCreateHEXTimeToken(7999));
require(pairAddressHTT8000 != address(0), "A038");
_set(0, poolPointSchedule[8]);
_set(3, poolPointSchedule[9]);
_set(1, poolPointSchedule[10]);
_set(4, poolPointSchedule[11]);
_set(2, poolPointSchedule[12]);
_add(poolPointSchedule[13], IERC20(pairAddressHTT8000));
}
_lastMassUpdate = block.timestamp;
}
/**
* @dev Update reward variables of the given pool to be up-to-date.
* @param _pid Internal ID of the pool.
*/
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.timestamp <= pool.lastRewardTime) {
return;
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
pool.lastRewardTime = block.timestamp;
return;
}
uint256 totalRewards = _getFarmEmissions(pool.lastRewardTime, block.timestamp);
uint256 actrReward = totalRewards * pool.allocPoint / totalAllocPoint;
actr.mint(address(this), actrReward);
pool.accActrPerShare = pool.accActrPerShare + (actrReward * ACC_ACTR_PRECISION / lpSupply);
pool.lastRewardTime = block.timestamp;
}
/**
* @dev Deposit LP tokens to MasterChef for ACTR allocation.
* @param _pid ID of the pool.
* @param _amount Amount of LP tokens to deposit.
*/
function deposit(uint256 _pid, uint256 _amount) external {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
uint256 pending = (user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;
user.amount = user.amount + _amount;
user.rewardDebt = user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION;
if (pending > 0) {
safeActrTransfer(msg.sender, pending);
emit CollectEmissions(msg.sender, pending);
}
pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
emit Deposit(msg.sender, _pid, _amount);
}
/**
* @dev Withdraw LP tokens from MasterChef.
* @param _pid ID of the pool.
* @param _amount Amount of LP tokens to withdraw.
*/
function withdraw(uint256 _pid, uint256 _amount) external {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, "A030");
updatePool(_pid);
uint256 pending = (user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION) - user.rewardDebt;
user.amount = user.amount - _amount;
user.rewardDebt = user.amount * pool.accActrPerShare / ACC_ACTR_PRECISION;
if (pending > 0) {
safeActrTransfer(msg.sender, pending);
emit CollectEmissions(msg.sender, pending);
}
pool.lpToken.safeTransfer(address(msg.sender), _amount);
emit Withdraw(msg.sender, _pid, _amount);
}
/**
* @dev Safe ACTR transfer function, just in case if rounding error causes pool to not have enough ACTR.
* @param _to Recipient address.
* @param _amount Amount of ACTR tokens to transfer.
*/
function safeActrTransfer(address _to, uint256 _amount) private {
uint256 actrBal = actr.balanceOf(address(this));
if (_amount > actrBal) {
actr.transfer(_to, actrBal);
} else {
actr.transfer(_to, _amount);
}
}
}
contracts/declarations/Types.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
struct HEXStake {
uint40 stakeId;
uint72 stakedHearts;
uint72 stakeShares;
uint16 lockedDay;
uint16 stakedDays;
uint16 unlockedDay;
bool isAutoStake;
}
struct HEXStakeMinimal {
uint40 stakeId;
uint72 stakedHearts;
uint72 stakeShares;
uint16 lockedDay;
uint16 stakedDays;
}
struct ShareStore {
HEXStakeMinimal stake;
uint16 mintedDays;
uint8 launchBonus;
uint16 loanStart;
uint16 loanedDays;
uint32 interestRate;
uint8 paymentsMade;
bool isLoaned;
}
struct ShareCache {
HEXStakeMinimal _stake;
uint256 _mintedDays;
uint256 _launchBonus;
uint256 _loanStart;
uint256 _loanedDays;
uint256 _interestRate;
uint256 _paymentsMade;
bool _isLoaned;
}
library LibPart {
bytes32 public constant TYPE_HASH = keccak256("Part(address account,uint96 value)");
struct Part {
address payable account;
uint96 value;
}
function hash(Part memory part) internal pure returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, part.account, part.value));
}
}
contracts/interfaces/HEX.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IHEX {
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event Claim(
uint256 data0,
uint256 data1,
bytes20 indexed btcAddr,
address indexed claimToAddr,
address indexed referrerAddr
);
event ClaimAssist(
uint256 data0,
uint256 data1,
uint256 data2,
address indexed senderAddr
);
event DailyDataUpdate(uint256 data0, address indexed updaterAddr);
event ShareRateChange(uint256 data0, uint40 indexed stakeId);
event StakeEnd(
uint256 data0,
uint256 data1,
address indexed stakerAddr,
uint40 indexed stakeId
);
event StakeGoodAccounting(
uint256 data0,
uint256 data1,
address indexed stakerAddr,
uint40 indexed stakeId,
address indexed senderAddr
);
event StakeStart(
uint256 data0,
address indexed stakerAddr,
uint40 indexed stakeId
);
event Transfer(address indexed from, address indexed to, uint256 value);
event XfLobbyEnter(
uint256 data0,
address indexed memberAddr,
uint256 indexed entryId,
address indexed referrerAddr
);
event XfLobbyExit(
uint256 data0,
address indexed memberAddr,
uint256 indexed entryId,
address indexed referrerAddr
);
function allocatedSupply() external view returns (uint256);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function btcAddressClaim(
uint256 rawSatoshis,
bytes32[] memory proof,
address claimToAddr,
bytes32 pubKeyX,
bytes32 pubKeyY,
uint8 claimFlags,
uint8 v,
bytes32 r,
bytes32 s,
uint256 autoStakeDays,
address referrerAddr
) external returns (uint256);
function btcAddressClaims(bytes20) external view returns (bool);
function btcAddressIsClaimable(
bytes20 btcAddr,
uint256 rawSatoshis,
bytes32[] memory proof
) external view returns (bool);
function btcAddressIsValid(
bytes20 btcAddr,
uint256 rawSatoshis,
bytes32[] memory proof
) external pure returns (bool);
function claimMessageMatchesSignature(
address claimToAddr,
bytes32 claimParamHash,
bytes32 pubKeyX,
bytes32 pubKeyY,
uint8 claimFlags,
uint8 v,
bytes32 r,
bytes32 s
) external pure returns (bool);
function currentDay() external view returns (uint256);
function dailyData(uint256)
external
view
returns (
uint72 dayPayoutTotal,
uint72 dayStakeSharesTotal,
uint56 dayUnclaimedSatoshisTotal
);
function dailyDataRange(uint256 beginDay, uint256 endDay)
external
view
returns (uint256[] memory list);
function dailyDataUpdate(uint256 beforeDay) external;
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 subtractedValue)
external
returns (bool);
function globalInfo() external view returns (uint256[13] memory);
function globals()
external
view
returns (
uint72 lockedHeartsTotal,
uint72 nextStakeSharesTotal,
uint40 shareRate,
uint72 stakePenaltyTotal,
uint16 dailyDataCount,
uint72 stakeSharesTotal,
uint40 latestStakeId,
uint128 claimStats
);
function increaseAllowance(address spender, uint256 addedValue)
external
returns (bool);
function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof)
external
pure
returns (bool);
function name() external view returns (string memory);
function pubKeyToBtcAddress(
bytes32 pubKeyX,
bytes32 pubKeyY,
uint8 claimFlags
) external pure returns (bytes20);
function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY)
external
pure
returns (address);
function stakeCount(address stakerAddr) external view returns (uint256);
function stakeEnd(uint256 stakeIndex, uint40 stakeIdParam) external;
function stakeGoodAccounting(
address stakerAddr,
uint256 stakeIndex,
uint40 stakeIdParam
) external;
function stakeLists(address, uint256)
external
view
returns (
uint40 stakeId,
uint72 stakedHearts,
uint72 stakeShares,
uint16 lockedDay,
uint16 stakedDays,
uint16 unlockedDay,
bool isAutoStake
);
function stakeStart(uint256 newStakedHearts, uint256 newStakedDays)
external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function xfLobby(uint256) external view returns (uint256);
function xfLobbyEnter(address referrerAddr) external payable;
function xfLobbyEntry(address memberAddr, uint256 entryId)
external
view
returns (uint256 rawAmount, address referrerAddr);
function xfLobbyExit(uint256 enterDay, uint256 count) external;
function xfLobbyFlush() external;
function xfLobbyMembers(uint256, address)
external
view
returns (uint40 headIndex, uint40 tailIndex);
function xfLobbyPendingDays(address memberAddr)
external
view
returns (uint256[2] memory words);
function xfLobbyRange(uint256 beginDay, uint256 endDay)
external
view
returns (uint256[] memory list);
}
contracts/interfaces/HEXStakeInstance.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { HEXStakeMinimal, HEXStake } from "../declarations/Types.sol";
interface IHEXStakeInstance {
/**
* @dev Calls the HEX function "stakeGoodAccounting" against the
* HEX stake held within the HSI.
*/
function share() external view returns (
HEXStakeMinimal memory stake,
uint16 mintedDays,
uint8 launchBonus,
uint16 loanStart,
uint16 loanedDays,
uint32 interestRate,
uint8 paymentsMade,
bool isLoaned
);
/**
* @dev Calls the HEX function "stakeGoodAccounting" against the
* HEX stake held within the HSI.
*/
function goodAccounting() external;
/**
* @dev Fetches stake data from the HEX contract.
* @return A "HEXStake" object containg the HEX stake data.
*/
function stakeDataFetch() external view returns(HEXStake memory);
}
contracts/interfaces/HEXStakeInstanceManager.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "./HEX.sol";
import "../declarations/Types.sol";
interface IHEXStakeInstanceManager {
event Approval(
address indexed owner,
address indexed approved,
uint256 indexed tokenId
);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
event HSIDetokenize(
uint256 timestamp,
uint256 indexed hsiTokenId,
address indexed hsiAddress,
address indexed staker
);
event HSIEnd(
uint256 timestamp,
address indexed hsiAddress,
address indexed staker
);
event HSIStart(
uint256 timestamp,
address indexed hsiAddress,
address indexed staker
);
event HSITokenize(
uint256 timestamp,
uint256 indexed hsiTokenId,
address indexed hsiAddress,
address indexed staker
);
event HSITransfer(
uint256 timestamp,
address indexed hsiAddress,
address indexed oldStaker,
address indexed newStaker
);
event RoyaltiesSet(uint256 tokenId, LibPart.Part[] royalties);
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
function approve(address to, uint256 tokenId) external;
function balanceOf(address owner) external view returns (uint256);
function getApproved(uint256 tokenId) external view returns (address);
function getRaribleV2Royalties(uint256 id)
external
view
returns (LibPart.Part[] memory);
function hexStakeDetokenize(uint256 tokenId) external returns (address);
function hexStakeEnd(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function hexStakeStart(uint256 amount, uint256 length)
external
returns (address);
function hexStakeTokenize(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function hsiCount(address user) external view returns (uint256);
function hsiLists(address, uint256) external view returns (address);
function hsiToken(uint256) external view returns (address);
function hsiTransfer(
address currentHolder,
uint256 hsiIndex,
address hsiAddress,
address newHolder
) external;
function hsiUpdate(
address holder,
uint256 hsiIndex,
address hsiAddress,
ShareCache memory share
) external;
function isApprovedForAll(address owner, address operator)
external
view
returns (bool);
function name() external view returns (string memory);
function owner() external pure returns (address);
function ownerOf(uint256 tokenId) external view returns (address);
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) external;
function setApprovalForAll(address operator, bool approved) external;
function stakeCount(address user) external view returns (uint256);
function stakeLists(address user, uint256 hsiIndex)
external
view
returns (HEXStake memory);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
function symbol() external view returns (string memory);
function tokenByIndex(uint256 index) external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index)
external
view
returns (uint256);
function tokenURI(uint256 tokenId) external view returns (string memory);
function totalSupply() external view returns (uint256);
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
}
contracts/interfaces/Hedron.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { HEXStakeMinimal } from "../declarations/Types.sol";
interface IHedron {
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event Claim(uint256 data, address indexed claimant, uint40 indexed stakeId);
event LoanEnd(
uint256 data,
address indexed borrower,
uint40 indexed stakeId
);
event LoanLiquidateBid(
uint256 data,
address indexed bidder,
uint40 indexed stakeId,
uint40 indexed liquidationId
);
event LoanLiquidateExit(
uint256 data,
address indexed liquidator,
uint40 indexed stakeId,
uint40 indexed liquidationId
);
event LoanLiquidateStart(
uint256 data,
address indexed borrower,
uint40 indexed stakeId,
uint40 indexed liquidationId
);
event LoanPayment(
uint256 data,
address indexed borrower,
uint40 indexed stakeId
);
event LoanStart(
uint256 data,
address indexed borrower,
uint40 indexed stakeId
);
event Mint(uint256 data, address indexed minter, uint40 indexed stakeId);
event Transfer(address indexed from, address indexed to, uint256 value);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function calcLoanPayment(
address borrower,
uint256 hsiIndex,
address hsiAddress
) external view returns (uint256, uint256);
function calcLoanPayoff(
address borrower,
uint256 hsiIndex,
address hsiAddress
) external view returns (uint256, uint256);
function claimInstanced(
uint256 hsiIndex,
address hsiAddress,
address hsiStarterAddress
) external;
function claimNative(uint256 stakeIndex, uint40 stakeId)
external
returns (uint256);
function currentDay() external view returns (uint256);
function dailyDataList(uint256)
external
view
returns (
uint72 dayMintedTotal,
uint72 dayLoanedTotal,
uint72 dayBurntTotal,
uint32 dayInterestRate,
uint8 dayMintMultiplier
);
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 subtractedValue)
external
returns (bool);
function hsim() external view returns (address);
function increaseAllowance(address spender, uint256 addedValue)
external
returns (bool);
function liquidationList(uint256)
external
view
returns (
uint256 liquidationStart,
address hsiAddress,
uint96 bidAmount,
address liquidator,
uint88 endOffset,
bool isActive
);
function loanInstanced(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function loanLiquidate(
address owner,
uint256 hsiIndex,
address hsiAddress
) external returns (uint256);
function loanLiquidateBid(uint256 liquidationId, uint256 liquidationBid)
external
returns (uint256);
function loanLiquidateExit(uint256 hsiIndex, uint256 liquidationId)
external
returns (address);
function loanPayment(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function loanPayoff(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function loanedSupply() external view returns (uint256);
function mintInstanced(uint256 hsiIndex, address hsiAddress)
external
returns (uint256);
function mintNative(uint256 stakeIndex, uint40 stakeId)
external
returns (uint256);
function name() external view returns (string memory);
function proofOfBenevolence(uint256 amount) external;
function shareList(uint256)
external
view
returns (
HEXStakeMinimal memory stake,
uint16 mintedDays,
uint8 launchBonus,
uint16 loanStart,
uint16 loanedDays,
uint32 interestRate,
uint8 paymentsMade,
bool isLoaned
);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
contracts/interfaces/PulseXFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IPulseXFactory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
Compiler Settings
{"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"],"":["ast"]}},"optimizer":{"runs":200,"enabled":true},"libraries":{},"evmVersion":"paris"}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"uint16","name":"_maturity","internalType":"uint16"},{"type":"address","name":"actuatorAddress","internalType":"address"}]},{"type":"error","name":"ERC20InsufficientAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"allowance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InsufficientBalance","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"uint256","name":"balance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InvalidApprover","inputs":[{"type":"address","name":"approver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidReceiver","inputs":[{"type":"address","name":"receiver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSender","inputs":[{"type":"address","name":"sender","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSpender","inputs":[{"type":"address","name":"spender","internalType":"address"}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CollectFees","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Withdraw","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"CREATION_FEE_RATE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"accHttPerShare","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract Actuator"}],"name":"actr","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"burn","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"calculateTax","inputs":[{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"collectFees","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"deposit","inputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"httManager","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"maturity","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mint","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalDeposits","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"rewardDebt","internalType":"uint256"},{"type":"uint256","name":"capitalAdded","internalType":"uint256"}],"name":"userInfo","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"},{"type":"uint256","name":"","internalType":"uint256"}],"name":"withdraw","inputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"}]}]
Contract Creation Code
0x60806040523480156200001157600080fd5b506040516200147738038062001477833981016040819052620000349162000284565b6200004361ffff831662000101565b60405160200162000055919062000306565b60408051601f198184030181529190526200007461ffff841662000101565b60405160200162000086919062000333565b60408051601f198184030181529190526003620000a48382620003f5565b506004620000b38282620003f5565b5050600580546001600160a01b0319166001600160a01b039390931692909217909155506008805461ffff929092166001600160b01b031990921691909117336201000002179055620004c1565b6060600062000110836200019a565b60010190506000816001600160401b038111156200013257620001326200034e565b6040519080825280601f01601f1916602001820160405280156200015d576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846200016757509392505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310620001e4577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef8100000000831062000211576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106200023057662386f26fc10000830492506010015b6305f5e100831062000249576305f5e100830492506008015b61271083106200025e57612710830492506004015b6064831062000271576064830492506002015b600a83106200027e576001015b92915050565b600080604083850312156200029857600080fd5b825161ffff81168114620002ab57600080fd5b60208401519092506001600160a01b0381168114620002c957600080fd5b809150509250929050565b6000815160005b81811015620002f75760208185018101518683015201620002db565b50600093019283525090919050565b6e02422ac102a34b6b2902a37b5b2b71608d1b815260006200032c600f830184620002d4565b9392505050565b634854542d60e01b815260006200032c6004830184620002d4565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200037957607f821691505b6020821081036200039a57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620003f0576000816000526020600020601f850160051c81016020861015620003cb5750805b601f850160051c820191505b81811015620003ec57828155600101620003d7565b5050505b505050565b81516001600160401b038111156200041157620004116200034e565b620004298162000422845462000364565b84620003a0565b602080601f831160018114620004615760008415620004485750858301515b600019600386901b1c1916600185901b178555620003ec565b600085815260208120601f198616915b82811015620004925788860151825594840194600190910190840162000471565b5085821015620004b15787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610fa680620004d16000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c80636d2dbe24116100b85780639dc29fac1161007c5780639dc29fac146102de578063a9059cbb146102f1578063bfb9f50214610304578063c879657214610317578063dd62ed3e1461031f578063f3fef3a31461035857600080fd5b80636d2dbe241461029357806370a082311461029b5780637d882097146102c457806395d89b41146102cd5780639cdcfb2b146102d557600080fd5b806323b872dd1161010a57806323b872dd146102055780632a67ba3614610218578063313ce5671461024957806340c10f191461025857806347e7ef241461026d578063483a93101461028057600080fd5b806306fdde0314610147578063095ea7b31461016557806318160ddd146101885780631959a0021461019a578063204f83f9146101e4575b600080fd5b61014f610380565b60405161015c9190610d82565b60405180910390f35b610178610173366004610ded565b610412565b604051901515815260200161015c565b6002545b60405190815260200161015c565b6101c96101a8366004610e17565b60096020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161015c565b6008546101f29061ffff1681565b60405161ffff909116815260200161015c565b610178610213366004610e39565b61042c565b600854610231906201000090046001600160a01b031681565b6040516001600160a01b03909116815260200161015c565b6040516008815260200161015c565b61026b610266366004610ded565b610450565b005b61018c61027b366004610ded565b610521565b61018c61028e366004610e75565b6106b0565b61018c606481565b61018c6102a9366004610e17565b6001600160a01b031660009081526020819052604090205490565b61018c60065481565b61014f6106ca565b61018c60075481565b61026b6102ec366004610ded565b6106d9565b6101786102ff366004610ded565b61072c565b600554610231906001600160a01b031681565b61018c61073a565b61018c61032d366004610e8e565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61036b610366366004610ded565b610831565b6040805192835260208301919091520161015c565b60606003805461038f90610ec1565b80601f01602080910402602001604051908101604052809291908181526020018280546103bb90610ec1565b80156104085780601f106103dd57610100808354040283529160200191610408565b820191906000526020600020905b8154815290600101906020018083116103eb57829003601f168201915b5050505050905090565b600033610420818585610a00565b60019150505b92915050565b60003361043a858285610a12565b610445858585610a8a565b506001949350505050565b6008546201000090046001600160a01b031633146104a25760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b60405180910390fd5b6006546000036104ba576104b68282610ae9565b5050565b60006104c5826106b0565b905060006104d38284610f11565b6006549091506104ed69021e19e0c9bab240000084610f24565b6104f79190610f3b565b6007546105049190610f5d565b6007556105113083610ae9565b61051b8482610ae9565b50505050565b6005546000906001600160a01b031633146105675760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b03831660009081526009602052604081206006805491928592610592908490610f5d565b9091555050600181015460075482546000929169021e19e0c9bab2400000916105bb9190610f24565b6105c59190610f3b565b6105cf9190610f11565b82549091506105df908590610f5d565b80835560075469021e19e0c9bab2400000916105fb9190610f24565b6106059190610f3b565b60018301554260028301558015610664576106208582610b1f565b846001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae8260405161065b91815260200190565b60405180910390a25b846001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c8560405161069f91815260200190565b60405180910390a250549392505050565b60006127106106c0606484610f24565b6104269190610f3b565b60606004805461038f90610ec1565b6008546201000090046001600160a01b031633146107225760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b6104b68282610b4d565b600033610420818585610a8a565b33600090815260096020526040812080546107805760405162461bcd60e51b8152600401610499906020808252600490820152632098191b60e11b604082015260600190565b6000816001015469021e19e0c9bab240000060075484600001546107a49190610f24565b6107ae9190610f3b565b6107b89190610f11565b905069021e19e0c9bab240000060075483600001546107d79190610f24565b6107e19190610f3b565b60018301558015610426576107f63382610b1f565b60405181815233907fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae9060200160405180910390a292915050565b60055460009081906001600160a01b031633146108795760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b038416600090815260096020526040902080548411156108cb5760405162461bcd60e51b8152600401610499906020808252600490820152634130333760e01b604082015260600190565b83600660008282546108dd9190610f11565b9091555050600181015460075482546000929169021e19e0c9bab2400000916109069190610f24565b6109109190610f3b565b61091a9190610f11565b825490915061092a908690610f11565b80835560075469021e19e0c9bab2400000916109469190610f24565b6109509190610f3b565b600183015580156109a9576109658682610b1f565b856001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae826040516109a091815260200190565b60405180910390a25b856001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364866040516109e491815260200190565b60405180910390a2508054600290910154909590945092505050565b610a0d8383836001610b83565b505050565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461051b5781811015610a7b57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610499565b61051b84848484036000610b83565b6001600160a01b038316610ab457604051634b637e8f60e11b815260006004820152602401610499565b6001600160a01b038216610ade5760405163ec442f0560e01b815260006004820152602401610499565b610a0d838383610c58565b6001600160a01b038216610b135760405163ec442f0560e01b815260006004820152602401610499565b6104b660008383610c58565b3060009081526020819052604090205480821115610b4257610a0d308483610a8a565b610a0d308484610a8a565b6001600160a01b038216610b7757604051634b637e8f60e11b815260006004820152602401610499565b6104b682600083610c58565b6001600160a01b038416610bad5760405163e602df0560e01b815260006004820152602401610499565b6001600160a01b038316610bd757604051634a1406b160e11b815260006004820152602401610499565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561051b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610c4a91815260200190565b60405180910390a350505050565b6001600160a01b038316610c83578060026000828254610c789190610f5d565b90915550610cf59050565b6001600160a01b03831660009081526020819052604090205481811015610cd65760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610499565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d1157600280548290039055610d30565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610d7591815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610db057858101830151858201604001528201610d94565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610de857600080fd5b919050565b60008060408385031215610e0057600080fd5b610e0983610dd1565b946020939093013593505050565b600060208284031215610e2957600080fd5b610e3282610dd1565b9392505050565b600080600060608486031215610e4e57600080fd5b610e5784610dd1565b9250610e6560208501610dd1565b9150604084013590509250925092565b600060208284031215610e8757600080fd5b5035919050565b60008060408385031215610ea157600080fd5b610eaa83610dd1565b9150610eb860208401610dd1565b90509250929050565b600181811c90821680610ed557607f821691505b602082108103610ef557634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561042657610426610efb565b808202811582820484141761042657610426610efb565b600082610f5857634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561042657610426610efb56fea264697066735822122015f4664b900775556b001c8d2cd115056d7089314772fe4a815e864c8bfd834f64736f6c634300081800330000000000000000000000000000000000000000000000000000000000001b5800000000000000000000000085df7ce20a4ce0cf859804b45cb540ffe42074da
Deployed ByteCode
0x608060405234801561001057600080fd5b50600436106101425760003560e01c80636d2dbe24116100b85780639dc29fac1161007c5780639dc29fac146102de578063a9059cbb146102f1578063bfb9f50214610304578063c879657214610317578063dd62ed3e1461031f578063f3fef3a31461035857600080fd5b80636d2dbe241461029357806370a082311461029b5780637d882097146102c457806395d89b41146102cd5780639cdcfb2b146102d557600080fd5b806323b872dd1161010a57806323b872dd146102055780632a67ba3614610218578063313ce5671461024957806340c10f191461025857806347e7ef241461026d578063483a93101461028057600080fd5b806306fdde0314610147578063095ea7b31461016557806318160ddd146101885780631959a0021461019a578063204f83f9146101e4575b600080fd5b61014f610380565b60405161015c9190610d82565b60405180910390f35b610178610173366004610ded565b610412565b604051901515815260200161015c565b6002545b60405190815260200161015c565b6101c96101a8366004610e17565b60096020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161015c565b6008546101f29061ffff1681565b60405161ffff909116815260200161015c565b610178610213366004610e39565b61042c565b600854610231906201000090046001600160a01b031681565b6040516001600160a01b03909116815260200161015c565b6040516008815260200161015c565b61026b610266366004610ded565b610450565b005b61018c61027b366004610ded565b610521565b61018c61028e366004610e75565b6106b0565b61018c606481565b61018c6102a9366004610e17565b6001600160a01b031660009081526020819052604090205490565b61018c60065481565b61014f6106ca565b61018c60075481565b61026b6102ec366004610ded565b6106d9565b6101786102ff366004610ded565b61072c565b600554610231906001600160a01b031681565b61018c61073a565b61018c61032d366004610e8e565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b61036b610366366004610ded565b610831565b6040805192835260208301919091520161015c565b60606003805461038f90610ec1565b80601f01602080910402602001604051908101604052809291908181526020018280546103bb90610ec1565b80156104085780601f106103dd57610100808354040283529160200191610408565b820191906000526020600020905b8154815290600101906020018083116103eb57829003601f168201915b5050505050905090565b600033610420818585610a00565b60019150505b92915050565b60003361043a858285610a12565b610445858585610a8a565b506001949350505050565b6008546201000090046001600160a01b031633146104a25760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b60405180910390fd5b6006546000036104ba576104b68282610ae9565b5050565b60006104c5826106b0565b905060006104d38284610f11565b6006549091506104ed69021e19e0c9bab240000084610f24565b6104f79190610f3b565b6007546105049190610f5d565b6007556105113083610ae9565b61051b8482610ae9565b50505050565b6005546000906001600160a01b031633146105675760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b03831660009081526009602052604081206006805491928592610592908490610f5d565b9091555050600181015460075482546000929169021e19e0c9bab2400000916105bb9190610f24565b6105c59190610f3b565b6105cf9190610f11565b82549091506105df908590610f5d565b80835560075469021e19e0c9bab2400000916105fb9190610f24565b6106059190610f3b565b60018301554260028301558015610664576106208582610b1f565b846001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae8260405161065b91815260200190565b60405180910390a25b846001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c8560405161069f91815260200190565b60405180910390a250549392505050565b60006127106106c0606484610f24565b6104269190610f3b565b60606004805461038f90610ec1565b6008546201000090046001600160a01b031633146107225760405162461bcd60e51b8152600401610499906020808252600490820152632098199b60e11b604082015260600190565b6104b68282610b4d565b600033610420818585610a8a565b33600090815260096020526040812080546107805760405162461bcd60e51b8152600401610499906020808252600490820152632098191b60e11b604082015260600190565b6000816001015469021e19e0c9bab240000060075484600001546107a49190610f24565b6107ae9190610f3b565b6107b89190610f11565b905069021e19e0c9bab240000060075483600001546107d79190610f24565b6107e19190610f3b565b60018301558015610426576107f63382610b1f565b60405181815233907fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae9060200160405180910390a292915050565b60055460009081906001600160a01b031633146108795760405162461bcd60e51b8152600401610499906020808252600490820152634130333960e01b604082015260600190565b6001600160a01b038416600090815260096020526040902080548411156108cb5760405162461bcd60e51b8152600401610499906020808252600490820152634130333760e01b604082015260600190565b83600660008282546108dd9190610f11565b9091555050600181015460075482546000929169021e19e0c9bab2400000916109069190610f24565b6109109190610f3b565b61091a9190610f11565b825490915061092a908690610f11565b80835560075469021e19e0c9bab2400000916109469190610f24565b6109509190610f3b565b600183015580156109a9576109658682610b1f565b856001600160a01b03167fc9a0214d4c5fed6341233260a7bc0c9ac1d712cc5882165fa985bb71d4f207ae826040516109a091815260200190565b60405180910390a25b856001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364866040516109e491815260200190565b60405180910390a2508054600290910154909590945092505050565b610a0d8383836001610b83565b505050565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461051b5781811015610a7b57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610499565b61051b84848484036000610b83565b6001600160a01b038316610ab457604051634b637e8f60e11b815260006004820152602401610499565b6001600160a01b038216610ade5760405163ec442f0560e01b815260006004820152602401610499565b610a0d838383610c58565b6001600160a01b038216610b135760405163ec442f0560e01b815260006004820152602401610499565b6104b660008383610c58565b3060009081526020819052604090205480821115610b4257610a0d308483610a8a565b610a0d308484610a8a565b6001600160a01b038216610b7757604051634b637e8f60e11b815260006004820152602401610499565b6104b682600083610c58565b6001600160a01b038416610bad5760405163e602df0560e01b815260006004820152602401610499565b6001600160a01b038316610bd757604051634a1406b160e11b815260006004820152602401610499565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561051b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610c4a91815260200190565b60405180910390a350505050565b6001600160a01b038316610c83578060026000828254610c789190610f5d565b90915550610cf59050565b6001600160a01b03831660009081526020819052604090205481811015610cd65760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610499565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d1157600280548290039055610d30565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610d7591815260200190565b60405180910390a3505050565b60006020808352835180602085015260005b81811015610db057858101830151858201604001528201610d94565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610de857600080fd5b919050565b60008060408385031215610e0057600080fd5b610e0983610dd1565b946020939093013593505050565b600060208284031215610e2957600080fd5b610e3282610dd1565b9392505050565b600080600060608486031215610e4e57600080fd5b610e5784610dd1565b9250610e6560208501610dd1565b9150604084013590509250925092565b600060208284031215610e8757600080fd5b5035919050565b60008060408385031215610ea157600080fd5b610eaa83610dd1565b9150610eb860208401610dd1565b90509250929050565b600181811c90821680610ed557607f821691505b602082108103610ef557634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561042657610426610efb565b808202811582820484141761042657610426610efb565b600082610f5857634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561042657610426610efb56fea264697066735822122015f4664b900775556b001c8d2cd115056d7089314772fe4a815e864c8bfd834f64736f6c63430008180033