Transactions
Token Transfers
Tokens
Internal Transactions
Coin Balance History
Logs
Code
Read Contract
Write Contract
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- PulseCoinStaking
- Optimization enabled
- true
- Compiler version
- v0.8.24+commit.e11b9ed9
- Optimization runs
- 200
- EVM Version
- default
- Verified at
- 2024-05-05T08:31:33.188964Z
Constructor Arguments
0x00000000000000000000000029ea7545def87022badc76323f373ea1e707c523000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a27000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d90000000000000000000000004c8218dc22e478963c02748857245fad79aad0c6
Arg [0] (address) : 0x29ea7545def87022badc76323f373ea1e707c523
Arg [1] (address) : 0xa1077a294dde1b09bb078844df40758a5d0f9a27
Arg [2] (address) : 0x165c3410fc91ef562c50559f7d2289febed552d9
Arg [3] (address) : 0x4c8218dc22e478963c02748857245fad79aad0c6
Contract source code
// File: @openzeppelin/contracts/utils/Context.sol
// 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;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: @openzeppelin/contracts/security/ReentrancyGuard.sol
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// File: @openzeppelin/contracts/utils/Address.sol
// 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();
}
}
}
// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol
// 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);
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
// 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);
}
// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
/**
* @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;
}
}
// File: PulseCoinStaking-05-05-24.sol
/**
* SPDX-License-Identifier: GPL-3.0
*
* ██████╗ ██╗ ██╗██╗ ███████╗███████╗ ██████╗ ██████╗ ██╗███╗ ██╗
* ██╔══██╗██║ ██║██║ ██╔════╝██╔════╝██╔════╝██╔═══██╗██║████╗ ██║
* ██████╔╝██║ ██║██║ ███████╗█████╗ ██║ ██║ ██║██║██╔██╗ ██║
* ██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ ██║ ██║ ██║██║██║╚██╗██║
* ██║ ╚██████╔╝███████╗███████║███████╗╚██████╗╚██████╔╝██║██║ ╚████║
* ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝
* __ __ _
* \ \/ /___ __ _______ _________ (_)___
* \ / __ \/ / / / ___/ / ___/ __ \/ / __ \
* / / /_/ / /_/ / / / /__/ /_/ / / / / /
* /_/\____/\__,_/_/ ____ \___/\____/_/_/ /_/________ _
* ____ ____ / __ \__ __/ /_______ / ____/ /_ ____ _(_)___
* / __ \/ __ \ / /_/ / / / / / ___/ _ \/ / / __ \/ __ `/ / __ \
* / /_/ / / / / / ____/ /_/ / (__ ) __/ /___/ / / / /_/ / / / / /
* \____/_/ /_/ /_/ \__,_/_/____/\___/\____/_/ /_/\__,_/_/_/ /_/
*
* @title PulseCoin Staking Contract
* @notice This contract is released under the GNU General Public License v3.0
*
*
* Copyright (C) 2024 PulseCoin
* Last changed 2024-05-05
*/
pragma solidity 0.8.24;
// Interface for WPLS token
interface IWPLS {
function deposit() external payable;
function withdraw(uint256 wad) external;
function approve(address guy, uint256 wad) external returns (bool);
function transfer(address dst, uint256 wad) external returns (bool);
}
// Interface for PulseX's Factory contract
interface IPulseXFactory {
function createPair(address tokenA, address tokenB) external returns (address pair);
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
interface IPulseXPair {
function mint(address to) external returns (uint256 liquidity);
function burn(uint256 amount) external;
function transfer(address to, uint256 amount) external returns (bool);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
interface IPulseXRouter {
// Add the addLiquidityETH function
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
// Function to get the address of WPLS token
function WPLS() external view returns (address);
function factory() external view returns (address);
}
contract PulseCoinStaking is ReentrancyGuard, Ownable {
using SafeERC20 for IERC20;
uint256 public constant PulseCoin_TOTAL_SUPPLY = 21e6 * 1e18; // Total supply of PulseCoin tokens
address public burnAddress = 0x000000000000000000000000000000000000dEaD;
uint256 public constant CLAIM_PERIOD_IN_BLOCKS = 92 * 24 * 3600 / 10;
uint256 public liquidityPercentage; // 10%
address public liquidityContractAddress;
uint256 public beneficiaryPercentage;
address public beneficiaryAddress;
uint256 public unstakeFee;
address public pulseXFactoryAddress;
address public WPLSContractAddress;
address public pulseXRouterAddress;
address public stakingTokenAddress;
// For testing, use Pulsechain Testnet V4 and the following testnet contracts
// address public constant pulseXFactoryAddress = 0xFf0538782D122d3112F75dc7121F61562261c0f7;
// address public constant WPLSContractAddress = 0x70499adEBB11Efd915E3b69E700c331778628707;
// address public constant pulseXRouterAddress = 0xDaE9dd3d1A52CfCe9d5F2fAC7fDe164D500E50f7;
// address public constant stakingTokenAddress = 0x4994093e0c753aF4534B2f3dfCd99DbCf45421EF;
uint256 public burntSupplyPercentage = 17;
uint256 public totalStakersCount;
struct Stake {
uint256 amount;
uint256 stakeBlockNumber;
}
struct StakeDetail {
uint256 stakeID;
address staker;
uint256 amount;
uint256 stakeBlockNumber;
bool isActive;
}
struct Airdrop {
IERC20 airdropToken;
uint256 airdropAmount;
uint256 snapshotTotalStaked;
uint256 airdropBlockNumber;
bool redistributed;
address pCCreatedLPAddress;
bool isExternal;
mapping(address => bool) claimed;
address msgSender;
bool useSnapshot;
uint256 remainingBalance;
}
mapping(address => Stake) public stakes;
mapping(uint256 => StakeDetail) public stakeDetails; // Mapping from stake ID to StakeDetail struct
mapping(address => uint256[]) public userStakeIDs; // Mapping from user address to list of their stake IDs
uint256 private stakeIDCounter = 0; // Counter to generate unique stake IDs
Airdrop[] public airdrops;
uint256 public totalStaked;
//mapping(address => uint256) public liquidityProvisionBalance;
mapping(address => bool) public factoryContracts; // Mapping to keep track of factory contracts
mapping(address => bool) public whitelistedTokens; // Whitelist of external tokens to accept
receive() external payable {}
// Constructor
constructor(
address _pulseXFactoryAddress,
address _WPLSContractAddress,
address _pulseXRouterAddress,
address _stakingTokenAddress
) Ownable(msg.sender) {
beneficiaryAddress = 0x000000000000000000000000000000000000dEaD;
liquidityContractAddress = 0x000000000000000000000000000000000000dEaD;
unstakeFee = 369;
liquidityPercentage = 10;
beneficiaryPercentage = 369;
pulseXFactoryAddress = _pulseXFactoryAddress;
WPLSContractAddress = _WPLSContractAddress;
pulseXRouterAddress = _pulseXRouterAddress;
stakingTokenAddress = _stakingTokenAddress;
PulseCoinToken = IERC20(_stakingTokenAddress);
pulseXFactory = IPulseXFactory(_pulseXFactoryAddress);
wpls = IWPLS(_WPLSContractAddress);
pulseXRouter = IPulseXRouter(_pulseXRouterAddress);
}
event Staked(address indexed user, uint256 amount, uint256 stakeID);
event Unstaked(address indexed user, uint256 amount);
event AirdropTokenReceived(address token, uint256 amount, uint256 airdropID);
event Claimed(address indexed user, uint256 airdropID);
event LiquidityPairCreated(address indexed token, address indexed wpls, address pair);
event LPTokensBurned(address indexed pair, uint256 amount, address burnAddress);
event LiquidityAdded(uint amountToken, uint amountETH, uint liquidity);
event FactoryContractAdded(address factoryAddress);
event FactoryContractRemoved(address factoryAddress);
event BeneficiaryAddressUpdated(address indexed newBeneficiaryAddress);
event UnstakeFeeUpdated(uint256 unstakeFee);
event LiquidityPercentageUpdated(uint256 newLiquidityPercentage);
event LiquidityContractAddressUpdated(address indexed newLiquidityContractAddress);
event BeneficiaryPercentageUpdated(uint256 newBeneficiaryPercentage);
// New constants for LP creation
IERC20 public PulseCoinToken = IERC20(stakingTokenAddress);
IPulseXFactory public pulseXFactory = IPulseXFactory(pulseXFactoryAddress);
IWPLS public wpls = IWPLS(WPLSContractAddress);
IPulseXRouter private pulseXRouter = IPulseXRouter(pulseXRouterAddress);
/**
* @dev Ensures that the function can only be called by addresses that have been designated as factory contracts.
* This modifier checks the calling address against a set of addresses marked as factory contracts within the contract's state. If the caller is not recognized as a factory contract, the transaction is reverted with an error message.
*/
modifier onlyFactoryContracts() {
require(factoryContracts[msg.sender], "Caller is not a factory contract");
_;
}
/**
* @notice Adds a new address to the list of factory contracts allowed to interact with certain functions.
* @dev Marks an address as a factory contract, enabling it to call functions protected by the `onlyFactoryContracts` modifier. Only callable by the contract owner.
* @param _factoryAddress The address of the factory contract to be added.
* Emits a `FactoryContractAdded` event upon successfully adding a new factory contract address.
*/
function addFactoryContract(address _factoryAddress) public onlyOwner {
factoryContracts[_factoryAddress] = true;
emit FactoryContractAdded(_factoryAddress);
}
/**
* @notice Removes an address from the list of factory contracts.
* @dev Deletes an address from the set of addresses marked as factory contracts, effectively revoking its ability to call functions protected by the `onlyFactoryContracts` modifier. Only callable by the contract owner.
* @param _factoryAddress The address of the factory contract to be removed.
* Emits a `FactoryContractRemoved` event upon successfully removing a factory contract address.
*/
function removeFactoryContract(address _factoryAddress) public onlyOwner {
delete factoryContracts[_factoryAddress];
emit FactoryContractRemoved(_factoryAddress);
}
/**
* @notice Adds a token to the whitelist, allowing it to be used for external airdrops.
* @dev Only the contract owner can add tokens to the whitelist. This control mechanism ensures that only tokens vetted and approved by the contract's administrators can participate in external airdrop processes. The function checks for the zero address to prevent invalid entries.
* @param _token The address of the token to be added to the whitelist.
* Requires that the provided token address is not the zero address, ensuring that only valid tokens are whitelisted.
* Sets the token's status in the `whitelistedTokens` mapping to `true`, effectively adding it to the whitelist.
*/
function addTokenToWhitelist(address _token) external onlyOwner {
require(_token != address(0), "Invalid token address");
whitelistedTokens[_token] = true;
}
/**
* @notice Removes a token from the whitelist, preventing it from being used for future external airdrops.
* @dev Only the contract owner can remove tokens from the whitelist, providing a mechanism for maintaining control over which tokens are approved for airdrops. This function ensures that tokens can be dynamically managed based on ongoing assessments and criteria.
* @param _token The address of the token to be removed from the whitelist.
* Requires that the token is currently whitelisted, ensuring that only previously approved tokens can be removed from the whitelist.
* Deletes the token's entry from the `whitelistedTokens` mapping, effectively removing it from the whitelist.
*/
function removeTokenFromWhitelist(address _token) external onlyOwner {
require(whitelistedTokens[_token], "Token not in whitelist");
delete whitelistedTokens[_token];
}
/**
* @notice Updates the beneficiary address where a portion of the fees or other designated funds may be sent.
* @dev Sets a new beneficiary address, ensuring it is not the zero address. Only callable by the contract owner.
* @param _newBeneficiaryAddress The new address to be set as the beneficiary.
* Requires that the new beneficiary address is not the zero address.
* Emits a `BeneficiaryAddressUpdated` event upon successfully updating the beneficiary address.
*/
function setBeneficiaryAddress(address _newBeneficiaryAddress) external onlyOwner {
require(_newBeneficiaryAddress != address(0), "Invalid address: cannot be the zero address");
beneficiaryAddress = _newBeneficiaryAddress;
emit BeneficiaryAddressUpdated(beneficiaryAddress);
}
/**
* @notice Updates the liquidity contract address which may be used for liquidity-related operations.
* @dev Sets a new liquidity contract address, ensuring it is not the zero address. Only callable by the contract owner. This address can be used for operations related to managing liquidity, such as adding liquidity to a pool.
* @param _newLiquidityContractAddress The new address to be set as the liquidity contract.
* Requires that the new liquidity contract address is not the zero address.
* Emits a `LiquidityContractAddressUpdated` event upon successfully updating the liquidity contract address.
*/
function setLiquidityContractAddress(address _newLiquidityContractAddress) external onlyOwner {
require(_newLiquidityContractAddress != address(0), "Invalid address: cannot be the zero address");
liquidityContractAddress = _newLiquidityContractAddress;
emit LiquidityContractAddressUpdated(_newLiquidityContractAddress);
}
/**
* @notice Updates the fee percentage charged when unstaking tokens early.
* @dev Sets a new unstake fee percentage, ensuring it is within the range 0% to 3.69%. Only callable by the contract owner.
* @param _newUnstakeFee The new unstake fee percentage to be set (in basis points).
* Requires that the new unstake fee is not negative and does not exceed 3.69%.
* Emits an `UnstakeFeeUpdated` event upon successfully updating the unstake fee.
*/
function setUnstakeFee(uint256 _newUnstakeFee) external onlyOwner {
require(_newUnstakeFee >= 0, "Unstake fee must not be negative");
require(_newUnstakeFee <= 369, "Unstake fee must not exceed 3.69%");
unstakeFee = _newUnstakeFee;
emit UnstakeFeeUpdated(unstakeFee);
}
/**
* @notice Updates the percentage of certain funds allocated for liquidity provision.
* @dev Sets a new liquidity percentage, ensuring it falls within the valid range of 0% to 100%. Only callable by the contract owner.
* @param _liquidityPercentage The new percentage of funds to be allocated towards liquidity provision.
* Requires that the new liquidity percentage is between 0% and 100%.
* Emits a `LiquidityPercentageUpdated` event upon successfully updating the liquidity percentage.
*/
function setLiquidityPercentage(uint256 _liquidityPercentage) external onlyOwner {
require(_liquidityPercentage >= 0 && _liquidityPercentage <= 100, "Liquidity percentage must be between 0% and 100%");
liquidityPercentage = _liquidityPercentage;
emit LiquidityPercentageUpdated(_liquidityPercentage);
}
/**
* @notice Updates the percentage of certain funds allocated to the beneficiary.
* @dev Sets a new beneficiary percentage, ensuring it falls within the valid range of 0% to 100%. Only callable by the contract owner.
* @param _beneficiaryPercentage The new percentage of funds to be allocated to the beneficiary.
* Requires that the new beneficiary percentage is between 0% and 100%.
* Emits a `BeneficiaryPercentageUpdated` event upon successfully updating the beneficiary percentage.
*/
function setBeneficiaryPercentage(uint256 _beneficiaryPercentage) external onlyOwner {
require(_beneficiaryPercentage >= 0 && _beneficiaryPercentage <= 100, "Beneficiary Percentage must be between 0% and 100%");
beneficiaryPercentage = _beneficiaryPercentage;
emit BeneficiaryPercentageUpdated(_beneficiaryPercentage);
}
/**
* @notice Allows a user to stake a specified amount of tokens into the contract, updating both the regular and detailed stake records.
* @dev Transfers the specified amount of tokens from the caller to the contract. This updates the caller's regular stake record and creates a detailed stake record with a unique ID for the action. It sets the stake's start block number for new stakes, increments the total number of stakers if it's a new staker, and updates the total staked amount. The function emits a `Staked` event upon successful staking. It ensures detailed tracking of each staking action for enhanced functionality such as snapshot-based airdrops.
* @param _amount The amount of tokens to be staked by the caller.
* Requires that the staking amount is greater than 0 and that the stake is not made from the zero address.
* For the caller's initial stake, sets the `stakeBlockNumber` to the current block number for the regular stake and increments `totalStakersCount`.
* Adds the staked amount to the caller's current stake and creates/updates detailed stake records.
* Emits a `Staked` event with the caller's address and the amount staked.
*/
function stake(uint256 _amount) public nonReentrant {
require(_amount > 0, "Amount must be greater than 0");
require(msg.sender != address(0), "Stake from the zero address");
// Check that the total staked plus the new amount does not exceed the total supply
require(totalStaked + _amount <= PulseCoin_TOTAL_SUPPLY, "Staking exceeds total supply");
PulseCoinToken.safeTransferFrom(msg.sender, address(this), _amount);
// Regular stake logic
if (stakes[msg.sender].amount == 0 && _amount > 0) {
stakes[msg.sender].stakeBlockNumber = block.number;
totalStakersCount++; // Update totalStakersCount if this is a new staker
}
stakes[msg.sender].amount += _amount;
// Create and record a new StakeDetail
uint256 newStakeID = ++stakeIDCounter;
stakeDetails[newStakeID] = StakeDetail({
stakeID: newStakeID,
staker: msg.sender,
amount: _amount,
stakeBlockNumber: block.number,
isActive: true
});
userStakeIDs[msg.sender].push(newStakeID);
totalStaked += _amount;
emit Staked(msg.sender, _amount, newStakeID);
}
/**
* This function has been edited after audit to correctly calculate the vesting completion block number.
* Audited code uses the userStake.StakeBlockNumber, while the code below uses airdropBlockNumber + vestingDuration.
* which is the intended behavior.
* @notice Checks if the vesting period for a user's stake has been completed for a specific airdrop.
* @dev Compares the current block number against the calculated vesting completion block number, derived from the airdrop's block number plus the vesting duration, to determine if the vesting is complete. Requires that the airdrop ID is valid.
* @param _user The address of the user whose vesting completion status is being checked.
* @param _airdropID The ID of the airdrop for which the vesting completion is being checked.
* @return complete Boolean indicating if the vesting period is complete.
* @return vestingCompletionBlock The block number at which the user's vesting period for the specified airdrop is complete.
*/
function isVestingComplete(address _user, uint256 _airdropID) public view returns (bool, uint256) {
require(_airdropID < airdrops.length, "Invalid airdrop ID");
Stake memory userStake = stakes[_user];
Airdrop storage airdrop = airdrops[_airdropID];
uint256 vestingDuration = calculateVestingDuration(userStake.amount);
uint256 vestingCompletionBlock = airdrop.airdropBlockNumber + vestingDuration;
return (block.number >= vestingCompletionBlock, vestingCompletionBlock);
}
/**
* @notice Calculates the vesting duration for a given stake amount.
* @dev Determines the vesting duration based on the size of the stake. Stakes up to 1000 ether have no vesting period, stakes between 1000 ether and 1 million ether have a vesting period that scales linearly up to 90 days, and stakes larger than 1 million ether have a fixed 90-day vesting period.
* @param _stakeAmount The amount of the stake for which to calculate the vesting duration.
* @return The vesting duration in blocks. For stakes up to 1000 ether, returns 0. For stakes between 1000 ether and 1 million ether, returns a scaled duration up to the equivalent of 90 days in blocks. For stakes over 1 million ether, returns the equivalent of 90 days in blocks.
*/
function calculateVestingDuration(uint256 _stakeAmount) public pure returns (uint256) {
if (_stakeAmount <= 1000 ether) {
return 0;
} else if (_stakeAmount <= 1e6 ether) {
uint256 daysCalc = (_stakeAmount - 1000 ether) * 90 / (1e6 ether - 1000 ether);
return daysCalc * 24 * 60 * 60 / 10; // Convert days to blocks, assuming an average block time
} else {
return 90 * 24 * 60 * 60 / 10; // 90 days in blocks, assuming an average block time
}
}
/**
* @notice Calculates the fee required to skip the vesting period for a user's staked tokens.
* @dev Determines the skip fee based on the user's staked duration relative to their total vesting duration. The initial fee is set at 3.69%, and decreases linearly based on how much of the vesting period has passed. If the entire vesting period has elapsed, no fee is applied. This method encourages users to fulfill their vesting period but offers flexibility for early withdrawal at a cost.
* @param _user The address of the user for whom the skip fee is being calculated.
* @return The calculated fee amount in tokens that the user must pay to skip the remainder of their vesting period. Returns 0 if the vesting period has already concluded.
* The skip fee is initially set at 3.69% of the staked amount and decreases linearly with time, proportionate to the duration staked versus the total vesting duration.
* If the user has already met or exceeded the vesting duration, the function returns 0, indicating no skip fee is necessary.
*/
function calculateSkipFee(address _user) public view returns (uint256) {
Stake memory userStake = stakes[_user];
uint256 initialFeePercentage = 369; // 3.69%
uint256 vestingDuration = calculateVestingDuration(userStake.amount);
uint256 stakedDuration = block.number - userStake.stakeBlockNumber;
if (stakedDuration >= vestingDuration) {
return 0; // No fee if vesting period is over
}
// Calculate discounted fee based on staked duration
uint256 discount = stakedDuration * initialFeePercentage / vestingDuration;
uint256 feePercentage = initialFeePercentage - discount;
return userStake.amount * feePercentage / 10000;
}
/**
* @notice Allows a user to unstake their staked tokens, applying an unstake fee penalty.
* @dev Unstakes the caller's entire token stake, subtracting an unstake fee defined by `unstakeFee` and transferring the remaining balance back to the caller. Also sets all associated StakeDetail entries as inactive. The unstake fee is burned, effectively removing it from circulation. This action reduces the total staked amount and decrements the count of total stakers. It emits an `Unstaked` event upon successful completion.
* Requires that the caller has an existing stake with a non-zero amount.
* Calculates the unstake penalty based on the `unstakeFee` percentage of the staked amount.
* Burns the penalty amount and transfers the remaining staked amount back to the caller.
* Sets all StakeDetail entries for the caller as inactive, ensuring they're not considered for future rewards.
* Resets the caller's stake amount to 0 and decrements the total stakers count.
* Emits an `Unstaked` event with the caller's address and the amount returned after applying the unstake penalty.
*/
function unstake() public nonReentrant {
Stake storage userStake = stakes[msg.sender];
require(userStake.amount > 0, "No stake found");
require(msg.sender != address(0), "Unstake from the zero address");
uint256 penalty = userStake.amount * unstakeFee / 10000; // Calculate unstake penalty
uint256 returnAmount = userStake.amount - penalty;
burn(penalty);
// Iterate over all stakeIDs for the caller and set them as inactive
for (uint256 i = 0; i < userStakeIDs[msg.sender].length; i++) {
uint256 stakeID = userStakeIDs[msg.sender][i];
StakeDetail storage detail = stakeDetails[stakeID];
detail.isActive = false;
}
totalStaked -= userStake.amount;
userStake.amount = 0;
if (userStake.amount == 0) {
totalStakersCount -= 1;
}
PulseCoinToken.safeTransfer(msg.sender, returnAmount);
emit Unstaked(msg.sender, returnAmount);
}
/**
* @notice Searches for the first airdrop that occurred on or after a specified block number.
* @dev Iterates through the list of airdrops recorded in the contract to find the first airdrop whose block number is greater than or equal to the given block number. This function is useful for identifying airdrops within a specific timeframe. If no airdrop is found that meets the criteria, the function returns the total number of airdrops, effectively indicating an out-of-bounds index.
* @param blockNumber The block number from which to start the search for subsequent airdrops.
* @return The index of the first airdrop occurring on or after the specified block number. If no such airdrop is found, returns the length of the airdrops array.
*/
function findFirstAirdropAfterBlock(uint256 blockNumber) public view returns (uint256) {
for (uint256 i = 0; i < airdrops.length; i++) {
if (airdrops[i].airdropBlockNumber >= blockNumber) {
return i;
}
}
return airdrops.length; // Indicate no airdrop found after the specified block by returning the array length
}
// Function to return the total number of airdrops
function getTotalAirdrops() public view returns (uint256) {
return airdrops.length;
}
function numberOfStakers() public view returns (uint256) {
return totalStakersCount;
}
/**
* @notice Receives tokens for an internal airdrop, with an option to automatically create a liquidity pair using a percentage of the airdropped tokens.
* @dev Accepts a token and an amount for airdrop, with the option to use a portion of the airdropped tokens to create a liquidity pair with PLS. Validates the token is not the staking token, checks for sufficient PLS if LP creation is requested, and ensures the LP percentage is within valid bounds. It then records the airdrop details, including whether it's external and the address of any created LP pair. Emits an `AirdropTokenReceived` event upon completion.
* @param _token The address of the token being airdropped.
* @param _amount The amount of the token being airdropped.
* @param createLP A boolean indicating whether part of the airdropped tokens should be used to create a liquidity pair with PLS.
* @param lpPercentage The percentage of the received airdrop amount to be used for liquidity provision, if `createLP` is true.
* Requires the airdropped token to not be the staking token.
* Requires that if `createLP` is true, sufficient PLS is sent with the call to cover the liquidity provision.
* The `lpPercentage` must be between 1 and 99, inclusive.
* Calculates the actual received amount of airdropped tokens, accounting for any potential transfer fees or taxes.
* Optionally creates a liquidity pair with the specified percentage of the airdropped tokens and PLS sent with the transaction, adjusting the liquidity provision balance based on the maximum allowed.
* Records the airdrop as internal, with details of the received amount, total staked at snapshot, block number, and LP pair address if created.
* Emits `AirdropTokenReceived` with the token address, received amount, and airdrop ID for tracking and verification.
*/
function receiveAirdropTokens(
address _token,
uint256 _amount,
bool createLP,
uint8 lpPercentage, //Parameter for liquidity provision percentage
bool useSnapshot // Use snapshot or flat rate
) public payable onlyFactoryContracts {
require(_token != address(PulseCoinToken), "Staking token cannot be airdropped");
require(!createLP || msg.value > 0, "Insufficient PLS for LP creation");
require(lpPercentage >= 1 && lpPercentage <= 99, "LP percentage must be between 1% and 99%");
IERC20 airdropToken = IERC20(_token);
// Store the initial balance of the airdrop token
uint256 initialBalance = airdropToken.balanceOf(address(this));
airdropToken.safeTransferFrom(msg.sender, address(this), _amount);
// Calculate the actual received amount, considering potential token tax
uint256 receivedAmount = airdropToken.balanceOf(address(this)) - initialBalance;
address lpPairAddress; // Variable to store LP pair address
uint256 lpTokenAmount = (receivedAmount * lpPercentage) / 100; // Calculate LP token amount based on percentage
// Use the burntSupplyPercentage variable in the calculation
//uint256 maxLiquidityProvision = (receivedAmount * burntSupplyPercentage) / 100;
if (createLP && lpTokenAmount > 0) {
lpPairAddress = _createLiquidityPair(_token, lpTokenAmount);
// Update liquidityProvisionBalance considering the maxLiquidityProvision
//liquidityProvisionBalance[_token] += lpTokenAmount > maxLiquidityProvision ? maxLiquidityProvision : lpTokenAmount;
}
uint256 transferAmount = 0;
if (!useSnapshot) {
transferAmount = (receivedAmount * burntSupplyPercentage) / 100;
airdropToken.safeTransfer(liquidityContractAddress, transferAmount);
}
uint256 remainingBalance = receivedAmount - lpTokenAmount - transferAmount;
Airdrop storage newAirdrop = airdrops.push();
newAirdrop.airdropToken = airdropToken;
newAirdrop.airdropAmount = receivedAmount;
newAirdrop.snapshotTotalStaked = totalStaked;
newAirdrop.airdropBlockNumber = block.number;
newAirdrop.pCCreatedLPAddress = lpPairAddress;
newAirdrop.isExternal = false;
newAirdrop.msgSender = msg.sender;
newAirdrop.useSnapshot = useSnapshot;
newAirdrop.remainingBalance = remainingBalance;
uint256 airdropID = airdrops.length - 1;
emit AirdropTokenReceived(_token, receivedAmount, airdropID);
}
/**
* @notice Receives tokens for an external airdrop, records the airdrop details, and requires that the token is whitelisted.
* @dev This function allows the contract to accept tokens intended for an external airdrop, recording the relevant details. It ensures the token is not the staking token itself and checks that the token is on a whitelist, adding an additional layer of security by allowing only approved tokens for airdrops. Upon successful receipt, the function records the airdrop as external without creating an LP pair address, and emits an `AirdropTokenReceived` event.
* @param _token The address of the token being airdropped. Must be a whitelisted token.
* @param _amount The amount of tokens being airdropped.
* @param useSnapshot A boolean indicating whether to use a snapshot for distribution calculations. This parameter allows the contract to flexibly handle different types of airdrop distributions.
* Requires that the airdropped token is not the staking token to prevent self-airdropping.
* Requires that the airdropped token is whitelisted, ensuring only approved tokens can be airdropped, adding a layer of control and security.
* Calculates the actual amount received to account for any potential transfer fees or taxes imposed by the token's contract, ensuring accurate recording of the airdropped amount.
* Records the airdrop details, including whether it's based on a snapshot of staked amounts at the time of the airdrop, and marks it as external. The absence of an LP pair address in the record indicates that this airdrop does not directly interact with liquidity pool mechanisms.
* Emits an `AirdropTokenReceived` event with the token address, the amount received, and the unique ID of the airdrop for tracking and verification purposes.
*/
function receiveExternalAirdropTokens(
address _token,
uint256 _amount,
bool useSnapshot
) public nonReentrant {
require(_token != address(PulseCoinToken), "Staking token cannot be airdropped");
require(whitelistedTokens[_token], "Token not whitelisted"); // Check if the token is whitelisted
IERC20 airdropToken = IERC20(_token);
uint256 initialBalance = airdropToken.balanceOf(address(this));
airdropToken.safeTransferFrom(msg.sender, address(this), _amount);
uint256 receivedAmount = airdropToken.balanceOf(address(this)) - initialBalance;
Airdrop storage newAirdrop = airdrops.push();
newAirdrop.airdropToken = airdropToken;
newAirdrop.airdropAmount = receivedAmount;
newAirdrop.snapshotTotalStaked = totalStaked;
newAirdrop.airdropBlockNumber = block.number;
newAirdrop.pCCreatedLPAddress = address(0); // No LP pair address
newAirdrop.isExternal = true; // Mark as external
newAirdrop.msgSender = msg.sender;
newAirdrop.useSnapshot = useSnapshot;
newAirdrop.remainingBalance = receivedAmount;
if (!useSnapshot) {
uint256 transferAmount = (receivedAmount * burntSupplyPercentage) / 100;
airdropToken.safeTransfer(liquidityContractAddress, transferAmount);
}
uint256 airdropID = airdrops.length - 1;
emit AirdropTokenReceived(_token, receivedAmount, airdropID);
}
/**
* @notice Creates a liquidity pair for the specified token and PLS, and then adds liquidity to it with a specified token amount and PLS amount sent in the transaction.
* @dev Approves the PulseX Router to spend the specified amount of the given token, then adds liquidity with a 5% slippage tolerance on both the token and PLS amounts. The liquidity tokens received are then burned by sending them to a predefined burn address, effectively removing them from circulation. This function emits a `LiquidityAdded` event upon successful addition of liquidity. The LP pair address is returned for reference.
* @param _token The address of the token for which liquidity is being added alongside PLS.
* @param _tokenAmount The amount of the specified token to add as liquidity.
* @return lpPairAddress The address of the liquidity pool (LP) pair created between the specified token and PLS.
* Emits a `LiquidityAdded` event indicating the amounts of the token and PLS added to liquidity, along with the liquidity amount.
* Requires that the transaction's msg.value is the PLS amount intended for liquidity addition.
* The function calculates minimum amounts for the token and PLS based on a 5% slippage tolerance and uses these in the liquidity addition call.
* After adding liquidity, the function retrieves the LP pair address and burns the received liquidity tokens by transferring them to the burn address.
*/
function _createLiquidityPair(address _token, uint256 _tokenAmount) private returns (address) {
IERC20(_token).approve(pulseXRouterAddress, _tokenAmount);
// Calculate minimum amounts with a 5% slippage tolerance
uint256 amountTokenMin = _tokenAmount * 95 / 100; // 5% less than the desired amount
uint256 amountETHMin = msg.value * 95 / 100; // 5% less than the provided ETH (PLS) amount
// Add liquidity using PLS and the airdropped token
(uint amountToken, uint amountETH, uint liquidity) = pulseXRouter.addLiquidityETH{value: msg.value}(
_token,
_tokenAmount,
amountTokenMin,
amountETHMin,
address(this),
block.timestamp
);
emit LiquidityAdded(amountToken, amountETH, liquidity);
// Retrieve LP pair address using the factory and the getPair method
address lpPairAddress = IPulseXFactory(pulseXRouter.factory()).getPair(_token, pulseXRouter.WPLS());
// Burn the LP tokens by safely transferring them to the burn address
IERC20(lpPairAddress).safeTransfer(burnAddress, liquidity);
emit LPTokensBurned(lpPairAddress, liquidity, burnAddress);
return lpPairAddress; // Return the LP pair address
}
/**
* @notice Claims the specified airdrop amount for the caller, with options to skip vesting for a fee and to direct the airdrop to a different receiver address.
* @dev Allows a user with an active stake and who is eligible for the airdrop to claim their share. The claim calculation differs based on whether the airdrop uses snapshot data (`useSnapshot` flag). If `skipVesting` is true, a fee is charged and immediately burned to allow immediate access to the airdrop tokens without waiting for vesting. Emits a `Claimed` event upon successful claim.
* @param _airdropID The ID of the airdrop being claimed.
* @param skipVesting A boolean indicating whether the caller wishes to skip the vesting period for an additional fee.
* @param _receiver The address that will receive the airdropped tokens. If set to the zero address, the tokens are sent to the caller.
* Requires:
* - `_airdropID` must be valid and correspond to an existing airdrop.
* - The caller must have an active stake at the time of the airdrop distribution if not using a snapshot, or at the airdrop block number if using a snapshot.
* - The claim must be made within the claim period defined by `CLAIM_PERIOD_IN_BLOCKS` from the airdrop's block number.
* - The airdrop must not have been previously claimed by the caller.
* - If `skipVesting` is true, the fee transfer must be successful, and the fee is burned.
* - If `skipVesting` is false, the caller's vesting period must be complete.
*
* The function calculates the user's share of the airdrop in two ways:
* - If `useSnapshot` is false, it uses the user's current stake relative to the total PulseCoin token supply.
* - If `useSnapshot` is true, it calculates the share based on the user's stake at the airdrop's block number relative to the snapshot total staked.
*
* The calculated share must be non-zero. Marks the airdrop as claimed for the caller to prevent duplicate claims and transfers the calculated share to the specified receiver address, or to the caller's address if no receiver is specified.
*/
function claimAirdrop(uint256 _airdropID, bool skipVesting, address _receiver) public nonReentrant {
require(_airdropID < airdrops.length, "Invalid airdrop ID");
require(msg.sender != address(0), "Claim from the zero address");
Airdrop storage airdrop = airdrops[_airdropID];
Stake storage userStake = stakes[msg.sender];
require(block.number <= airdrop.airdropBlockNumber + CLAIM_PERIOD_IN_BLOCKS, "Claim period has ended");
require(userStake.amount > 0, "No active stake");
require(!airdrop.claimed[msg.sender], "Airdrop already claimed");
require(airdrop.airdropAmount > 0, "Invalid airdrop amount");
if (skipVesting) {
uint256 fee = calculateSkipFee(msg.sender);
require(PulseCoinToken.transferFrom(msg.sender, address(this), fee), "Fee transfer failed");
burn(fee);
} else {
(bool vestingComplete, ) = isVestingComplete(msg.sender, _airdropID);
require(vestingComplete, "Vesting not complete");
}
uint256 userShare;
if (airdrop.useSnapshot) {
// Calculate share based on the snapshot
uint256 userStakeAtSnapshot = getUserStakeAtBlock(msg.sender, airdrop.airdropBlockNumber);
require(userStakeAtSnapshot > 0, "No stake at snapshot");
userShare = (airdrop.airdropAmount * userStakeAtSnapshot) / airdrop.snapshotTotalStaked;
} else {
// Use existing logic for share calculation
userShare = (airdrop.airdropAmount * userStake.amount) / PulseCoin_TOTAL_SUPPLY;
}
require(userShare > 0, "User share is zero");
airdrop.claimed[msg.sender] = true;
airdrop.remainingBalance -= userShare; // Updates remainingBalance
address receiver = _receiver == address(0) ? msg.sender : _receiver;
SafeERC20.safeTransfer(airdrop.airdropToken, receiver, userShare);
emit Claimed(msg.sender, _airdropID);
}
/**
* @dev Calculates the total amount staked by a user up to a specific block number,
* based on the detailed stake records. This function iterates through all stake
* actions recorded in `StakeDetail` for the given user, summing up the amounts of
* those that were active (not yet unstaked) at or before the specified block number.
* It ensures that the sum of detailed stake records does not exceed the total stake
* amount recorded in the aggregated `Stake` struct for the user, maintaining data
* consistency within the contract.
*
* @notice This function is used internally to determine a user's staked amount at a
* specific block, primarily for snapshot-based calculations such as airdrop distributions.
*
* @param user The address of the user whose stake is being queried.
* @param blockNumber The block number at which to calculate the user's total stake.
* The calculation includes all stakes active at or before this block.
*
* @return stakeAmountFromDetails The total amount staked by the user at the specified
* block number, according to detailed `StakeDetail` records. If no stakes are active
* or if the user has no stakes, this will be 0.
*
* Requirements:
* - The function only includes stakes that were active (not unstaked) at or before
* the specified block number.
* - The sum of detailed stakes must not exceed the user's total stake amount as
* recorded in the master `Stake` struct, ensuring consistency between detailed
* records and aggregated stake data.
*/
function getUserStakeAtBlock(address user, uint256 blockNumber) private view returns (uint256) {
uint256 stakeAmountFromDetails = 0;
for (uint256 i = 0; i < userStakeIDs[user].length; i++) {
StakeDetail storage detail = stakeDetails[userStakeIDs[user][i]];
if (detail.stakeBlockNumber <= blockNumber && detail.isActive) {
stakeAmountFromDetails += detail.amount;
}
}
// Ensure the calculated amount does not exceed the "master" stake amount.
require(stakes[user].amount >= stakeAmountFromDetails, "Stake details exceed total stake");
return stakeAmountFromDetails;
}
/**
* @notice Checks whether a specific airdrop has been claimed by a given user address.
* @dev Retrieves the airdrop identified by `_airdropID` and checks the `claimed` mapping for the given `_user` address. This function ensures that only valid airdrop IDs are queried by requiring that the `_airdropID` is less than the total number of airdrops.
* @param _user The address of the user to check for airdrop claim status.
* @param _airdropID The ID of the airdrop to check for claim status.
* @return claimedStatus True if the airdrop has been claimed by the given user, false otherwise.
* Requires that the provided `_airdropID` corresponds to an existing airdrop.
*/
function isAirdropClaimed(address _user, uint256 _airdropID) public view returns (bool claimedStatus) {
require(_airdropID < airdrops.length, "Invalid airdrop ID");
Airdrop storage airdrop = airdrops[_airdropID];
return airdrop.claimed[_user];
}
/**
* @notice Redistributes unclaimed tokens from a specified airdrop after the claim period has ended.
* @dev After the claim period for an airdrop has ended, this function is called to redistribute unclaimed tokens. It rewards the caller for triggering the redistribution, supports designated beneficiaries, enhances liquidity by sending a portion to a liquidity contract, and burns the remaining tokens.
* @param _airdropID The ID of the airdrop whose unclaimed tokens are to be redistributed.
* Requires:
* - The claim period for the specified airdrop must have concluded.
* - The airdrop must not have been previously redistributed.
*
* The function executes the following steps:
* 1. Calculates and caps the caller's reward based on the average stake.
* 2. Transfers the calculated reward to the caller.
* 3. Allocates and transfers a specified percentage of the remaining unclaimed amount to the beneficiary address.
* 4. Allocates and transfers another portion for liquidity purposes to the liquidity contract address.
* 5. Burns any tokens still remaining after these distributions by transferring them to the burn address.
*
* Marks the airdrop as redistributed to prevent duplicate redistribution actions.
*/
function redistributeAirdrop(uint256 _airdropID) public nonReentrant {
Airdrop storage airdrop = airdrops[_airdropID];
uint256 claimPeriodEndBlock = airdrop.airdropBlockNumber + CLAIM_PERIOD_IN_BLOCKS;
require(block.number >= claimPeriodEndBlock, "Claim period not over");
require(!airdrop.redistributed, "Airdrop already redistributed");
uint256 unclaimedAmount = calculateUnclaimedAmount(_airdropID);
if (unclaimedAmount == 0) {
airdrop.redistributed = true;
return; // Early exit if no tokens are available for redistribution
}
// Calculate reward for the caller based on the average stake
uint256 totalStakers = numberOfStakers(); // Implementation required
uint256 averageStake = totalStaked / totalStakers;
uint256 callerReward = (unclaimedAmount * averageStake) / PulseCoin_TOTAL_SUPPLY;
callerReward = callerReward > unclaimedAmount ? unclaimedAmount : callerReward; // Cap caller reward
// Transfer caller reward
if (callerReward > 0) {
SafeERC20.safeTransfer(airdrop.airdropToken, msg.sender, callerReward);
unclaimedAmount -= callerReward;
}
// Deduct and transfer beneficiary amount
uint256 beneficiaryAmount = (unclaimedAmount * beneficiaryPercentage) / 10000;
if (beneficiaryAmount > 0) {
SafeERC20.safeTransfer(airdrop.airdropToken, beneficiaryAddress, beneficiaryAmount);
unclaimedAmount -= beneficiaryAmount;
}
// Deduct and account for liquidity amount
uint256 liquidityAmount = (unclaimedAmount * liquidityPercentage) / 100;
if (liquidityAmount > 0) {
SafeERC20.safeTransfer(airdrop.airdropToken, liquidityContractAddress, liquidityAmount);
unclaimedAmount -= liquidityAmount;
}
// Burn the remaining unclaimed amount
if (unclaimedAmount > 0) {
SafeERC20.safeTransfer(airdrop.airdropToken, burnAddress, unclaimedAmount);
}
airdrop.redistributed = true;
}
/**
* @notice Calculates the unclaimed amount of tokens for a specific airdrop, accounting for external adjustments like tax tokens.
* @dev Retrieves the airdrop details using the provided _airdropID and calculates the unclaimed amount based on the lower of the recorded remainingBalance and the actual token balance in the contract. This accounts for scenarios where external factors like tax tokens may affect the balance.
* @param _airdropID The ID of the airdrop for which to calculate the unclaimed amount.
* @return The amount of tokens from the specified airdrop that have not been claimed, adjusted for actual token balances.
*/
function calculateUnclaimedAmount(uint256 _airdropID) public view returns (uint256) {
require(_airdropID < airdrops.length, "Invalid airdrop ID"); // Ensures valid airdrop ID
Airdrop storage airdrop = airdrops[_airdropID];
// Get the actual current balance of the airdrop token in the contract
uint256 actualBalance = airdrop.airdropToken.balanceOf(address(this));
// Use the lesser of the stored remainingBalance or the actual balance to account for discrepancies
uint256 unclaimedAmount = airdrop.remainingBalance < actualBalance ? airdrop.remainingBalance : actualBalance;
return unclaimedAmount;
}
/**
* @notice Retrieves the IDs of all airdrops associated with a given token address.
* @dev Iterates through all airdrops to find those matching the specified token address and compiles their IDs into an array.
* @param _token The address of the token for which to find associated airdrop IDs.
* @return airdropIDs An array of airdrop IDs for the given token.
*/
function getAirdropsForToken(address _token) public view returns (uint256[] memory) {
uint256 count = 0;
for (uint256 i = 0; i < airdrops.length; i++) {
if (address(airdrops[i].airdropToken) == _token) {
count++;
}
}
uint256[] memory airdropIDs = new uint256[](count);
uint256 index = 0;
for (uint256 i = 0; i < airdrops.length; i++) {
if (address(airdrops[i].airdropToken) == _token) {
airdropIDs[index] = i;
index++;
}
}
return airdropIDs;
}
// Function to get all stake IDs for a given user address
function getUserStakeIDs(address _user) public view returns (uint256[] memory) {
return userStakeIDs[_user];
}
/**
* @notice Burns a specified amount of PulseCoinToken by sending it to a burn address.
* @dev Transfers the specified amount of PulseCoinToken to a predefined burn address. Used to permanently remove tokens from circulation.
* @param _amount The amount of PulseCoinToken to burn.
* Requires the transfer to the burn address to succeed.
*/
function burn(uint256 _amount) private {
require(PulseCoinToken.transfer(burnAddress, _amount), "Burn transfer failed");
}
}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_pulseXFactoryAddress","internalType":"address"},{"type":"address","name":"_WPLSContractAddress","internalType":"address"},{"type":"address","name":"_pulseXRouterAddress","internalType":"address"},{"type":"address","name":"_stakingTokenAddress","internalType":"address"}]},{"type":"error","name":"AddressEmptyCode","inputs":[{"type":"address","name":"target","internalType":"address"}]},{"type":"error","name":"AddressInsufficientBalance","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"error","name":"FailedInnerCall","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"error","name":"SafeERC20FailedOperation","inputs":[{"type":"address","name":"token","internalType":"address"}]},{"type":"event","name":"AirdropTokenReceived","inputs":[{"type":"address","name":"token","internalType":"address","indexed":false},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"uint256","name":"airdropID","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"BeneficiaryAddressUpdated","inputs":[{"type":"address","name":"newBeneficiaryAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"BeneficiaryPercentageUpdated","inputs":[{"type":"uint256","name":"newBeneficiaryPercentage","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Claimed","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"airdropID","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FactoryContractAdded","inputs":[{"type":"address","name":"factoryAddress","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"FactoryContractRemoved","inputs":[{"type":"address","name":"factoryAddress","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"LPTokensBurned","inputs":[{"type":"address","name":"pair","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"address","name":"burnAddress","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"LiquidityAdded","inputs":[{"type":"uint256","name":"amountToken","internalType":"uint256","indexed":false},{"type":"uint256","name":"amountETH","internalType":"uint256","indexed":false},{"type":"uint256","name":"liquidity","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"LiquidityContractAddressUpdated","inputs":[{"type":"address","name":"newLiquidityContractAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"LiquidityPairCreated","inputs":[{"type":"address","name":"token","internalType":"address","indexed":true},{"type":"address","name":"wpls","internalType":"address","indexed":true},{"type":"address","name":"pair","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"LiquidityPercentageUpdated","inputs":[{"type":"uint256","name":"newLiquidityPercentage","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"Staked","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"uint256","name":"stakeID","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"UnstakeFeeUpdated","inputs":[{"type":"uint256","name":"unstakeFee","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Unstaked","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":"CLAIM_PERIOD_IN_BLOCKS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IERC20"}],"name":"PulseCoinToken","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PulseCoin_TOTAL_SUPPLY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"WPLSContractAddress","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addFactoryContract","inputs":[{"type":"address","name":"_factoryAddress","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addTokenToWhitelist","inputs":[{"type":"address","name":"_token","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"airdropToken","internalType":"contract IERC20"},{"type":"uint256","name":"airdropAmount","internalType":"uint256"},{"type":"uint256","name":"snapshotTotalStaked","internalType":"uint256"},{"type":"uint256","name":"airdropBlockNumber","internalType":"uint256"},{"type":"bool","name":"redistributed","internalType":"bool"},{"type":"address","name":"pCCreatedLPAddress","internalType":"address"},{"type":"bool","name":"isExternal","internalType":"bool"},{"type":"address","name":"msgSender","internalType":"address"},{"type":"bool","name":"useSnapshot","internalType":"bool"},{"type":"uint256","name":"remainingBalance","internalType":"uint256"}],"name":"airdrops","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"beneficiaryAddress","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"beneficiaryPercentage","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"burnAddress","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"burntSupplyPercentage","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"calculateSkipFee","inputs":[{"type":"address","name":"_user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"calculateUnclaimedAmount","inputs":[{"type":"uint256","name":"_airdropID","internalType":"uint256"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"calculateVestingDuration","inputs":[{"type":"uint256","name":"_stakeAmount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claimAirdrop","inputs":[{"type":"uint256","name":"_airdropID","internalType":"uint256"},{"type":"bool","name":"skipVesting","internalType":"bool"},{"type":"address","name":"_receiver","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"factoryContracts","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"findFirstAirdropAfterBlock","inputs":[{"type":"uint256","name":"blockNumber","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"","internalType":"uint256[]"}],"name":"getAirdropsForToken","inputs":[{"type":"address","name":"_token","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getTotalAirdrops","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"","internalType":"uint256[]"}],"name":"getUserStakeIDs","inputs":[{"type":"address","name":"_user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"claimedStatus","internalType":"bool"}],"name":"isAirdropClaimed","inputs":[{"type":"address","name":"_user","internalType":"address"},{"type":"uint256","name":"_airdropID","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"},{"type":"uint256","name":"","internalType":"uint256"}],"name":"isVestingComplete","inputs":[{"type":"address","name":"_user","internalType":"address"},{"type":"uint256","name":"_airdropID","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"liquidityContractAddress","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"liquidityPercentage","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"numberOfStakers","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IPulseXFactory"}],"name":"pulseXFactory","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"pulseXFactoryAddress","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"pulseXRouterAddress","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"receiveAirdropTokens","inputs":[{"type":"address","name":"_token","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"},{"type":"bool","name":"createLP","internalType":"bool"},{"type":"uint8","name":"lpPercentage","internalType":"uint8"},{"type":"bool","name":"useSnapshot","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"receiveExternalAirdropTokens","inputs":[{"type":"address","name":"_token","internalType":"address"},{"type":"uint256","name":"_amount","internalType":"uint256"},{"type":"bool","name":"useSnapshot","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"redistributeAirdrop","inputs":[{"type":"uint256","name":"_airdropID","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeFactoryContract","inputs":[{"type":"address","name":"_factoryAddress","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeTokenFromWhitelist","inputs":[{"type":"address","name":"_token","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setBeneficiaryAddress","inputs":[{"type":"address","name":"_newBeneficiaryAddress","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setBeneficiaryPercentage","inputs":[{"type":"uint256","name":"_beneficiaryPercentage","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setLiquidityContractAddress","inputs":[{"type":"address","name":"_newLiquidityContractAddress","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setLiquidityPercentage","inputs":[{"type":"uint256","name":"_liquidityPercentage","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setUnstakeFee","inputs":[{"type":"uint256","name":"_newUnstakeFee","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"stake","inputs":[{"type":"uint256","name":"_amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"stakeID","internalType":"uint256"},{"type":"address","name":"staker","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"stakeBlockNumber","internalType":"uint256"},{"type":"bool","name":"isActive","internalType":"bool"}],"name":"stakeDetails","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"stakeBlockNumber","internalType":"uint256"}],"name":"stakes","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"stakingTokenAddress","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalStaked","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalStakersCount","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"unstake","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"unstakeFee","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"userStakeIDs","inputs":[{"type":"address","name":"","internalType":"address"},{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"whitelistedTokens","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IWPLS"}],"name":"wpls","inputs":[]},{"type":"receive","stateMutability":"payable"}]
Contract Creation Code
0x6080604052600280546001600160a01b031990811661dead179091556011600c8190555f9055600b546016805483166001600160a01b03928316179055600854601780548416918316919091179055600954601880548416918316919091179055600a546019805490931691161790553480156200007b575f80fd5b5060405162003734380380620037348339810160408190526200009e91620001f0565b60015f553380620000c857604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b620000d38162000183565b506006805461dead6001600160a01b031991821681179092556004805482169092179091556101716007819055600a60038190556005919091556008805483166001600160a01b03978816908117909155600980548416968816968717905581548316948716948517909155600b8054831693909616928317909555601680548216909217909155601780548216909417909355601880548416909217909155601980549092161790556200024a565b600180546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b80516001600160a01b0381168114620001eb575f80fd5b919050565b5f805f806080858703121562000204575f80fd5b6200020f85620001d4565b93506200021f60208601620001d4565b92506200022f60408601620001d4565b91506200023f60608601620001d4565b905092959194509250565b6134dc80620002585f395ff3fe6080604052600436106102f4575f3560e01c80638920de5c11610189578063c6de410e116100d8578063d9c4870e11610092578063eba70dff1161006d578063eba70dff146109c9578063ec6be06e146109e8578063ed21d26914610a07578063f2fde38b14610a1c575f80fd5b8063d9c4870e14610967578063daf9c21014610986578063e2c635fa146109b4575f80fd5b8063c6de410e146108a9578063cba1fcea146108d5578063cebe6008146108f4578063cff391f91461090a578063d84c1b3814610929578063d85bcd5f14610948575f80fd5b80639ab7f3ac11610143578063ba6101641161011e578063ba6101641461082d578063bafc73091461084c578063bb52289c1461086b578063c4b003431461088a575f80fd5b80639ab7f3ac14610751578063a694fc3a14610787578063aba8e329146107a6575f80fd5b80638920de5c146106a45780638a7c1d6d146106c25780638da5cb5b146106e15780638ea97d26146106fe5780638f33fe1f14610713578063927ef7fa14610732575f80fd5b80633ec588fc116102455780636bdbfc19116101ff578063715018a6116101da578063715018a61461062e578063742d389e146106425780637693e47914610670578063817b1cd21461068f575f80fd5b80636bdbfc19146105dd5780637097b145146105f057806370d5ae051461060f575f80fd5b80633ec588fc146104b55780634b1f1f4d146104d45780634f01a3a7146104f35780635298b8691461052a5780635eabf5771461054957806360db50821461055d575f80fd5b806317b2a6db116102b057806322971e581161028b57806322971e581461044f578063296e5c12146104635780632def662014610482578063306275be14610496575f80fd5b806317b2a6db146103f25780631a37225a1461041157806321695c7214610430575f80fd5b806218a116146102ff578062eed37b146103205780630230ee011461033f578063048b87ba1461036757806306a55a021461037c57806316934fc4146103ab575f80fd5b366102fb57005b5f80fd5b34801561030a575f80fd5b5061031e6103193660046130fd565b610a3b565b005b34801561032b575f80fd5b5061031e61033a366004613135565b610ae9565b34801561034a575f80fd5b5061035460055481565b6040519081526020015b60405180910390f35b348015610372575f80fd5b5061035460035481565b348015610387575f80fd5b5061039b610396366004613174565b610fba565b604051901515815260200161035e565b3480156103b6575f80fd5b506103dd6103c536600461319e565b600e6020525f90815260409020805460019091015482565b6040805192835260208301919091520161035e565b3480156103fd575f80fd5b5061035461040c3660046130fd565b611026565b34801561041c575f80fd5b5061031e61042b3660046130fd565b6110f9565b34801561043b575f80fd5b5061031e61044a36600461319e565b6111a0565b34801561045a575f80fd5b50601254610354565b34801561046e575f80fd5b5061031e61047d3660046131b9565b6111fb565b34801561048d575f80fd5b5061031e61159b565b3480156104a1575f80fd5b5061031e6104b036600461319e565b611769565b3480156104c0575f80fd5b506103546104cf3660046130fd565b6117f1565b3480156104df575f80fd5b5061031e6104ee3660046130fd565b61183f565b3480156104fe575f80fd5b50601754610512906001600160a01b031681565b6040516001600160a01b03909116815260200161035e565b348015610535575f80fd5b50600b54610512906001600160a01b031681565b348015610554575f80fd5b50600d54610354565b348015610568575f80fd5b5061057c6105773660046130fd565b6118e8565b604080516001600160a01b039b8c168152602081019a909a528901979097526060880195909552921515608087015290861660a0860152151560c085015290931660e08301529115156101008201526101208101919091526101400161035e565b61031e6105eb3660046131ed565b611965565b3480156105fb575f80fd5b50600a54610512906001600160a01b031681565b34801561061a575f80fd5b50600254610512906001600160a01b031681565b348015610639575f80fd5b5061031e611e3f565b34801561064d575f80fd5b5061039b61065c36600461319e565b60146020525f908152604090205460ff1681565b34801561067b575f80fd5b5061035461068a366004613174565b611e50565b34801561069a575f80fd5b5061035460135481565b3480156106af575f80fd5b506103546a115eec47f6cf7e3500000081565b3480156106cd575f80fd5b5061031e6106dc3660046130fd565b611e7b565b3480156106ec575f80fd5b506001546001600160a01b0316610512565b348015610709575f80fd5b5061035460075481565b34801561071e575f80fd5b50600854610512906001600160a01b031681565b34801561073d575f80fd5b50601854610512906001600160a01b031681565b34801561075c575f80fd5b5061077061076b366004613174565b6120d9565b60408051921515835260208301919091520161035e565b348015610792575f80fd5b5061031e6107a13660046130fd565b612186565b3480156107b1575f80fd5b506107fb6107c03660046130fd565b600f6020525f90815260409020805460018201546002830154600384015460049094015492936001600160a01b039092169290919060ff1685565b604080519586526001600160a01b0390941660208601529284019190915260608301521515608082015260a00161035e565b348015610838575f80fd5b50601654610512906001600160a01b031681565b348015610857575f80fd5b5061035461086636600461319e565b61242a565b348015610876575f80fd5b5061031e61088536600461319e565b6124e2565b348015610895575f80fd5b506103546108a43660046130fd565b61253a565b3480156108b4575f80fd5b506108c86108c336600461319e565b6125d7565b60405161035e9190613256565b3480156108e0575f80fd5b50600454610512906001600160a01b031681565b3480156108ff575f80fd5b50610354620c210081565b348015610915575f80fd5b506108c861092436600461319e565b612640565b348015610934575f80fd5b5061031e61094336600461319e565b61276d565b348015610953575f80fd5b50600954610512906001600160a01b031681565b348015610972575f80fd5b50600654610512906001600160a01b031681565b348015610991575f80fd5b5061039b6109a036600461319e565b60156020525f908152604090205460ff1681565b3480156109bf575f80fd5b50610354600c5481565b3480156109d4575f80fd5b5061031e6109e336600461319e565b6127e6565b3480156109f3575f80fd5b5061031e610a0236600461319e565b61285d565b348015610a12575f80fd5b50610354600d5481565b348015610a27575f80fd5b5061031e610a3636600461319e565b6128d4565b610a4361290e565b610a51565b60405180910390fd5b610171811115610aad5760405162461bcd60e51b815260206004820152602160248201527f556e7374616b6520666565206d757374206e6f742065786365656420332e36396044820152602560f81b6064820152608401610a48565b60078190556040518181527f49ece0381b25e74505cb31ec43d4dea97c449b2993f3a5f13cc0f9893205dbd3906020015b60405180910390a150565b610af161293b565b6012548310610b125760405162461bcd60e51b8152600401610a4890613299565b33610b5f5760405162461bcd60e51b815260206004820152601b60248201527f436c61696d2066726f6d20746865207a65726f206164647265737300000000006044820152606401610a48565b5f60128481548110610b7357610b736132c5565b5f9182526020808320338452600e90915260409092206003600890920290920190810154909250610ba890620c2100906132ed565b431115610bf05760405162461bcd60e51b815260206004820152601660248201527510db185a5b481c195c9a5bd9081a185cc8195b99195960521b6044820152606401610a48565b8054610c305760405162461bcd60e51b815260206004820152600f60248201526e4e6f20616374697665207374616b6560881b6044820152606401610a48565b335f90815260058301602052604090205460ff1615610c915760405162461bcd60e51b815260206004820152601760248201527f41697264726f7020616c726561647920636c61696d65640000000000000000006044820152606401610a48565b5f826001015411610cdd5760405162461bcd60e51b8152602060048201526016602482015275125b9d985b1a5908185a5c991c9bdc08185b5bdd5b9d60521b6044820152606401610a48565b8315610db7575f610ced3361242a565b6016546040516323b872dd60e01b8152336004820152306024820152604481018390529192506001600160a01b0316906323b872dd906064016020604051808303815f875af1158015610d42573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d669190613300565b610da85760405162461bcd60e51b8152602060048201526013602482015272119959481d1c985b9cd9995c8819985a5b1959606a1b6044820152606401610a48565b610db181612992565b50610e0b565b5f610dc233876120d9565b50905080610e095760405162461bcd60e51b815260206004820152601460248201527356657374696e67206e6f7420636f6d706c65746560601b6044820152606401610a48565b505b60068201545f90600160a01b900460ff1615610e9f575f610e30338560030154612a4b565b90505f8111610e785760405162461bcd60e51b8152602060048201526014602482015273139bc81cdd185ad948185d081cdb985c1cda1bdd60621b6044820152606401610a48565b8360020154818560010154610e8d919061331b565b610e979190613332565b915050610ec9565b815460018401546a115eec47f6cf7e3500000091610ebc9161331b565b610ec69190613332565b90505b5f8111610f0d5760405162461bcd60e51b815260206004820152601260248201527155736572207368617265206973207a65726f60701b6044820152606401610a48565b335f9081526005840160205260408120805460ff19166001179055600784018054839290610f3c908490613351565b909155505f90506001600160a01b03851615610f585784610f5a565b335b8454909150610f73906001600160a01b03168284612b54565b60405187815233907fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a9060200160405180910390a250505050610fb560015f55565b505050565b6012545f908210610fdd5760405162461bcd60e51b8152600401610a4890613299565b5f60128381548110610ff157610ff16132c5565b5f91825260208083206001600160a01b03881684526005600890930201919091019052604090205460ff169150505b92915050565b6012545f9082106110495760405162461bcd60e51b8152600401610a4890613299565b5f6012838154811061105d5761105d6132c5565b5f918252602082206008919091020180546040516370a0823160e01b81523060048201529193506001600160a01b0316906370a0823190602401602060405180830381865afa1580156110b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110d69190613364565b90505f818360070154106110ea57816110f0565b82600701545b95945050505050565b61110161290e565b606481111561116b5760405162461bcd60e51b815260206004820152603060248201527f4c69717569646974792070657263656e74616765206d7573742062652062657460448201526f7765656e20302520616e64203130302560801b6064820152608401610a48565b60038190556040518181527f75078b0d8f3176b03c798e134c19fd1f03e82c6965d5ef29053660391e85db5d90602001610ade565b6111a861290e565b6001600160a01b0381165f81815260146020908152604091829020805460ff1916600117905590519182527f9acda53b350b4a722fd275bc98e8c0c901fe33d1f33d9c8abba45ffe7ab593f29101610ade565b61120361293b565b6016546001600160a01b03908116908416036112315760405162461bcd60e51b8152600401610a489061337b565b6001600160a01b0383165f9081526015602052604090205460ff166112905760405162461bcd60e51b8152602060048201526015602482015274151bdad95b881b9bdd081dda1a5d195b1a5cdd1959605a1b6044820152606401610a48565b6040516370a0823160e01b815230600482015283905f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156112d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112fa9190613364565b90506113116001600160a01b038316333087612bb3565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611357573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061137b9190613364565b6113859190613351565b601280546001810182555f919091527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444600890910290810180546001600160a01b0319166001600160a01b0387161781557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344582018390556013547fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3446830155437fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34478301557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344882018054600160a81b610100600160b01b03199091161790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344a820180546001600160a81b0319163360ff60a01b191617600160a01b891515021790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344b9091018290559091508461152f575f6064600c5484611506919061331b565b6115109190613332565b60045490915061152d906001600160a01b03878116911683612b54565b505b6012545f9061154090600190613351565b604080516001600160a01b038b168152602081018690529081018290529091507f727f90a5b643016a5e6fcb64cb55db20202a5c3026007b891e9048d5d588866f9060600160405180910390a15050505050610fb560015f55565b6115a361293b565b335f908152600e6020526040902080546115f05760405162461bcd60e51b815260206004820152600e60248201526d139bc81cdd185ad948199bdd5b9960921b6044820152606401610a48565b3361163d5760405162461bcd60e51b815260206004820152601d60248201527f556e7374616b652066726f6d20746865207a65726f20616464726573730000006044820152606401610a48565b5f612710600754835f0154611652919061331b565b61165c9190613332565b90505f81835f015461166e9190613351565b905061167982612992565b5f5b335f908152601060205260409020548110156116dc57335f9081526010602052604081208054839081106116b1576116b16132c5565b5f9182526020808320909101548252600f905260409020600401805460ff191690555060010161167b565b508254601380545f906116f0908490613351565b90915550505f83556001600d5f82825461170a9190613351565b9091555050601654611726906001600160a01b03163383612b54565b60405181815233907f0f5bb82176feb1b5e747e28471aa92156a04d9f3ab9f45f28e2d704232b93f759060200160405180910390a250505061176760015f55565b565b61177161290e565b6001600160a01b0381165f9081526015602052604090205460ff166117d15760405162461bcd60e51b8152602060048201526016602482015275151bdad95b881b9bdd081a5b881dda1a5d195b1a5cdd60521b6044820152606401610a48565b6001600160a01b03165f908152601560205260409020805460ff19169055565b5f805b601254811015611835578260128281548110611812576118126132c5565b905f5260205f209060080201600301541061182d5792915050565b6001016117f4565b5050601254919050565b61184761290e565b60648111156118b35760405162461bcd60e51b815260206004820152603260248201527f42656e65666963696172792050657263656e74616765206d757374206265206260448201527165747765656e20302520616e64203130302560701b6064820152608401610a48565b60058190556040518181527f37a0fb5651f65cba1d5940d003190047bf5ae1cafecf07cdfbe62022706f9e5a90602001610ade565b601281815481106118f7575f80fd5b5f91825260209091206008909102018054600182015460028301546003840154600485015460068601546007909601546001600160a01b03958616975093959294919360ff808316946101008404851694600160a81b909404821693811692600160a01b909104909116908a565b335f9081526014602052604090205460ff166119c35760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f74206120666163746f727920636f6e74726163746044820152606401610a48565b6016546001600160a01b03908116908616036119f15760405162461bcd60e51b8152600401610a489061337b565b8215806119fd57505f34115b611a495760405162461bcd60e51b815260206004820181905260248201527f496e73756666696369656e7420504c5320666f72204c50206372656174696f6e6044820152606401610a48565b60018260ff1610158015611a61575060638260ff1611155b611abe5760405162461bcd60e51b815260206004820152602860248201527f4c502070657263656e74616765206d757374206265206265747765656e20312560448201526720616e642039392560c01b6064820152608401610a48565b6040516370a0823160e01b815230600482015285905f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611b04573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b289190613364565b9050611b3f6001600160a01b038316333089612bb3565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b85573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ba99190613364565b611bb39190613351565b90505f806064611bc660ff89168561331b565b611bd09190613332565b9050878015611bde57505f81115b15611bf057611bed8a82612bf2565b91505b5f86611c2d576064600c5485611c06919061331b565b611c109190613332565b600454909150611c2d906001600160a01b03888116911683612b54565b5f81611c398487613351565b611c439190613351565b60128054600181810183555f83815260089092027fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444810180546001600160a01b03808f166001600160a01b03199092169190911782557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344583018c90556013547fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3446840155437fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34478401557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34488301805460ff60a81b19928d166101000292909216610100600160b01b03199092169190911790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344a820180548f1515600160a01b0260ff60a01b1933166001600160a81b0319909216919091171790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344b909101859055925493945091929091611dd691613351565b90507f727f90a5b643016a5e6fcb64cb55db20202a5c3026007b891e9048d5d588866f8e8883604051611e27939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a15050505050505050505050505050565b611e4761290e565b6117675f612f49565b6010602052815f5260405f208181548110611e69575f80fd5b905f5260205f20015f91509150505481565b611e8361293b565b5f60128281548110611e9757611e976132c5565b905f5260205f20906008020190505f620c21008260030154611eb991906132ed565b905080431015611f035760405162461bcd60e51b815260206004820152601560248201527421b630b4b6903832b934b7b2103737ba1037bb32b960591b6044820152606401610a48565b600482015460ff1615611f585760405162461bcd60e51b815260206004820152601d60248201527f41697264726f7020616c726561647920726564697374726962757465640000006044820152606401610a48565b5f611f6284611026565b9050805f03611f80575050600401805460ff191660011790556120cd565b5f611f8a600d5490565b90505f81601354611f9b9190613332565b90505f6a115eec47f6cf7e35000000611fb4838661331b565b611fbe9190613332565b9050838111611fcd5780611fcf565b835b90508015611ffa578554611fed906001600160a01b03163383612b54565b611ff78185613351565b93505b5f6127106005548661200c919061331b565b6120169190613332565b9050801561204757865460065461203a916001600160a01b03908116911683612b54565b6120448186613351565b94505b5f606460035487612058919061331b565b6120629190613332565b90508015612093578754600454612086916001600160a01b03908116911683612b54565b6120908187613351565b95505b85156120b55787546002546120b5916001600160a01b03908116911688612b54565b5050506004909401805460ff19166001179055505050505b6120d660015f55565b50565b6012545f90819083106120fe5760405162461bcd60e51b8152600401610a4890613299565b6001600160a01b0384165f908152600e6020908152604080832081518083019092528054825260010154918101919091526012805491929186908110612146576121466132c5565b905f5260205f20906008020190505f612161835f015161253a565b90505f81836003015461217491906132ed565b43811115999098509650505050505050565b61218e61293b565b5f81116121dd5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a48565b3361222a5760405162461bcd60e51b815260206004820152601b60248201527f5374616b652066726f6d20746865207a65726f206164647265737300000000006044820152606401610a48565b6a115eec47f6cf7e350000008160135461224491906132ed565b11156122925760405162461bcd60e51b815260206004820152601c60248201527f5374616b696e67206578636565647320746f74616c20737570706c79000000006044820152606401610a48565b6016546122aa906001600160a01b0316333084612bb3565b335f908152600e60205260409020541580156122c557505f81115b156122f357335f908152600e6020526040812043600190910155600d8054916122ed836133bd565b91905055505b335f908152600e6020526040812080548392906123119084906132ed565b925050819055505f60115f8154612327906133bd565b91829055506040805160a08101825282815233602080830182815283850188815243606086019081526001608087018181525f8a8152600f875289812098518955945188830180546001600160a01b0319166001600160a01b03909216919091179055925160028801559051600387015590516004909501805460ff19169515159590951790945591825260108152928120805492830181558152918220018290556013805492935084929091906123e09084906132ed565b9091555050604080518381526020810183905233917f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee90910160405180910390a2506120d660015f55565b6001600160a01b0381165f908152600e6020908152604080832081518083019092528054808352600190910154928201929092529061017190839061246e9061253a565b90505f8360200151436124819190613351565b905081811061249557505f95945050505050565b5f826124a1858461331b565b6124ab9190613332565b90505f6124b88286613351565b905061271081875f01516124cc919061331b565b6124d69190613332565b98975050505050505050565b6124ea61290e565b6001600160a01b0381165f81815260146020908152604091829020805460ff1916905590519182527f0d249836c97f781ffa90b97488112c9167ba88003fa03cff7a330970d62713ee9101610ade565b5f683635c9adc5dea00000821161255257505f919050565b69d3c21bcecceda100000082116125cd575f69d38be6051f27c2600000612582683635c9adc5dea0000085613351565b61258d90605a61331b565b6125979190613332565b9050600a6125a682601861331b565b6125b190603c61331b565b6125bc90603c61331b565b6125c69190613332565b9392505050565b50620bdd80919050565b6001600160a01b0381165f9081526010602090815260409182902080548351818402810184019094528084526060939283018282801561263457602002820191905f5260205f20905b815481526020019060010190808311612620575b50505050509050919050565b60605f805b6012548110156126a057836001600160a01b03166012828154811061266c5761266c6132c5565b5f9182526020909120600890910201546001600160a01b0316036126985781612694816133bd565b9250505b600101612645565b505f8167ffffffffffffffff8111156126bb576126bb6133d5565b6040519080825280602002602001820160405280156126e4578160200160208202803683370190505b5090505f805b60125481101561276357856001600160a01b031660128281548110612711576127116132c5565b5f9182526020909120600890910201546001600160a01b03160361275b5780838381518110612742576127426132c5565b602090810291909101015281612757816133bd565b9250505b6001016126ea565b5090949350505050565b61277561290e565b6001600160a01b0381166127c35760405162461bcd60e51b8152602060048201526015602482015274496e76616c696420746f6b656e206164647265737360581b6044820152606401610a48565b6001600160a01b03165f908152601560205260409020805460ff19166001179055565b6127ee61290e565b6001600160a01b0381166128145760405162461bcd60e51b8152600401610a48906133e9565b600480546001600160a01b0319166001600160a01b0383169081179091556040517f86cb8a60ff627a5d108a2eeb20b4c7ae3c0953e714e9d027ebc48bd294bcc071905f90a250565b61286561290e565b6001600160a01b03811661288b5760405162461bcd60e51b8152600401610a48906133e9565b600680546001600160a01b0319166001600160a01b0383169081179091556040517f592ee555b2ba862b174914f086596310e9b2be37ae9496b82820ccf6b25ef631905f90a250565b6128dc61290e565b6001600160a01b03811661290557604051631e4fbdf760e01b81525f6004820152602401610a48565b6120d681612f49565b6001546001600160a01b031633146117675760405163118cdaa760e01b8152336004820152602401610a48565b60025f540361298c5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610a48565b60025f55565b60165460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526024810184905291169063a9059cbb906044016020604051808303815f875af11580156129e4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a089190613300565b6120d65760405162461bcd60e51b8152602060048201526014602482015273109d5c9b881d1c985b9cd9995c8819985a5b195960621b6044820152606401610a48565b5f80805b6001600160a01b0385165f90815260106020526040902054811015612aec576001600160a01b0385165f9081526010602052604081208054600f91839185908110612a9c57612a9c6132c5565b905f5260205f20015481526020019081526020015f20905084816003015411158015612acc5750600481015460ff165b15612ae3576002810154612ae090846132ed565b92505b50600101612a4f565b506001600160a01b0384165f908152600e60205260409020548111156125c65760405162461bcd60e51b815260206004820181905260248201527f5374616b652064657461696c732065786365656420746f74616c207374616b656044820152606401610a48565b6040516001600160a01b03838116602483015260448201839052610fb591859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612f9a565b6040516001600160a01b038481166024830152838116604483015260648201839052612bec9186918216906323b872dd90608401612b81565b50505050565b600a5460405163095ea7b360e01b81526001600160a01b039182166004820152602481018390525f9184169063095ea7b3906044016020604051808303815f875af1158015612c43573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c679190613300565b505f6064612c7684605f61331b565b612c809190613332565b90505f6064612c9034605f61331b565b612c9a9190613332565b60195460405163f305d71960e01b81526001600160a01b0388811660048301526024820188905260448201869052606482018490523060848301524260a48301529293505f928392839291169063f305d71990349060c40160606040518083038185885af1158015612d0e573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190612d339190613434565b604080518481526020810184905290810182905292955090935091507fd7f28048575eead8851d024ead087913957dfb4fd1a02b4d1573f5352a5a2be39060600160405180910390a16019546040805163c45a015560e01b815290515f926001600160a01b03169163c45a01559160048083019260209291908290030181865afa158015612dc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612de7919061345f565b6001600160a01b031663e6a439058a60195f9054906101000a90046001600160a01b03166001600160a01b031663ef8ef56f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612e46573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e6a919061345f565b6040516001600160e01b031960e085901b1681526001600160a01b03928316600482015291166024820152604401602060405180830381865afa158015612eb3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed7919061345f565b600254909150612ef4906001600160a01b03808416911684612b54565b600254604080518481526001600160a01b039283166020820152918316917fee362beb50174d6f1b6792512b350a67767a22b6750da5940b7f0ee69818c45b910160405180910390a298975050505050505050565b600180546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f612fae6001600160a01b03841683612ffb565b905080515f14158015612fd2575080806020019051810190612fd09190613300565b155b15610fb557604051635274afe760e01b81526001600160a01b0384166004820152602401610a48565b60606125c683835f845f80856001600160a01b0316848660405161301f919061347a565b5f6040518083038185875af1925050503d805f8114613059576040519150601f19603f3d011682016040523d82523d5f602084013e61305e565b606091505b509150915061306e868383613078565b9695505050505050565b60608261308d57613088826130d4565b6125c6565b81511580156130a457506001600160a01b0384163b155b156130cd57604051639996b31560e01b81526001600160a01b0385166004820152602401610a48565b50806125c6565b8051156130e45780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f6020828403121561310d575f80fd5b5035919050565b80151581146120d6575f80fd5b6001600160a01b03811681146120d6575f80fd5b5f805f60608486031215613147575f80fd5b83359250602084013561315981613114565b9150604084013561316981613121565b809150509250925092565b5f8060408385031215613185575f80fd5b823561319081613121565b946020939093013593505050565b5f602082840312156131ae575f80fd5b81356125c681613121565b5f805f606084860312156131cb575f80fd5b83356131d681613121565b925060208401359150604084013561316981613114565b5f805f805f60a08688031215613201575f80fd5b853561320c81613121565b945060208601359350604086013561322381613114565b9250606086013560ff81168114613238575f80fd5b9150608086013561324881613114565b809150509295509295909350565b602080825282518282018190525f9190848201906040850190845b8181101561328d57835183529284019291840191600101613271565b50909695505050505050565b602080825260129082015271125b9d985b1a5908185a5c991c9bdc08125160721b604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b80820180821115611020576110206132d9565b5f60208284031215613310575f80fd5b81516125c681613114565b8082028115828204841417611020576110206132d9565b5f8261334c57634e487b7160e01b5f52601260045260245ffd5b500490565b81810381811115611020576110206132d9565b5f60208284031215613374575f80fd5b5051919050565b60208082526022908201527f5374616b696e6720746f6b656e2063616e6e6f742062652061697264726f7070604082015261195960f21b606082015260800190565b5f600182016133ce576133ce6132d9565b5060010190565b634e487b7160e01b5f52604160045260245ffd5b6020808252602b908201527f496e76616c696420616464726573733a2063616e6e6f7420626520746865207a60408201526a65726f206164647265737360a81b606082015260800190565b5f805f60608486031215613446575f80fd5b8351925060208401519150604084015190509250925092565b5f6020828403121561346f575f80fd5b81516125c681613121565b5f82515f5b81811015613499576020818601810151858301520161347f565b505f92019182525091905056fea2646970667358221220466a584c087c78379a8aedc2d3fb1b776af9bdbcd18eb429e02cdb0fc61540b564736f6c6343000818003300000000000000000000000029ea7545def87022badc76323f373ea1e707c523000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a27000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d90000000000000000000000004c8218dc22e478963c02748857245fad79aad0c6
Deployed ByteCode
0x6080604052600436106102f4575f3560e01c80638920de5c11610189578063c6de410e116100d8578063d9c4870e11610092578063eba70dff1161006d578063eba70dff146109c9578063ec6be06e146109e8578063ed21d26914610a07578063f2fde38b14610a1c575f80fd5b8063d9c4870e14610967578063daf9c21014610986578063e2c635fa146109b4575f80fd5b8063c6de410e146108a9578063cba1fcea146108d5578063cebe6008146108f4578063cff391f91461090a578063d84c1b3814610929578063d85bcd5f14610948575f80fd5b80639ab7f3ac11610143578063ba6101641161011e578063ba6101641461082d578063bafc73091461084c578063bb52289c1461086b578063c4b003431461088a575f80fd5b80639ab7f3ac14610751578063a694fc3a14610787578063aba8e329146107a6575f80fd5b80638920de5c146106a45780638a7c1d6d146106c25780638da5cb5b146106e15780638ea97d26146106fe5780638f33fe1f14610713578063927ef7fa14610732575f80fd5b80633ec588fc116102455780636bdbfc19116101ff578063715018a6116101da578063715018a61461062e578063742d389e146106425780637693e47914610670578063817b1cd21461068f575f80fd5b80636bdbfc19146105dd5780637097b145146105f057806370d5ae051461060f575f80fd5b80633ec588fc146104b55780634b1f1f4d146104d45780634f01a3a7146104f35780635298b8691461052a5780635eabf5771461054957806360db50821461055d575f80fd5b806317b2a6db116102b057806322971e581161028b57806322971e581461044f578063296e5c12146104635780632def662014610482578063306275be14610496575f80fd5b806317b2a6db146103f25780631a37225a1461041157806321695c7214610430575f80fd5b806218a116146102ff578062eed37b146103205780630230ee011461033f578063048b87ba1461036757806306a55a021461037c57806316934fc4146103ab575f80fd5b366102fb57005b5f80fd5b34801561030a575f80fd5b5061031e6103193660046130fd565b610a3b565b005b34801561032b575f80fd5b5061031e61033a366004613135565b610ae9565b34801561034a575f80fd5b5061035460055481565b6040519081526020015b60405180910390f35b348015610372575f80fd5b5061035460035481565b348015610387575f80fd5b5061039b610396366004613174565b610fba565b604051901515815260200161035e565b3480156103b6575f80fd5b506103dd6103c536600461319e565b600e6020525f90815260409020805460019091015482565b6040805192835260208301919091520161035e565b3480156103fd575f80fd5b5061035461040c3660046130fd565b611026565b34801561041c575f80fd5b5061031e61042b3660046130fd565b6110f9565b34801561043b575f80fd5b5061031e61044a36600461319e565b6111a0565b34801561045a575f80fd5b50601254610354565b34801561046e575f80fd5b5061031e61047d3660046131b9565b6111fb565b34801561048d575f80fd5b5061031e61159b565b3480156104a1575f80fd5b5061031e6104b036600461319e565b611769565b3480156104c0575f80fd5b506103546104cf3660046130fd565b6117f1565b3480156104df575f80fd5b5061031e6104ee3660046130fd565b61183f565b3480156104fe575f80fd5b50601754610512906001600160a01b031681565b6040516001600160a01b03909116815260200161035e565b348015610535575f80fd5b50600b54610512906001600160a01b031681565b348015610554575f80fd5b50600d54610354565b348015610568575f80fd5b5061057c6105773660046130fd565b6118e8565b604080516001600160a01b039b8c168152602081019a909a528901979097526060880195909552921515608087015290861660a0860152151560c085015290931660e08301529115156101008201526101208101919091526101400161035e565b61031e6105eb3660046131ed565b611965565b3480156105fb575f80fd5b50600a54610512906001600160a01b031681565b34801561061a575f80fd5b50600254610512906001600160a01b031681565b348015610639575f80fd5b5061031e611e3f565b34801561064d575f80fd5b5061039b61065c36600461319e565b60146020525f908152604090205460ff1681565b34801561067b575f80fd5b5061035461068a366004613174565b611e50565b34801561069a575f80fd5b5061035460135481565b3480156106af575f80fd5b506103546a115eec47f6cf7e3500000081565b3480156106cd575f80fd5b5061031e6106dc3660046130fd565b611e7b565b3480156106ec575f80fd5b506001546001600160a01b0316610512565b348015610709575f80fd5b5061035460075481565b34801561071e575f80fd5b50600854610512906001600160a01b031681565b34801561073d575f80fd5b50601854610512906001600160a01b031681565b34801561075c575f80fd5b5061077061076b366004613174565b6120d9565b60408051921515835260208301919091520161035e565b348015610792575f80fd5b5061031e6107a13660046130fd565b612186565b3480156107b1575f80fd5b506107fb6107c03660046130fd565b600f6020525f90815260409020805460018201546002830154600384015460049094015492936001600160a01b039092169290919060ff1685565b604080519586526001600160a01b0390941660208601529284019190915260608301521515608082015260a00161035e565b348015610838575f80fd5b50601654610512906001600160a01b031681565b348015610857575f80fd5b5061035461086636600461319e565b61242a565b348015610876575f80fd5b5061031e61088536600461319e565b6124e2565b348015610895575f80fd5b506103546108a43660046130fd565b61253a565b3480156108b4575f80fd5b506108c86108c336600461319e565b6125d7565b60405161035e9190613256565b3480156108e0575f80fd5b50600454610512906001600160a01b031681565b3480156108ff575f80fd5b50610354620c210081565b348015610915575f80fd5b506108c861092436600461319e565b612640565b348015610934575f80fd5b5061031e61094336600461319e565b61276d565b348015610953575f80fd5b50600954610512906001600160a01b031681565b348015610972575f80fd5b50600654610512906001600160a01b031681565b348015610991575f80fd5b5061039b6109a036600461319e565b60156020525f908152604090205460ff1681565b3480156109bf575f80fd5b50610354600c5481565b3480156109d4575f80fd5b5061031e6109e336600461319e565b6127e6565b3480156109f3575f80fd5b5061031e610a0236600461319e565b61285d565b348015610a12575f80fd5b50610354600d5481565b348015610a27575f80fd5b5061031e610a3636600461319e565b6128d4565b610a4361290e565b610a51565b60405180910390fd5b610171811115610aad5760405162461bcd60e51b815260206004820152602160248201527f556e7374616b6520666565206d757374206e6f742065786365656420332e36396044820152602560f81b6064820152608401610a48565b60078190556040518181527f49ece0381b25e74505cb31ec43d4dea97c449b2993f3a5f13cc0f9893205dbd3906020015b60405180910390a150565b610af161293b565b6012548310610b125760405162461bcd60e51b8152600401610a4890613299565b33610b5f5760405162461bcd60e51b815260206004820152601b60248201527f436c61696d2066726f6d20746865207a65726f206164647265737300000000006044820152606401610a48565b5f60128481548110610b7357610b736132c5565b5f9182526020808320338452600e90915260409092206003600890920290920190810154909250610ba890620c2100906132ed565b431115610bf05760405162461bcd60e51b815260206004820152601660248201527510db185a5b481c195c9a5bd9081a185cc8195b99195960521b6044820152606401610a48565b8054610c305760405162461bcd60e51b815260206004820152600f60248201526e4e6f20616374697665207374616b6560881b6044820152606401610a48565b335f90815260058301602052604090205460ff1615610c915760405162461bcd60e51b815260206004820152601760248201527f41697264726f7020616c726561647920636c61696d65640000000000000000006044820152606401610a48565b5f826001015411610cdd5760405162461bcd60e51b8152602060048201526016602482015275125b9d985b1a5908185a5c991c9bdc08185b5bdd5b9d60521b6044820152606401610a48565b8315610db7575f610ced3361242a565b6016546040516323b872dd60e01b8152336004820152306024820152604481018390529192506001600160a01b0316906323b872dd906064016020604051808303815f875af1158015610d42573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d669190613300565b610da85760405162461bcd60e51b8152602060048201526013602482015272119959481d1c985b9cd9995c8819985a5b1959606a1b6044820152606401610a48565b610db181612992565b50610e0b565b5f610dc233876120d9565b50905080610e095760405162461bcd60e51b815260206004820152601460248201527356657374696e67206e6f7420636f6d706c65746560601b6044820152606401610a48565b505b60068201545f90600160a01b900460ff1615610e9f575f610e30338560030154612a4b565b90505f8111610e785760405162461bcd60e51b8152602060048201526014602482015273139bc81cdd185ad948185d081cdb985c1cda1bdd60621b6044820152606401610a48565b8360020154818560010154610e8d919061331b565b610e979190613332565b915050610ec9565b815460018401546a115eec47f6cf7e3500000091610ebc9161331b565b610ec69190613332565b90505b5f8111610f0d5760405162461bcd60e51b815260206004820152601260248201527155736572207368617265206973207a65726f60701b6044820152606401610a48565b335f9081526005840160205260408120805460ff19166001179055600784018054839290610f3c908490613351565b909155505f90506001600160a01b03851615610f585784610f5a565b335b8454909150610f73906001600160a01b03168284612b54565b60405187815233907fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a9060200160405180910390a250505050610fb560015f55565b505050565b6012545f908210610fdd5760405162461bcd60e51b8152600401610a4890613299565b5f60128381548110610ff157610ff16132c5565b5f91825260208083206001600160a01b03881684526005600890930201919091019052604090205460ff169150505b92915050565b6012545f9082106110495760405162461bcd60e51b8152600401610a4890613299565b5f6012838154811061105d5761105d6132c5565b5f918252602082206008919091020180546040516370a0823160e01b81523060048201529193506001600160a01b0316906370a0823190602401602060405180830381865afa1580156110b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110d69190613364565b90505f818360070154106110ea57816110f0565b82600701545b95945050505050565b61110161290e565b606481111561116b5760405162461bcd60e51b815260206004820152603060248201527f4c69717569646974792070657263656e74616765206d7573742062652062657460448201526f7765656e20302520616e64203130302560801b6064820152608401610a48565b60038190556040518181527f75078b0d8f3176b03c798e134c19fd1f03e82c6965d5ef29053660391e85db5d90602001610ade565b6111a861290e565b6001600160a01b0381165f81815260146020908152604091829020805460ff1916600117905590519182527f9acda53b350b4a722fd275bc98e8c0c901fe33d1f33d9c8abba45ffe7ab593f29101610ade565b61120361293b565b6016546001600160a01b03908116908416036112315760405162461bcd60e51b8152600401610a489061337b565b6001600160a01b0383165f9081526015602052604090205460ff166112905760405162461bcd60e51b8152602060048201526015602482015274151bdad95b881b9bdd081dda1a5d195b1a5cdd1959605a1b6044820152606401610a48565b6040516370a0823160e01b815230600482015283905f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156112d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112fa9190613364565b90506113116001600160a01b038316333087612bb3565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611357573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061137b9190613364565b6113859190613351565b601280546001810182555f919091527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444600890910290810180546001600160a01b0319166001600160a01b0387161781557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344582018390556013547fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3446830155437fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34478301557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344882018054600160a81b610100600160b01b03199091161790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344a820180546001600160a81b0319163360ff60a01b191617600160a01b891515021790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344b9091018290559091508461152f575f6064600c5484611506919061331b565b6115109190613332565b60045490915061152d906001600160a01b03878116911683612b54565b505b6012545f9061154090600190613351565b604080516001600160a01b038b168152602081018690529081018290529091507f727f90a5b643016a5e6fcb64cb55db20202a5c3026007b891e9048d5d588866f9060600160405180910390a15050505050610fb560015f55565b6115a361293b565b335f908152600e6020526040902080546115f05760405162461bcd60e51b815260206004820152600e60248201526d139bc81cdd185ad948199bdd5b9960921b6044820152606401610a48565b3361163d5760405162461bcd60e51b815260206004820152601d60248201527f556e7374616b652066726f6d20746865207a65726f20616464726573730000006044820152606401610a48565b5f612710600754835f0154611652919061331b565b61165c9190613332565b90505f81835f015461166e9190613351565b905061167982612992565b5f5b335f908152601060205260409020548110156116dc57335f9081526010602052604081208054839081106116b1576116b16132c5565b5f9182526020808320909101548252600f905260409020600401805460ff191690555060010161167b565b508254601380545f906116f0908490613351565b90915550505f83556001600d5f82825461170a9190613351565b9091555050601654611726906001600160a01b03163383612b54565b60405181815233907f0f5bb82176feb1b5e747e28471aa92156a04d9f3ab9f45f28e2d704232b93f759060200160405180910390a250505061176760015f55565b565b61177161290e565b6001600160a01b0381165f9081526015602052604090205460ff166117d15760405162461bcd60e51b8152602060048201526016602482015275151bdad95b881b9bdd081a5b881dda1a5d195b1a5cdd60521b6044820152606401610a48565b6001600160a01b03165f908152601560205260409020805460ff19169055565b5f805b601254811015611835578260128281548110611812576118126132c5565b905f5260205f209060080201600301541061182d5792915050565b6001016117f4565b5050601254919050565b61184761290e565b60648111156118b35760405162461bcd60e51b815260206004820152603260248201527f42656e65666963696172792050657263656e74616765206d757374206265206260448201527165747765656e20302520616e64203130302560701b6064820152608401610a48565b60058190556040518181527f37a0fb5651f65cba1d5940d003190047bf5ae1cafecf07cdfbe62022706f9e5a90602001610ade565b601281815481106118f7575f80fd5b5f91825260209091206008909102018054600182015460028301546003840154600485015460068601546007909601546001600160a01b03958616975093959294919360ff808316946101008404851694600160a81b909404821693811692600160a01b909104909116908a565b335f9081526014602052604090205460ff166119c35760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f74206120666163746f727920636f6e74726163746044820152606401610a48565b6016546001600160a01b03908116908616036119f15760405162461bcd60e51b8152600401610a489061337b565b8215806119fd57505f34115b611a495760405162461bcd60e51b815260206004820181905260248201527f496e73756666696369656e7420504c5320666f72204c50206372656174696f6e6044820152606401610a48565b60018260ff1610158015611a61575060638260ff1611155b611abe5760405162461bcd60e51b815260206004820152602860248201527f4c502070657263656e74616765206d757374206265206265747765656e20312560448201526720616e642039392560c01b6064820152608401610a48565b6040516370a0823160e01b815230600482015285905f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611b04573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b289190613364565b9050611b3f6001600160a01b038316333089612bb3565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b85573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ba99190613364565b611bb39190613351565b90505f806064611bc660ff89168561331b565b611bd09190613332565b9050878015611bde57505f81115b15611bf057611bed8a82612bf2565b91505b5f86611c2d576064600c5485611c06919061331b565b611c109190613332565b600454909150611c2d906001600160a01b03888116911683612b54565b5f81611c398487613351565b611c439190613351565b60128054600181810183555f83815260089092027fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444810180546001600160a01b03808f166001600160a01b03199092169190911782557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344583018c90556013547fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3446840155437fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34478401557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34488301805460ff60a81b19928d166101000292909216610100600160b01b03199092169190911790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344a820180548f1515600160a01b0260ff60a01b1933166001600160a81b0319909216919091171790557fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344b909101859055925493945091929091611dd691613351565b90507f727f90a5b643016a5e6fcb64cb55db20202a5c3026007b891e9048d5d588866f8e8883604051611e27939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a15050505050505050505050505050565b611e4761290e565b6117675f612f49565b6010602052815f5260405f208181548110611e69575f80fd5b905f5260205f20015f91509150505481565b611e8361293b565b5f60128281548110611e9757611e976132c5565b905f5260205f20906008020190505f620c21008260030154611eb991906132ed565b905080431015611f035760405162461bcd60e51b815260206004820152601560248201527421b630b4b6903832b934b7b2103737ba1037bb32b960591b6044820152606401610a48565b600482015460ff1615611f585760405162461bcd60e51b815260206004820152601d60248201527f41697264726f7020616c726561647920726564697374726962757465640000006044820152606401610a48565b5f611f6284611026565b9050805f03611f80575050600401805460ff191660011790556120cd565b5f611f8a600d5490565b90505f81601354611f9b9190613332565b90505f6a115eec47f6cf7e35000000611fb4838661331b565b611fbe9190613332565b9050838111611fcd5780611fcf565b835b90508015611ffa578554611fed906001600160a01b03163383612b54565b611ff78185613351565b93505b5f6127106005548661200c919061331b565b6120169190613332565b9050801561204757865460065461203a916001600160a01b03908116911683612b54565b6120448186613351565b94505b5f606460035487612058919061331b565b6120629190613332565b90508015612093578754600454612086916001600160a01b03908116911683612b54565b6120908187613351565b95505b85156120b55787546002546120b5916001600160a01b03908116911688612b54565b5050506004909401805460ff19166001179055505050505b6120d660015f55565b50565b6012545f90819083106120fe5760405162461bcd60e51b8152600401610a4890613299565b6001600160a01b0384165f908152600e6020908152604080832081518083019092528054825260010154918101919091526012805491929186908110612146576121466132c5565b905f5260205f20906008020190505f612161835f015161253a565b90505f81836003015461217491906132ed565b43811115999098509650505050505050565b61218e61293b565b5f81116121dd5760405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606401610a48565b3361222a5760405162461bcd60e51b815260206004820152601b60248201527f5374616b652066726f6d20746865207a65726f206164647265737300000000006044820152606401610a48565b6a115eec47f6cf7e350000008160135461224491906132ed565b11156122925760405162461bcd60e51b815260206004820152601c60248201527f5374616b696e67206578636565647320746f74616c20737570706c79000000006044820152606401610a48565b6016546122aa906001600160a01b0316333084612bb3565b335f908152600e60205260409020541580156122c557505f81115b156122f357335f908152600e6020526040812043600190910155600d8054916122ed836133bd565b91905055505b335f908152600e6020526040812080548392906123119084906132ed565b925050819055505f60115f8154612327906133bd565b91829055506040805160a08101825282815233602080830182815283850188815243606086019081526001608087018181525f8a8152600f875289812098518955945188830180546001600160a01b0319166001600160a01b03909216919091179055925160028801559051600387015590516004909501805460ff19169515159590951790945591825260108152928120805492830181558152918220018290556013805492935084929091906123e09084906132ed565b9091555050604080518381526020810183905233917f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee90910160405180910390a2506120d660015f55565b6001600160a01b0381165f908152600e6020908152604080832081518083019092528054808352600190910154928201929092529061017190839061246e9061253a565b90505f8360200151436124819190613351565b905081811061249557505f95945050505050565b5f826124a1858461331b565b6124ab9190613332565b90505f6124b88286613351565b905061271081875f01516124cc919061331b565b6124d69190613332565b98975050505050505050565b6124ea61290e565b6001600160a01b0381165f81815260146020908152604091829020805460ff1916905590519182527f0d249836c97f781ffa90b97488112c9167ba88003fa03cff7a330970d62713ee9101610ade565b5f683635c9adc5dea00000821161255257505f919050565b69d3c21bcecceda100000082116125cd575f69d38be6051f27c2600000612582683635c9adc5dea0000085613351565b61258d90605a61331b565b6125979190613332565b9050600a6125a682601861331b565b6125b190603c61331b565b6125bc90603c61331b565b6125c69190613332565b9392505050565b50620bdd80919050565b6001600160a01b0381165f9081526010602090815260409182902080548351818402810184019094528084526060939283018282801561263457602002820191905f5260205f20905b815481526020019060010190808311612620575b50505050509050919050565b60605f805b6012548110156126a057836001600160a01b03166012828154811061266c5761266c6132c5565b5f9182526020909120600890910201546001600160a01b0316036126985781612694816133bd565b9250505b600101612645565b505f8167ffffffffffffffff8111156126bb576126bb6133d5565b6040519080825280602002602001820160405280156126e4578160200160208202803683370190505b5090505f805b60125481101561276357856001600160a01b031660128281548110612711576127116132c5565b5f9182526020909120600890910201546001600160a01b03160361275b5780838381518110612742576127426132c5565b602090810291909101015281612757816133bd565b9250505b6001016126ea565b5090949350505050565b61277561290e565b6001600160a01b0381166127c35760405162461bcd60e51b8152602060048201526015602482015274496e76616c696420746f6b656e206164647265737360581b6044820152606401610a48565b6001600160a01b03165f908152601560205260409020805460ff19166001179055565b6127ee61290e565b6001600160a01b0381166128145760405162461bcd60e51b8152600401610a48906133e9565b600480546001600160a01b0319166001600160a01b0383169081179091556040517f86cb8a60ff627a5d108a2eeb20b4c7ae3c0953e714e9d027ebc48bd294bcc071905f90a250565b61286561290e565b6001600160a01b03811661288b5760405162461bcd60e51b8152600401610a48906133e9565b600680546001600160a01b0319166001600160a01b0383169081179091556040517f592ee555b2ba862b174914f086596310e9b2be37ae9496b82820ccf6b25ef631905f90a250565b6128dc61290e565b6001600160a01b03811661290557604051631e4fbdf760e01b81525f6004820152602401610a48565b6120d681612f49565b6001546001600160a01b031633146117675760405163118cdaa760e01b8152336004820152602401610a48565b60025f540361298c5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610a48565b60025f55565b60165460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526024810184905291169063a9059cbb906044016020604051808303815f875af11580156129e4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a089190613300565b6120d65760405162461bcd60e51b8152602060048201526014602482015273109d5c9b881d1c985b9cd9995c8819985a5b195960621b6044820152606401610a48565b5f80805b6001600160a01b0385165f90815260106020526040902054811015612aec576001600160a01b0385165f9081526010602052604081208054600f91839185908110612a9c57612a9c6132c5565b905f5260205f20015481526020019081526020015f20905084816003015411158015612acc5750600481015460ff165b15612ae3576002810154612ae090846132ed565b92505b50600101612a4f565b506001600160a01b0384165f908152600e60205260409020548111156125c65760405162461bcd60e51b815260206004820181905260248201527f5374616b652064657461696c732065786365656420746f74616c207374616b656044820152606401610a48565b6040516001600160a01b03838116602483015260448201839052610fb591859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612f9a565b6040516001600160a01b038481166024830152838116604483015260648201839052612bec9186918216906323b872dd90608401612b81565b50505050565b600a5460405163095ea7b360e01b81526001600160a01b039182166004820152602481018390525f9184169063095ea7b3906044016020604051808303815f875af1158015612c43573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c679190613300565b505f6064612c7684605f61331b565b612c809190613332565b90505f6064612c9034605f61331b565b612c9a9190613332565b60195460405163f305d71960e01b81526001600160a01b0388811660048301526024820188905260448201869052606482018490523060848301524260a48301529293505f928392839291169063f305d71990349060c40160606040518083038185885af1158015612d0e573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190612d339190613434565b604080518481526020810184905290810182905292955090935091507fd7f28048575eead8851d024ead087913957dfb4fd1a02b4d1573f5352a5a2be39060600160405180910390a16019546040805163c45a015560e01b815290515f926001600160a01b03169163c45a01559160048083019260209291908290030181865afa158015612dc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612de7919061345f565b6001600160a01b031663e6a439058a60195f9054906101000a90046001600160a01b03166001600160a01b031663ef8ef56f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612e46573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e6a919061345f565b6040516001600160e01b031960e085901b1681526001600160a01b03928316600482015291166024820152604401602060405180830381865afa158015612eb3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed7919061345f565b600254909150612ef4906001600160a01b03808416911684612b54565b600254604080518481526001600160a01b039283166020820152918316917fee362beb50174d6f1b6792512b350a67767a22b6750da5940b7f0ee69818c45b910160405180910390a298975050505050505050565b600180546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f612fae6001600160a01b03841683612ffb565b905080515f14158015612fd2575080806020019051810190612fd09190613300565b155b15610fb557604051635274afe760e01b81526001600160a01b0384166004820152602401610a48565b60606125c683835f845f80856001600160a01b0316848660405161301f919061347a565b5f6040518083038185875af1925050503d805f8114613059576040519150601f19603f3d011682016040523d82523d5f602084013e61305e565b606091505b509150915061306e868383613078565b9695505050505050565b60608261308d57613088826130d4565b6125c6565b81511580156130a457506001600160a01b0384163b155b156130cd57604051639996b31560e01b81526001600160a01b0385166004820152602401610a48565b50806125c6565b8051156130e45780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f6020828403121561310d575f80fd5b5035919050565b80151581146120d6575f80fd5b6001600160a01b03811681146120d6575f80fd5b5f805f60608486031215613147575f80fd5b83359250602084013561315981613114565b9150604084013561316981613121565b809150509250925092565b5f8060408385031215613185575f80fd5b823561319081613121565b946020939093013593505050565b5f602082840312156131ae575f80fd5b81356125c681613121565b5f805f606084860312156131cb575f80fd5b83356131d681613121565b925060208401359150604084013561316981613114565b5f805f805f60a08688031215613201575f80fd5b853561320c81613121565b945060208601359350604086013561322381613114565b9250606086013560ff81168114613238575f80fd5b9150608086013561324881613114565b809150509295509295909350565b602080825282518282018190525f9190848201906040850190845b8181101561328d57835183529284019291840191600101613271565b50909695505050505050565b602080825260129082015271125b9d985b1a5908185a5c991c9bdc08125160721b604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b80820180821115611020576110206132d9565b5f60208284031215613310575f80fd5b81516125c681613114565b8082028115828204841417611020576110206132d9565b5f8261334c57634e487b7160e01b5f52601260045260245ffd5b500490565b81810381811115611020576110206132d9565b5f60208284031215613374575f80fd5b5051919050565b60208082526022908201527f5374616b696e6720746f6b656e2063616e6e6f742062652061697264726f7070604082015261195960f21b606082015260800190565b5f600182016133ce576133ce6132d9565b5060010190565b634e487b7160e01b5f52604160045260245ffd5b6020808252602b908201527f496e76616c696420616464726573733a2063616e6e6f7420626520746865207a60408201526a65726f206164647265737360a81b606082015260800190565b5f805f60608486031215613446575f80fd5b8351925060208401519150604084015190509250925092565b5f6020828403121561346f575f80fd5b81516125c681613121565b5f82515f5b81811015613499576020818601810151858301520161347f565b505f92019182525091905056fea2646970667358221220466a584c087c78379a8aedc2d3fb1b776af9bdbcd18eb429e02cdb0fc61540b564736f6c63430008180033