Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 32 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Migrate And Stak... | 18728132 | 461 days ago | IN | 0 ETH | 0.01871955 | ||||
Migrate And Stak... | 17283813 | 664 days ago | IN | 0 ETH | 0.01327082 | ||||
Migrate And Stak... | 16968578 | 708 days ago | IN | 0 ETH | 0.00611304 | ||||
Migrate And Stak... | 16895935 | 719 days ago | IN | 0 ETH | 0.00507793 | ||||
Migrate And Stak... | 16855469 | 724 days ago | IN | 0 ETH | 0.00582998 | ||||
Migrate And Stak... | 16847278 | 725 days ago | IN | 0 ETH | 0.00468803 | ||||
Migrate And Stak... | 16828396 | 728 days ago | IN | 0 ETH | 0.01104239 | ||||
Stake And Lock | 16807076 | 731 days ago | IN | 0 ETH | 0.01137708 | ||||
Migrate And Stak... | 16655804 | 752 days ago | IN | 0 ETH | 0.00898976 | ||||
Stake And Lock | 16637851 | 755 days ago | IN | 0 ETH | 0.01442381 | ||||
Stake And Lock | 16629824 | 756 days ago | IN | 0 ETH | 0.02340043 | ||||
Migrate And Stak... | 16622541 | 757 days ago | IN | 0 ETH | 0.00632441 | ||||
Stake And Lock | 16368663 | 792 days ago | IN | 0 ETH | 0.00595908 | ||||
Stake And Lock | 16334830 | 797 days ago | IN | 0 ETH | 0.00867718 | ||||
Migrate Stake An... | 16328666 | 798 days ago | IN | 0 ETH | 0.00858355 | ||||
Stake And Lock | 16290476 | 803 days ago | IN | 0 ETH | 0.00530748 | ||||
Migrate Stake An... | 16275190 | 805 days ago | IN | 0 ETH | 0.00505595 | ||||
Migrate And Stak... | 16189571 | 817 days ago | IN | 0 ETH | 0.00399874 | ||||
Migrate Stake An... | 16182832 | 818 days ago | IN | 0 ETH | 0.00637774 | ||||
Migrate And Stak... | 16180912 | 819 days ago | IN | 0 ETH | 0.00413229 | ||||
Migrate Stake An... | 16179259 | 819 days ago | IN | 0 ETH | 0.00580205 | ||||
Migrate Stake An... | 16177284 | 819 days ago | IN | 0 ETH | 0.01133849 | ||||
Stake And Lock | 16176657 | 819 days ago | IN | 0 ETH | 0.00808079 | ||||
Migrate Stake An... | 16176606 | 819 days ago | IN | 0 ETH | 0.00827436 | ||||
Migrate Stake An... | 16175809 | 819 days ago | IN | 0 ETH | 0.00755016 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Method | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|---|
0x61012060 | 16169913 | 820 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Name:
StakingRouterV1
Compiler Version
v0.8.16+commit.07a7930e
Optimization Enabled:
Yes with 20000 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "contracts/interfaces/IERC721Transferable.sol"; import "contracts/interfaces/IAliceNetFactory.sol"; import "contracts/interfaces/IStakingNFT.sol"; import "contracts/interfaces/IStakingToken.sol"; import "contracts/utils/EthSafeTransfer.sol"; import "contracts/utils/ERC20SafeTransfer.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; import "contracts/utils/auth/ImmutablePublicStaking.sol"; import "contracts/utils/auth/ImmutableALCA.sol"; import "contracts/Lockup.sol"; /// @custom:salt StakingRouterV1 /// @custom:deploy-type deployCreateAndRegister /// @custom:deploy-group lockup /// @custom:deploy-group-index 1 contract StakingRouterV1 is ImmutablePublicStaking, ImmutableALCA, ERC20SafeTransfer, EthSafeTransfer { error InvalidStakingAmount(uint256 stakingAmount, uint256 migratedAmount); bytes32 internal constant _LOCKUP_SALT = 0x4c6f636b75700000000000000000000000000000000000000000000000000000; address internal immutable _legacyToken; address internal immutable _lockupContract; constructor() ImmutableFactory(msg.sender) ImmutablePublicStaking() ImmutableALCA() { _legacyToken = IStakingToken(_alcaAddress()).getLegacyTokenAddress(); _lockupContract = IAliceNetFactory(_factoryAddress()).lookup(_LOCKUP_SALT); } /// @notice Migrates an amount of legacy token (MADToken) to ALCA tokens and stake them in the /// PublicStaking contract. User calling this function must have approved this contract to /// transfer the `migrationAmount_` MADTokens beforehand. /// @param to_ the address that will own the position /// @param migrationAmount_ the amount of legacy token to migrate /// @param stakingAmount_ the amount of ALCA that will staked and locked function migrateAndStake(address to_, uint256 migrationAmount_, uint256 stakingAmount_) public { uint256 migratedAmount = _migrate(msg.sender, migrationAmount_); _verifyAndSendAnyRemainder(to_, migratedAmount, stakingAmount_); _stake(to_, stakingAmount_); } /// @notice Migrates an amount of legacy token (MADToken) to ALCA tokens, stake them in the /// PublicStaking contract and in sequence lock the position. User calling this function must have /// approved this contract to transfer the `migrationAmount_` MADTokens beforehand. /// @param to_ the address that will own the locked position /// @param migrationAmount_ the amount of legacy token to migrate /// @param stakingAmount_ the amount of ALCA that will staked and locked function migrateStakeAndLock( address to_, uint256 migrationAmount_, uint256 stakingAmount_ ) public { uint256 migratedAmount = _migrate(msg.sender, migrationAmount_); _verifyAndSendAnyRemainder(to_, migratedAmount, stakingAmount_); // mint the position directly to the lockup contract uint256 tokenID = _stake(_lockupContract, stakingAmount_); // right in sequence claim the minted position Lockup(payable(_lockupContract)).lockFromTransfer(tokenID, to_); } /// @notice Stake an amount of ALCA in the PublicStaking contract and lock the position in /// sequence. User calling this function must have approved this contract to /// transfer the `stakingAmount_` ALCA beforehand. /// @param to_ the address that will own the locked position /// @param stakingAmount_ the amount of ALCA that will staked function stakeAndLock(address to_, uint256 stakingAmount_) public { _safeTransferFromERC20(IERC20Transferable(_alcaAddress()), msg.sender, stakingAmount_); // mint the position directly to the lockup contract uint256 tokenID = _stake(_lockupContract, stakingAmount_); // right in sequence claim the minted position Lockup(payable(_lockupContract)).lockFromTransfer(tokenID, to_); } /// @notice Get the address of the legacy token. /// @return the address of the legacy token (MADToken). function getLegacyTokenAddress() public view returns (address) { return _legacyToken; } function _migrate(address from_, uint256 amount_) internal returns (uint256 migratedAmount_) { _safeTransferFromERC20(IERC20Transferable(_legacyToken), from_, amount_); IERC20(_legacyToken).approve(_alcaAddress(), amount_); migratedAmount_ = IStakingToken(_alcaAddress()).migrateTo(address(this), amount_); } function _stake(address to_, uint256 stakingAmount_) internal returns (uint256 tokenID_) { IERC20(_alcaAddress()).approve(_publicStakingAddress(), stakingAmount_); tokenID_ = IStakingNFT(_publicStakingAddress()).mintTo(to_, stakingAmount_, 0); } function _verifyAndSendAnyRemainder( address to_, uint256 migratedAmount_, uint256 stakingAmount_ ) internal { if (stakingAmount_ > migratedAmount_) { revert InvalidStakingAmount(stakingAmount_, migratedAmount_); } uint256 remainder = migratedAmount_ - stakingAmount_; if (remainder > 0) { _safeTransferERC20(IERC20Transferable(_alcaAddress()), to_, remainder); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @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 amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; import "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.0; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol) pragma solidity ^0.8.0; import "../IERC721Receiver.sol"; /** * @dev Implementation of the {IERC721Receiver} interface. * * Accepts all token transfers. * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. */ contract ERC721Holder is IERC721Receiver { /** * @dev See {IERC721Receiver-onERC721Received}. * * Always returns `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address, address, uint256, bytes memory ) public virtual override returns (bytes4) { return this.onERC721Received.selector; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; import "contracts/utils/auth/ImmutableALCA.sol"; import "contracts/utils/auth/ImmutablePublicStaking.sol"; import "contracts/utils/auth/ImmutableFoundation.sol"; import "contracts/utils/EthSafeTransfer.sol"; import "contracts/utils/ERC20SafeTransfer.sol"; import "contracts/utils/MagicEthTransfer.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "contracts/interfaces/IStakingNFT.sol"; import "contracts/libraries/errors/LockupErrors.sol"; import "contracts/libraries/lockup/AccessControlled.sol"; import "contracts/RewardPool.sol"; import "contracts/Lockup.sol"; /** * @notice This contract holds all ALCA that is held in escrow for lockup * bonuses. All ALCA is hold into a single staked position that is owned * locally. * @dev deployed by the RewardPool contract */ contract BonusPool is ImmutableALCA, ImmutablePublicStaking, ImmutableFoundation, ERC20SafeTransfer, EthSafeTransfer, ERC721Holder, AccessControlled, MagicEthTransfer { uint256 internal immutable _totalBonusAmount; address internal immutable _lockupContract; address internal immutable _rewardPool; // tokenID of the position created to hold the amount that will be redistributed as bonus uint256 internal _tokenID; event BonusPositionCreated(uint256 tokenID); constructor( address aliceNetFactory_, address lockupContract_, address rewardPool_, uint256 totalBonusAmount_ ) ImmutableFactory(aliceNetFactory_) ImmutableALCA() ImmutablePublicStaking() ImmutableFoundation() { _totalBonusAmount = totalBonusAmount_; _lockupContract = lockupContract_; _rewardPool = rewardPool_; } receive() external payable { if (msg.sender != _publicStakingAddress()) { revert LockupErrors.AddressNotAllowedToSendEther(); } } /// @notice function that creates/mint a publicStaking position with an amount that will be /// redistributed as bonus at the end of the lockup period. The amount of ALCA has to be /// transferred before calling this function. /// @dev can be only called by the AliceNet factory function createBonusStakedPosition() public onlyFactory { if (_tokenID != 0) { revert LockupErrors.BonusTokenAlreadyCreated(); } IERC20 alca = IERC20(_alcaAddress()); //get the total balance of ALCA owned by bonus pool as stake amount uint256 _stakeAmount = alca.balanceOf(address(this)); if (_stakeAmount < _totalBonusAmount) { revert LockupErrors.NotEnoughALCAToStake(_stakeAmount, _totalBonusAmount); } // approve the staking contract to transfer the ALCA alca.approve(_publicStakingAddress(), _totalBonusAmount); uint256 tokenID = IStakingNFT(_publicStakingAddress()).mint(_totalBonusAmount); _tokenID = tokenID; emit BonusPositionCreated(_tokenID); } /// @notice Burns that bonus staked position, and send the bonus amount of shares + profits to /// the rewardPool contract, so users can collect. function terminate() public onlyLockup { if (_tokenID == 0) { revert LockupErrors.BonusTokenNotCreated(); } // burn the nft to collect all profits. IStakingNFT(_publicStakingAddress()).burn(_tokenID); // restarting the _tokenID _tokenID = 0; // send the total balance of ALCA to the rewardPool contract uint256 alcaBalance = IERC20(_alcaAddress()).balanceOf(address(this)); _safeTransferERC20( IERC20Transferable(_alcaAddress()), _getRewardPoolAddress(), alcaBalance ); // send also all the balance of ether uint256 ethBalance = address(this).balance; RewardPool(_getRewardPoolAddress()).deposit{value: ethBalance}(alcaBalance); } /// @notice gets the lockup contract address /// @return the lockup contract address function getLockupContractAddress() public view returns (address) { return _getLockupContractAddress(); } /// @notice gets the rewardPool contract address /// @return the rewardPool contract address function getRewardPoolAddress() public view returns (address) { return _getRewardPoolAddress(); } /// @notice gets the tokenID of the publicStaking position that has the whole bonus amount /// @return the tokenID of the publicStaking position that has the whole bonus amount function getBonusStakedPosition() public view returns (uint256) { return _tokenID; } /// @notice gets the total amount of ALCA that was staked initially in the publicStaking position /// @return the total amount of ALCA that was staked initially in the publicStaking position function getTotalBonusAmount() public view returns (uint256) { return _totalBonusAmount; } /// @notice estimates a user's bonus amount + bonus position profits. /// @param currentSharesLocked_ The current number of shares locked in the lockup contract /// @param userShares_ The amount of shares that a user locked-up. /// @return bonusRewardEth the estimated amount ether profits for a user /// @return bonusRewardToken the estimated amount ALCA profits for a user function estimateBonusAmountWithReward( uint256 currentSharesLocked_, uint256 userShares_ ) public view returns (uint256 bonusRewardEth, uint256 bonusRewardToken) { if (_tokenID == 0) { return (0, 0); } (uint256 estimatedPayoutEth, uint256 estimatedPayoutToken) = IStakingNFT( _publicStakingAddress() ).estimateAllProfits(_tokenID); (uint256 shares, , , , ) = IStakingNFT(_publicStakingAddress()).getPosition(_tokenID); estimatedPayoutToken += shares; // compute what will be the amount that a user will receive from the amount that will be // sent to the reward contract. bonusRewardEth = (estimatedPayoutEth * userShares_) / currentSharesLocked_; bonusRewardToken = (estimatedPayoutToken * userShares_) / currentSharesLocked_; } function _getLockupContractAddress() internal view override returns (address) { return _lockupContract; } function _getBonusPoolAddress() internal view override returns (address) { return address(this); } function _getRewardPoolAddress() internal view override returns (address) { return _rewardPool; } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IAliceNetFactory { function lookup(bytes32 salt_) external view returns (address); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IERC20Transferable { function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); function transfer(address recipient, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function balanceOf(address account) external view returns (uint256); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IERC721Transferable { function safeTransferFrom(address from, address to, uint256 tokenId) external; }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IMagicEthTransfer { function depositEth(uint8 magic_) external payable; }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IStakingNFT { function skimExcessEth(address to_) external returns (uint256 excess); function skimExcessToken(address to_) external returns (uint256 excess); function depositToken(uint8 magic_, uint256 amount_) external; function depositEth(uint8 magic_) external payable; function lockPosition( address caller_, uint256 tokenID_, uint256 lockDuration_ ) external returns (uint256); function lockOwnPosition(uint256 tokenID_, uint256 lockDuration_) external returns (uint256); function lockWithdraw(uint256 tokenID_, uint256 lockDuration_) external returns (uint256); function mint(uint256 amount_) external returns (uint256 tokenID); function mintTo( address to_, uint256 amount_, uint256 lockDuration_ ) external returns (uint256 tokenID); function burn(uint256 tokenID_) external returns (uint256 payoutEth, uint256 payoutALCA); function burnTo( address to_, uint256 tokenID_ ) external returns (uint256 payoutEth, uint256 payoutALCA); function collectEth(uint256 tokenID_) external returns (uint256 payout); function collectToken(uint256 tokenID_) external returns (uint256 payout); function collectAllProfits( uint256 tokenID_ ) external returns (uint256 payoutToken, uint256 payoutEth); function collectEthTo(address to_, uint256 tokenID_) external returns (uint256 payout); function collectTokenTo(address to_, uint256 tokenID_) external returns (uint256 payout); function collectAllProfitsTo( address to_, uint256 tokenID_ ) external returns (uint256 payoutToken, uint256 payoutEth); function getPosition( uint256 tokenID_ ) external view returns ( uint256 shares, uint256 freeAfter, uint256 withdrawFreeAfter, uint256 accumulatorEth, uint256 accumulatorToken ); function getTotalShares() external view returns (uint256); function getTotalReserveEth() external view returns (uint256); function getTotalReserveALCA() external view returns (uint256); function estimateEthCollection(uint256 tokenID_) external view returns (uint256 payout); function estimateTokenCollection(uint256 tokenID_) external view returns (uint256 payout); function estimateAllProfits( uint256 tokenID_ ) external view returns (uint256 payoutEth, uint256 payoutToken); function estimateExcessToken() external view returns (uint256 excess); function estimateExcessEth() external view returns (uint256 excess); function getEthAccumulator() external view returns (uint256 accumulator, uint256 slush); function getTokenAccumulator() external view returns (uint256 accumulator, uint256 slush); function getLatestMintedPositionID() external view returns (uint256); function getAccumulatorScaleFactor() external pure returns (uint256); function getMaxMintLock() external pure returns (uint256); function getMaxGovernanceLock() external pure returns (uint256); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; interface IStakingToken { function migrate(uint256 amount) external returns (uint256); function migrateTo(address to, uint256 amount) external returns (uint256); function finishEarlyStage() external; function externalMint(address to, uint256 amount) external; function externalBurn(address from, uint256 amount) external; function getLegacyTokenAddress() external view returns (address); function convert(uint256 amount) external view returns (uint256); } interface IStakingTokenMinter { function mint(address to, uint256 amount) external; } interface IStakingTokenBurner { function burn(address to, uint256 amount) external; }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; library ERC20SafeTransferErrors { error CannotCallContractMethodsOnZeroAddress(); error Erc20TransferFailed(address erc20Address, address from, address to, uint256 amount); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; library ETHSafeTransferErrors { error CannotTransferToZeroAddress(); error EthTransferFailed(address from, address to, uint256 amount); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; library LockupErrors { error AddressNotAllowedToSendEther(); error OnlyStakingNFTAllowed(); error ContractDoesNotOwnTokenID(uint256 tokenID_); error AddressAlreadyLockedUp(); error TokenIDAlreadyClaimed(uint256 tokenID_); error InsufficientBalanceForEarlyExit(uint256 exitValue, uint256 currentBalance); error UserHasNoPosition(); error PreLockStateRequired(); error PreLockStateNotAllowed(); error PostLockStateNotAllowed(); error PostLockStateRequired(); error PayoutUnsafe(); error PayoutSafe(); error TokenIDNotLocked(uint256 tokenID_); error InvalidPositionWithdrawPeriod(uint256 withdrawFreeAfter, uint256 endBlock); error InLockStateRequired(); error BonusTokenNotCreated(); error BonusTokenAlreadyCreated(); error NotEnoughALCAToStake(uint256 currentBalance, uint256 expectedAmount); error InvalidTotalSharesValue(); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; library MagicValueErrors { error BadMagic(uint256 magic); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; abstract contract AccessControlled { error CallerNotLockup(); error CallerNotLockupOrBonus(); modifier onlyLockup() { if (msg.sender != _getLockupContractAddress()) { revert CallerNotLockup(); } _; } modifier onlyLockupOrBonus() { // must protect increment of token balance if ( msg.sender != _getLockupContractAddress() && msg.sender != address(_getBonusPoolAddress()) ) { revert CallerNotLockupOrBonus(); } _; } function _getLockupContractAddress() internal view virtual returns (address); function _getBonusPoolAddress() internal view virtual returns (address); function _getRewardPoolAddress() internal view virtual returns (address); }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "contracts/interfaces/IERC721Transferable.sol"; import "contracts/interfaces/IStakingNFT.sol"; import "contracts/libraries/errors/LockupErrors.sol"; import "contracts/libraries/lockup/AccessControlled.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; import "contracts/utils/auth/ImmutablePublicStaking.sol"; import "contracts/utils/auth/ImmutableALCA.sol"; import "contracts/utils/EthSafeTransfer.sol"; import "contracts/utils/ERC20SafeTransfer.sol"; import "contracts/BonusPool.sol"; import "contracts/RewardPool.sol"; /** * @notice This contract locks up publicStaking position for a certain period. The position is * transferred to this contract, and the original owner is entitled to collect profits, and unlock * the position. If the position was kept locked until the end of the locking period, the original * owner will be able to get the original position back, plus any profits gained by the position * (e.g from ALCB sale) + a bonus amount based on the amount of shares of the public staking * position. * * Original owner will be able to collect profits from the position normally during the locking * period. However, a certain percentage will be held by the contract and only distributed after the * locking period has finished and the user unlocks. * * Original owner will be able to unlock position (partially or fully) before the locking period has * finished. The owner will able to decide which will be the amount unlocked earlier (called * exitAmount). In case of full exit (exitAmount == positionShares), the owner will not get the * percentage of profits of that position that are held by this contract and he will not receive any * bonus amount. In case, of partial exit (exitAmount < positionShares), the owner will be loosing * only the profits + bonus relative to the exiting amount. * * * @dev deployed by the AliceNetFactory contract */ /// @custom:salt Lockup /// @custom:deploy-type deployCreateAndRegister /// @custom:deploy-group lockup /// @custom:deploy-group-index 0 contract Lockup is ImmutablePublicStaking, ImmutableALCA, ERC20SafeTransfer, EthSafeTransfer, ERC721Holder { enum State { PreLock, InLock, PostLock } uint256 public constant SCALING_FACTOR = 10 ** 18; uint256 public constant FRACTION_RESERVED = SCALING_FACTOR / 5; // rewardPool contract address address internal immutable _rewardPool; // bonusPool contract address address internal immutable _bonusPool; // block on which lock starts uint256 internal immutable _startBlock; // block on which lock ends uint256 internal immutable _endBlock; // Total Locked describes the total number of ALCA locked in this contract. // Since no accumulators are used this is tracked to allow proportionate // payouts. uint256 internal _totalSharesLocked; // _ownerOf tracks who is the owner of a tokenID locked in this contract // mapping(tokenID -> owner). mapping(uint256 => address) internal _ownerOf; // _tokenOf is the inverse of ownerOf and returns the owner given the tokenID // users are only allowed 1 position per account, mapping (owner -> tokenID). mapping(address => uint256) internal _tokenOf; // maps and index to a tokenID for iterable counting i.e (index -> tokenID). // Stop iterating when token id is zero. Must use tail insert to delete or else // pagination will end early. mapping(uint256 => uint256) internal _tokenIDs; // lookup index by ID (tokenID -> index). mapping(uint256 => uint256) internal _reverseTokenIDs; // tracks the number of tokenIDs this contract holds. uint256 internal _lenTokenIDs; // support mapping to keep track all the ethereum owed to user to be // redistributed in the postLock phase during safe mode. mapping(address => uint256) internal _rewardEth; // support mapping to keep track all the token owed to user to be // redistributed in the postLock phase during safe mode. mapping(address => uint256) internal _rewardTokens; // Flag to determine if we are in the postLock phase safe or unsafe, i.e if // users are allowed to withdrawal or not. All profits need to be collect by all // positions before setting the safe mode. bool public payoutSafe; // offset for pagination when collecting the profits in the postLock unsafe // phase. Many people may call aggregateProfits until all rewards has been // collected. uint256 internal _tokenIDOffset; event EarlyExit(address to_, uint256 tokenID_); event NewLockup(address from_, uint256 tokenID_); modifier onlyPreLock() { if (_getState() != State.PreLock) { revert LockupErrors.PreLockStateRequired(); } _; } modifier excludePreLock() { if (_getState() == State.PreLock) { revert LockupErrors.PreLockStateNotAllowed(); } _; } modifier onlyPostLock() { if (_getState() != State.PostLock) { revert LockupErrors.PostLockStateRequired(); } _; } modifier excludePostLock() { if (_getState() == State.PostLock) { revert LockupErrors.PostLockStateNotAllowed(); } _; } modifier onlyPayoutSafe() { if (!payoutSafe) { revert LockupErrors.PayoutUnsafe(); } _; } modifier onlyPayoutUnSafe() { if (payoutSafe) { revert LockupErrors.PayoutSafe(); } _; } modifier onlyInLock() { if (_getState() != State.InLock) { revert LockupErrors.InLockStateRequired(); } _; } constructor( uint256 enrollmentPeriod_, uint256 lockDuration_, uint256 totalBonusAmount_ ) ImmutableFactory(msg.sender) ImmutablePublicStaking() ImmutableALCA() { RewardPool rewardPool = new RewardPool( _alcaAddress(), _factoryAddress(), totalBonusAmount_ ); _rewardPool = address(rewardPool); _bonusPool = rewardPool.getBonusPoolAddress(); _startBlock = block.number + enrollmentPeriod_; _endBlock = _startBlock + lockDuration_; } /// @dev only publicStaking and rewardPool are allowed to send ether to this contract receive() external payable { if (msg.sender != _publicStakingAddress() && msg.sender != _rewardPool) { revert LockupErrors.AddressNotAllowedToSendEther(); } } /// @notice callback function called by the ERC721.safeTransfer. On safe transfer of /// publicStaking positions to this contract, it will be performing checks and in case everything /// is fine, that position will be locked in name of the original owner that performed the /// transfer /// @dev publicStaking positions can only be safe transferred to this contract on PreLock phase /// (enrollment phase) /// @param from_ original owner of the publicStaking Position. The position will locked for this /// address /// @param tokenID_ The publicStaking tokenID that will be locked up function onERC721Received( address, address from_, uint256 tokenID_, bytes memory ) public override onlyPreLock returns (bytes4) { if (msg.sender != _publicStakingAddress()) { revert LockupErrors.OnlyStakingNFTAllowed(); } _lockFromTransfer(tokenID_, from_); return this.onERC721Received.selector; } /// @notice transfer and locks a pre-approved publicStaking position to this contract /// @dev can only be called at PreLock phase (enrollment phase) /// @param tokenID_ The publicStaking tokenID that will be locked up function lockFromApproval(uint256 tokenID_) public { // msg.sender already approved transfer, so contract can safeTransfer to itself; by doing // this onERC721Received is called as part of the chain of transfer methods hence the checks // run from within onERC721Received IERC721Transferable(_publicStakingAddress()).safeTransferFrom( msg.sender, address(this), tokenID_ ); } /// @notice locks a position that was already transferred to this contract without using /// safeTransfer. WARNING: SHOULD ONLY BE USED FROM SMART CONTRACT THAT TRANSFERS A POSITION AND /// CALL THIS METHOD RIGHT IN SEQUENCE /// @dev can only be called at PreLock phase (enrollment phase) /// @param tokenID_ The publicStaking tokenID that will be locked up /// @param tokenOwner_ The address that will be used as the user entitled to that position function lockFromTransfer(uint256 tokenID_, address tokenOwner_) public onlyPreLock { _lockFromTransfer(tokenID_, tokenOwner_); } /// @notice collects all profits from a position locked up by this contract. Only a certain /// amount of the profits will be sent, the rest will held by the contract and released at the /// final unlock. /// @dev can only be called if the PostLock phase has not began /// @dev can only be called by position's entitled owner /// @return payoutEth the amount of eth that was sent to user /// @return payoutToken the amount of ALCA that was sent to user function collectAllProfits() public excludePostLock returns (uint256 payoutEth, uint256 payoutToken) { return _collectAllProfits(_payableSender(), _validateAndGetTokenId()); } /// @notice function to partially or fully unlock a locked position. The entitled owner will /// able to decide which will be the amount unlocked earlier (exitValue_). In case of full exit /// (exitValue_ == positionShares), the owner will not get the percentage of profits of that /// position that are held by this contract and he will not receive any bonus amount. In case, of /// partial exit (exitValue_< positionShares), the owner will be loosing only the profits + bonus /// relative to the exiting amount. The owner may choose via stakeExit_ boolean if the ALCA will be /// sent a new publicStaking position or as ALCA directly to his address. /// @dev can only be called if the PostLock phase has not began /// @dev can only be called by position's entitled owner /// @param exitValue_ The amount in which the user wants to unlock earlier /// @param stakeExit_ Flag to decide the ALCA will be sent directly or staked as new /// publicStaking position /// @return payoutEth the amount of eth that was sent to user discounting the reserved amount /// @return payoutToken the amount of ALCA discounting the reserved amount that was sent or /// staked as new position to the user function unlockEarly( uint256 exitValue_, bool stakeExit_ ) public excludePostLock returns (uint256 payoutEth, uint256 payoutToken) { uint256 tokenID = _validateAndGetTokenId(); // get the number of shares and check validity uint256 shares = _getNumShares(tokenID); if (exitValue_ > shares) { revert LockupErrors.InsufficientBalanceForEarlyExit(exitValue_, shares); } // burn the existing position (payoutEth, payoutToken) = IStakingNFT(_publicStakingAddress()).burn(tokenID); // separating alca reward from alca shares payoutToken -= shares; // blank old record _ownerOf[tokenID] = address(0); // create placeholder uint256 newTokenID; // find shares delta and mint new position uint256 remainingShares = shares - exitValue_; if (remainingShares > 0) { // approve the transfer of ALCA in order to mint the publicStaking position IERC20(_alcaAddress()).approve(_publicStakingAddress(), remainingShares); // burn profits contain staked position... so sub it out newTokenID = IStakingNFT(_publicStakingAddress()).mint(remainingShares); // set new records _ownerOf[newTokenID] = msg.sender; _replaceTokenID(tokenID, newTokenID); } else { _removeTokenID(tokenID); } // safe because newTokenId is zero if shares == exitValue _tokenOf[msg.sender] = newTokenID; _totalSharesLocked -= exitValue_; (payoutEth, payoutToken) = _distributeAllProfits( _payableSender(), payoutEth, payoutToken, exitValue_, stakeExit_ ); emit EarlyExit(msg.sender, tokenID); } /// @notice aggregateProfits iterate alls locked positions and collect their profits before /// allowing withdraws/unlocks. This step is necessary to make sure that the correct reserved /// amount is in the rewardPool before allowing unlocks. This function will not send any ether or /// ALCA to users, since this can be very dangerous (specially on a loop). Instead all the /// assets that are not sent to the rewardPool are held in the lockup contract, and the right /// balance is stored per position owner. All the value will be send to the owner address at the /// call of the `{unlock()}` function. This function can only be called after the locking period /// has finished. Anyone can call this function. function aggregateProfits() public onlyPayoutUnSafe onlyPostLock { // get some gas cost tracking setup uint256 gasStart = gasleft(); uint256 gasLoop; // start index where we left off plus one uint256 i = _tokenIDOffset + 1; // for loop that will exit when one of following is true the gas remaining is less than 5x // the estimated per iteration cost or the iterator is done for (; ; i++) { (uint256 tokenID, bool ok) = _getTokenIDAtIndex(i); if (!ok) { // if we get here, iteration of array is done and we can move on with life and set // payoutSafe since all payouts have been recorded payoutSafe = true; // burn the bonus Position and send the bonus to the rewardPool contract BonusPool(payable(_bonusPool)).terminate(); break; } address payable acct = _getOwnerOf(tokenID); _collectAllProfits(acct, tokenID); uint256 gasRem = gasleft(); if (gasLoop == 0) { // record gas iteration estimate if not done gasLoop = gasStart - gasRem; // give 5x multi on it to ensure even an overpriced element by 2x the normal // cost will still pass gasLoop = 5 * gasLoop; // accounts for state writes on exit gasLoop = gasLoop + 10000; } else if (gasRem <= gasLoop) { // if we are below cutoff break break; } } _tokenIDOffset = i; } /// @notice unlocks a locked position and collect all kind of profits (bonus shares, held /// rewards etc). Can only be called after the locking period has finished and {aggregateProfits} /// has been executed for positions. Can only be called by the user entitled to a position /// (address that locked a position). This function can only be called after the locking period /// has finished and {aggregateProfits()} has been executed for all locked positions. /// @param to_ destination address were the profits, shares will be sent /// @param stakeExit_ boolean flag indicating if the ALCA should be returned directly or staked /// into a new publicStaking position. /// @return payoutEth the ether amount deposited to an address after unlock /// @return payoutToken the ALCA amount staked or sent to an address after unlock function unlock( address to_, bool stakeExit_ ) public onlyPostLock onlyPayoutSafe returns (uint256 payoutEth, uint256 payoutToken) { uint256 tokenID = _validateAndGetTokenId(); uint256 shares = _getNumShares(tokenID); bool isLastPosition = _lenTokenIDs == 1; (payoutEth, payoutToken) = _burnLockedPosition(tokenID, msg.sender); (uint256 accumulatedRewardEth, uint256 accumulatedRewardToken) = RewardPool(_rewardPool) .payout(_totalSharesLocked, shares, isLastPosition); payoutEth += accumulatedRewardEth; payoutToken += accumulatedRewardToken; (uint256 aggregatedEth, uint256 aggregatedToken) = _withdrawalAggregatedAmount(msg.sender); payoutEth += aggregatedEth; payoutToken += aggregatedToken; _transferEthAndTokensWithReStake(to_, payoutEth, payoutToken, stakeExit_); } /// @notice gets the address that is entitled to unlock/collect profits for a position. I.e the /// address that locked this position into this contract. /// @param tokenID_ the position Id to retrieve the owner /// @return the owner address of a position. Returns 0 if a position is not locked into this /// contract function ownerOf(uint256 tokenID_) public view returns (address payable) { return _getOwnerOf(tokenID_); } /// @notice gets the positionID that an address is entitled to unlock/collect profits. I.e /// position that an address locked into this contract. /// @param acct_ address to retrieve a position (tokenID) /// @return the position ID (tokenID) of the position that the address locked into this /// contract. If an address doesn't possess any locked position in this contract, this function /// returns 0 function tokenOf(address acct_) public view returns (uint256) { return _getTokenOf(acct_); } /// @notice gets the total number of positions locked into this contract. Can be used with /// {getIndexByTokenId} and {getPositionByIndex} to get all publicStaking positions held by this /// contract. /// @return the total number of positions locked into this contract function getCurrentNumberOfLockedPositions() public view returns (uint256) { return _lenTokenIDs; } /// @notice gets the position referenced by an index in the enumerable mapping implemented by /// this contract. Can be used {getIndexByTokenId} to get all positions IDs locked by this /// contract. /// @param index_ the index to get the positionID /// @return the tokenId referenced by an index the enumerable mapping (indexes start at 1). If /// the index doesn't exists this function returns 0 function getPositionByIndex(uint256 index_) public view returns (uint256) { return _tokenIDs[index_]; } /// @notice gets the index of a position in the enumerable mapping implemented by this contract. /// Can be used {getPositionByIndex} to get all positions IDs locked by this contract. /// @param tokenID_ the position ID to get index for /// @return the index of a position in the enumerable mapping (indexes start at 1). If the /// tokenID is not locked into this contract this function returns 0 function getIndexByTokenId(uint256 tokenID_) public view returns (uint256) { return _reverseTokenIDs[tokenID_]; } /// @notice gets the ethereum block where the locking period will start. This block is also /// when the enrollment period will finish. I.e after this block we don't allow new positions to /// be locked. /// @return the ethereum block where the locking period will start function getLockupStartBlock() public view returns (uint256) { return _startBlock; } /// @notice gets the ethereum block where the locking period will end. After this block /// aggregateProfit has to be called to enable the unlock period. /// @return the ethereum block where the locking period will end function getLockupEndBlock() public view returns (uint256) { return _endBlock; } /// @notice gets the ether and ALCA balance owed to a user after aggregateProfit has been /// called. This funds are send after final unlock. /// @return user ether balance held by this contract /// @return user ALCA balance held by this contract function getTemporaryRewardBalance(address user_) public view returns (uint256, uint256) { return _getTemporaryRewardBalance(user_); } /// @notice gets the RewardPool contract address /// @return the reward pool contract address function getRewardPoolAddress() public view returns (address) { return _rewardPool; } /// @notice gets the bonusPool contract address /// @return the bonusPool contract address function getBonusPoolAddress() public view returns (address) { return _bonusPool; } /// @notice gets the current amount of ALCA that is locked in this contract, after all early exits /// @return the amount of ALCA that is currently locked in this contract function getTotalSharesLocked() public view returns (uint256) { return _totalSharesLocked; } /// @notice gets the current state of the lockup (preLock, InLock, PostLock) /// @return the current state of the lockup contract function getState() public view returns (State) { return _getState(); } /// @notice estimate the (liquid) income that can be collected from locked positions via /// {collectAllProfits} /// @dev this functions deducts the reserved amount that is sent to rewardPool contract function estimateProfits( uint256 tokenID_ ) public view returns (uint256 payoutEth, uint256 payoutToken) { // check if the position owned by this contract _verifyLockedPosition(tokenID_); (payoutEth, payoutToken) = IStakingNFT(_publicStakingAddress()).estimateAllProfits( tokenID_ ); (uint256 reserveEth, uint256 reserveToken) = _computeReservedAmount(payoutEth, payoutToken); payoutEth -= reserveEth; payoutToken -= reserveToken; } /// @notice function to estimate the final amount of ALCA and ether that a locked /// position will receive at the end of the locking period. Depending on the preciseEstimation_ flag this function can be an imprecise approximation, /// the real amount can differ especially as user's collect profits in the middle of the locking /// period. Passing preciseEstimation_ as true will give a precise estimate since all profits are aggregated in a loop, /// hence is optional as it can be expensive if called as part of a smart contract transaction that alters state. After the locking /// period has finished and aggregateProfits has been executed for all locked positions the estimate will also be accurate. /// @dev this function is just an approximation when preciseEstimation_ is false, the real amount can differ! /// @param tokenID_ The token to check for the final profits. /// @param preciseEstimation_ whether to use the precise estimation or the approximation (precise is expensive due to looping so use wisely) /// @return positionShares_ the positions ALCA shares /// @return payoutEth_ the ether amount that the position will receive as profit /// @return payoutToken_ the ALCA amount that the position will receive as profit function estimateFinalBonusWithProfits( uint256 tokenID_, bool preciseEstimation_ ) public view returns (uint256 positionShares_, uint256 payoutEth_, uint256 payoutToken_) { // check if the position owned by this contract _verifyLockedPosition(tokenID_); positionShares_ = _getNumShares(tokenID_); uint256 currentSharesLocked = _totalSharesLocked; // get the bonus amount + any profit from the bonus staked position (payoutEth_, payoutToken_) = BonusPool(payable(_bonusPool)).estimateBonusAmountWithReward( currentSharesLocked, positionShares_ ); // get the cumulative rewards held in the rewardPool so far. In the case that // aggregateProfits has not been ran, the amount returned by this call may not be precise, // since only some users may have been collected until this point, in which case // preciseEstimation_ can be passed as true to get a precise estimate. (uint256 rewardEthProfit, uint256 rewardTokenProfit) = RewardPool(_rewardPool) .estimateRewards(currentSharesLocked, positionShares_); payoutEth_ += rewardEthProfit; payoutToken_ += rewardTokenProfit; uint256 reservedEth; uint256 reservedToken; // if aggregateProfits has been called (indicated by the payoutSafe flag), this calculation is not needed if (preciseEstimation_ && !payoutSafe) { // get this positions share based on all user profits aggregated (NOTE: precise but expensive due to the loop) (reservedEth, reservedToken) = _estimateUserAggregatedProfits( positionShares_, currentSharesLocked ); } else { // get any future profit that will be held in the rewardPool for this position (uint256 positionEthProfit, uint256 positionTokenProfit) = IStakingNFT( _publicStakingAddress() ).estimateAllProfits(tokenID_); (reservedEth, reservedToken) = _computeReservedAmount( positionEthProfit, positionTokenProfit ); } payoutEth_ += reservedEth; payoutToken_ += reservedToken; // get any eth and token held by this contract as result of the call to the aggregateProfit // function (uint256 aggregatedEth, uint256 aggregatedTokens) = _getTemporaryRewardBalance( _getOwnerOf(tokenID_) ); payoutEth_ += aggregatedEth; payoutToken_ += aggregatedTokens; } /// @notice return the percentage amount that is held from the locked positions /// @dev this value is scaled by 100. Therefore the values are from 0-100% /// @return the percentage amount that is held from the locked positions function getReservedPercentage() public pure returns (uint256) { return (100 * FRACTION_RESERVED) / SCALING_FACTOR; } /// @notice gets the fraction of the amount that is reserved to reward pool /// @return the calculated reserved amount function getReservedAmount(uint256 amount_) public pure returns (uint256) { return (amount_ * FRACTION_RESERVED) / SCALING_FACTOR; } function _lockFromTransfer(uint256 tokenID_, address tokenOwner_) internal { _validateEntry(tokenID_, tokenOwner_); _checkTokenTransfer(tokenID_); _lock(tokenID_, tokenOwner_); } function _lock(uint256 tokenID_, address tokenOwner_) internal { uint256 shares = _verifyPositionAndGetShares(tokenID_); _totalSharesLocked += shares; _tokenOf[tokenOwner_] = tokenID_; _ownerOf[tokenID_] = tokenOwner_; _newTokenID(tokenID_); emit NewLockup(tokenOwner_, tokenID_); } function _burnLockedPosition( uint256 tokenID_, address tokenOwner_ ) internal returns (uint256 payoutEth, uint256 payoutToken) { // burn the old position (payoutEth, payoutToken) = IStakingNFT(_publicStakingAddress()).burn(tokenID_); //delete tokenID_ from iterable tokenID mapping _removeTokenID(tokenID_); delete (_tokenOf[tokenOwner_]); delete (_ownerOf[tokenID_]); } function _withdrawalAggregatedAmount( address account_ ) internal returns (uint256 payoutEth, uint256 payoutToken) { // case of we are sending out final pay based on request just pay all payoutEth = _rewardEth[account_]; payoutToken = _rewardTokens[account_]; _rewardEth[account_] = 0; _rewardTokens[account_] = 0; } function _collectAllProfits( address payable acct_, uint256 tokenID_ ) internal returns (uint256 payoutEth, uint256 payoutToken) { (payoutEth, payoutToken) = IStakingNFT(_publicStakingAddress()).collectAllProfits(tokenID_); return _distributeAllProfits(acct_, payoutEth, payoutToken, 0, false); } function _distributeAllProfits( address payable acct_, uint256 payoutEth_, uint256 payoutToken_, uint256 additionalTokens, bool stakeExit_ ) internal returns (uint256 userPayoutEth, uint256 userPayoutToken) { State state = _getState(); bool localPayoutSafe = payoutSafe; userPayoutEth = payoutEth_; userPayoutToken = payoutToken_; (uint256 reservedEth, uint256 reservedToken) = _computeReservedAmount( payoutEth_, payoutToken_ ); userPayoutEth -= reservedEth; userPayoutToken -= reservedToken; // send tokens to reward pool _depositFundsInRewardPool(reservedEth, reservedToken); // in case this is being called by {aggregateProfits()} we don't send any asset to the // users, we just store the owed amounts on state if (!localPayoutSafe && state == State.PostLock) { // we should not send here and should instead track to local mapping as // otherwise a single bad user could block exit operations for all other users // by making the send to their account fail via a contract _rewardEth[acct_] += userPayoutEth; _rewardTokens[acct_] += userPayoutToken; return (userPayoutEth, userPayoutToken); } // adding any additional token that should be sent to the user (e.g shares from // burned position on early exit) userPayoutToken += additionalTokens; _transferEthAndTokensWithReStake(acct_, userPayoutEth, userPayoutToken, stakeExit_); return (userPayoutEth, userPayoutToken); } function _transferEthAndTokensWithReStake( address to_, uint256 payoutEth_, uint256 payoutToken_, bool stakeExit_ ) internal { if (stakeExit_) { IERC20(_alcaAddress()).approve(_publicStakingAddress(), payoutToken_); IStakingNFT(_publicStakingAddress()).mintTo(to_, payoutToken_, 0); } else { _safeTransferERC20(IERC20Transferable(_alcaAddress()), to_, payoutToken_); } _safeTransferEth(to_, payoutEth_); } function _newTokenID(uint256 tokenID_) internal { uint256 index = _lenTokenIDs + 1; _tokenIDs[index] = tokenID_; _reverseTokenIDs[tokenID_] = index; _lenTokenIDs = index; } function _replaceTokenID(uint256 oldID_, uint256 newID_) internal { uint256 index = _reverseTokenIDs[oldID_]; _reverseTokenIDs[oldID_] = 0; _tokenIDs[index] = newID_; _reverseTokenIDs[newID_] = index; } function _removeTokenID(uint256 tokenID_) internal { uint256 initialLen = _lenTokenIDs; if (initialLen == 0) { return; } if (initialLen == 1) { uint256 index = _reverseTokenIDs[tokenID_]; _reverseTokenIDs[tokenID_] = 0; _tokenIDs[index] = 0; _lenTokenIDs = 0; return; } // pop the tail uint256 tailTokenID = _tokenIDs[initialLen]; _tokenIDs[initialLen] = 0; _lenTokenIDs = initialLen - 1; if (tailTokenID == tokenID_) { // element was tail, so we are done _reverseTokenIDs[tailTokenID] = 0; return; } // use swap logic to re-insert tail over other position _replaceTokenID(tokenID_, tailTokenID); } function _depositFundsInRewardPool(uint256 reservedEth_, uint256 reservedToken_) internal { _safeTransferERC20(IERC20Transferable(_alcaAddress()), _rewardPool, reservedToken_); RewardPool(_rewardPool).deposit{value: reservedEth_}(reservedToken_); } function _getNumShares(uint256 tokenID_) internal view returns (uint256 shares) { (shares, , , , ) = IStakingNFT(_publicStakingAddress()).getPosition(tokenID_); } function _estimateTotalAggregatedProfits() internal view returns (uint256 payoutEth, uint256 payoutToken) { for (uint256 i = 1; i <= _lenTokenIDs; i++) { (uint256 tokenID, ) = _getTokenIDAtIndex(i); (uint256 stakingProfitEth, uint256 stakingProfitToken) = IStakingNFT( _publicStakingAddress() ).estimateAllProfits(tokenID); (uint256 reserveEth, uint256 reserveToken) = _computeReservedAmount( stakingProfitEth, stakingProfitToken ); payoutEth += reserveEth; payoutToken += reserveToken; } } function _estimateUserAggregatedProfits( uint256 userShares_, uint256 totalShares_ ) internal view returns (uint256 payoutEth, uint256 payoutToken) { (payoutEth, payoutToken) = _estimateTotalAggregatedProfits(); payoutEth = (payoutEth * userShares_) / totalShares_; payoutToken = (payoutToken * userShares_) / totalShares_; } function _payableSender() internal view returns (address payable) { return payable(msg.sender); } function _getTokenIDAtIndex(uint256 index_) internal view returns (uint256 tokenID, bool ok) { tokenID = _tokenIDs[index_]; return (tokenID, tokenID > 0); } function _checkTokenTransfer(uint256 tokenID_) internal view { if (IERC721(_publicStakingAddress()).ownerOf(tokenID_) != address(this)) { revert LockupErrors.ContractDoesNotOwnTokenID(tokenID_); } } function _validateEntry(uint256 tokenID_, address sender_) internal view { if (_getOwnerOf(tokenID_) != address(0)) { revert LockupErrors.TokenIDAlreadyClaimed(tokenID_); } if (_getTokenOf(sender_) != 0) { revert LockupErrors.AddressAlreadyLockedUp(); } } function _validateAndGetTokenId() internal view returns (uint256) { // get tokenID of caller uint256 tokenID = _getTokenOf(msg.sender); if (tokenID == 0) { revert LockupErrors.UserHasNoPosition(); } return tokenID; } function _verifyLockedPosition(uint256 tokenID_) internal view { if (_getOwnerOf(tokenID_) == address(0)) { revert LockupErrors.TokenIDNotLocked(tokenID_); } } // Gets the shares of position and checks if a position exists and if we can collect the // profits after the _endBlock. function _verifyPositionAndGetShares(uint256 tokenId_) internal view returns (uint256) { // get position fails if the position doesn't exists! (uint256 shares, , uint256 withdrawFreeAfter, , ) = IStakingNFT(_publicStakingAddress()) .getPosition(tokenId_); if (withdrawFreeAfter >= _endBlock) { revert LockupErrors.InvalidPositionWithdrawPeriod(withdrawFreeAfter, _endBlock); } return shares; } function _getState() internal view returns (State) { if (block.number < _startBlock) { return State.PreLock; } if (block.number < _endBlock) { return State.InLock; } return State.PostLock; } function _getOwnerOf(uint256 tokenID_) internal view returns (address payable) { return payable(_ownerOf[tokenID_]); } function _getTokenOf(address acct_) internal view returns (uint256) { return _tokenOf[acct_]; } function _getTemporaryRewardBalance(address user_) internal view returns (uint256, uint256) { return (_rewardEth[user_], _rewardTokens[user_]); } function _computeReservedAmount( uint256 payoutEth_, uint256 payoutToken_ ) internal pure returns (uint256 reservedEth, uint256 reservedToken) { reservedEth = (payoutEth_ * FRACTION_RESERVED) / SCALING_FACTOR; reservedToken = (payoutToken_ * FRACTION_RESERVED) / SCALING_FACTOR; } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "contracts/interfaces/IStakingNFT.sol"; import "contracts/libraries/lockup/AccessControlled.sol"; import "contracts/libraries/errors/LockupErrors.sol"; import "contracts/BonusPool.sol"; import "contracts/utils/EthSafeTransfer.sol"; import "contracts/utils/ERC20SafeTransfer.sol"; /** * @notice RewardPool holds all ether and ALCA that is part of reserved amount * of rewards on base positions. * @dev deployed by the lockup contract */ contract RewardPool is AccessControlled, EthSafeTransfer, ERC20SafeTransfer { address internal immutable _alca; address internal immutable _lockupContract; address internal immutable _bonusPool; uint256 internal _ethReserve; uint256 internal _tokenReserve; constructor(address alca_, address aliceNetFactory_, uint256 totalBonusAmount_) { _bonusPool = address( new BonusPool(aliceNetFactory_, msg.sender, address(this), totalBonusAmount_) ); _lockupContract = msg.sender; _alca = alca_; } /// @notice function that receives ether and updates the token and ether reservers. The ALCA /// tokens has to be sent prior the call to this function. /// @dev can only be called by the bonusPool or lockup contracts /// @param numTokens_ number of ALCA tokens transferred to this contract before the call to this /// function function deposit(uint256 numTokens_) public payable onlyLockupOrBonus { _tokenReserve += numTokens_; _ethReserve += msg.value; } /// @notice function to pay a user after the lockup period. If a user is the last exiting the /// lockup it will receive any remainders kept by this contract by integer division errors. /// @dev only can be called by the lockup contract /// @param totalShares_ the total shares at the end of the lockup period /// @param userShares_ the user shares /// @param isLastPosition_ if the user is the last position exiting from the lockup contract function payout( uint256 totalShares_, uint256 userShares_, bool isLastPosition_ ) public onlyLockup returns (uint256 proportionalEth, uint256 proportionalTokens) { if (totalShares_ == 0 || userShares_ > totalShares_) { revert LockupErrors.InvalidTotalSharesValue(); } // last position gets any remainder left on this contract if (isLastPosition_) { proportionalEth = address(this).balance; proportionalTokens = IERC20(_alca).balanceOf(address(this)); } else { (proportionalEth, proportionalTokens) = _computeProportions(totalShares_, userShares_); } _safeTransferERC20(IERC20Transferable(_alca), _lockupContract, proportionalTokens); _safeTransferEth(payable(_lockupContract), proportionalEth); } /// @notice gets the bonusPool contract address /// @return the bonusPool contract address function getBonusPoolAddress() public view returns (address) { return _getBonusPoolAddress(); } /// @notice gets the lockup contract address /// @return the lockup contract address function getLockupContractAddress() public view returns (address) { return _getLockupContractAddress(); } /// @notice get the ALCA reserve kept by this contract /// @return the ALCA reserve kept by this contract function getTokenReserve() public view returns (uint256) { return _tokenReserve; } /// @notice get the ether reserve kept by this contract /// @return the ether reserve kept by this contract function getEthReserve() public view returns (uint256) { return _ethReserve; } /// @notice estimates the final amount that a user will receive from the assets hold by this /// contract after end of the lockup period. /// @param totalShares_ total number of shares locked by the lockup contract /// @param userShares_ the user's shares /// @return proportionalEth The ether that a user will receive at the end of the lockup period /// @return proportionalTokens The ALCA that a user will receive at the end of the lockup period function estimateRewards( uint256 totalShares_, uint256 userShares_ ) public view returns (uint256 proportionalEth, uint256 proportionalTokens) { if (totalShares_ == 0 || userShares_ > totalShares_) { revert LockupErrors.InvalidTotalSharesValue(); } return _computeProportions(totalShares_, userShares_); } function _computeProportions( uint256 totalShares_, uint256 userShares_ ) internal view returns (uint256 proportionalEth, uint256 proportionalTokens) { proportionalEth = (_ethReserve * userShares_) / totalShares_; proportionalTokens = (_tokenReserve * userShares_) / totalShares_; } function _getLockupContractAddress() internal view override returns (address) { return _lockupContract; } function _getBonusPoolAddress() internal view override returns (address) { return _bonusPool; } function _getRewardPoolAddress() internal view override returns (address) { return address(this); } }
// This file is auto-generated by hardhat generate-immutable-auth-contract task. DO NOT EDIT. // SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/utils/DeterministicAddress.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; import "contracts/interfaces/IAliceNetFactory.sol"; abstract contract ImmutableALCA is ImmutableFactory { address private immutable _alca; error OnlyALCA(address sender, address expected); modifier onlyALCA() { if (msg.sender != _alca) { revert OnlyALCA(msg.sender, _alca); } _; } constructor() { _alca = IAliceNetFactory(_factoryAddress()).lookup(_saltForALCA()); } function _alcaAddress() internal view returns (address) { return _alca; } function _saltForALCA() internal pure returns (bytes32) { return 0x414c434100000000000000000000000000000000000000000000000000000000; } }
// This file is auto-generated by hardhat generate-immutable-auth-contract task. DO NOT EDIT. // SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/utils/DeterministicAddress.sol"; abstract contract ImmutableFactory is DeterministicAddress { address private immutable _factory; error OnlyFactory(address sender, address expected); modifier onlyFactory() { if (msg.sender != _factory) { revert OnlyFactory(msg.sender, _factory); } _; } constructor(address factory_) { _factory = factory_; } function _factoryAddress() internal view returns (address) { return _factory; } }
// This file is auto-generated by hardhat generate-immutable-auth-contract task. DO NOT EDIT. // SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/utils/DeterministicAddress.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; abstract contract ImmutableFoundation is ImmutableFactory { address private immutable _foundation; error OnlyFoundation(address sender, address expected); modifier onlyFoundation() { if (msg.sender != _foundation) { revert OnlyFoundation(msg.sender, _foundation); } _; } constructor() { _foundation = getMetamorphicContractAddress( 0x466f756e646174696f6e00000000000000000000000000000000000000000000, _factoryAddress() ); } function _foundationAddress() internal view returns (address) { return _foundation; } function _saltForFoundation() internal pure returns (bytes32) { return 0x466f756e646174696f6e00000000000000000000000000000000000000000000; } }
// This file is auto-generated by hardhat generate-immutable-auth-contract task. DO NOT EDIT. // SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/utils/DeterministicAddress.sol"; import "contracts/utils/auth/ImmutableFactory.sol"; abstract contract ImmutablePublicStaking is ImmutableFactory { address private immutable _publicStaking; error OnlyPublicStaking(address sender, address expected); modifier onlyPublicStaking() { if (msg.sender != _publicStaking) { revert OnlyPublicStaking(msg.sender, _publicStaking); } _; } constructor() { _publicStaking = getMetamorphicContractAddress( 0x5075626c69635374616b696e6700000000000000000000000000000000000000, _factoryAddress() ); } function _publicStakingAddress() internal view returns (address) { return _publicStaking; } function _saltForPublicStaking() internal pure returns (bytes32) { return 0x5075626c69635374616b696e6700000000000000000000000000000000000000; } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; abstract contract DeterministicAddress { function getMetamorphicContractAddress( bytes32 _salt, address _factory ) public pure returns (address) { // byte code for metamorphic contract // 6020363636335afa1536363636515af43d36363e3d36f3 bytes32 metamorphicContractBytecodeHash_ = 0x1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be; return address( uint160( uint256( keccak256( abi.encodePacked( hex"ff", _factory, _salt, metamorphicContractBytecodeHash_ ) ) ) ) ); } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/interfaces/IERC20Transferable.sol"; import "contracts/libraries/errors/ERC20SafeTransferErrors.sol"; abstract contract ERC20SafeTransfer { // _safeTransferFromERC20 performs a transferFrom call against an erc20 contract in a safe manner // by reverting on failure // this function will return without performing a call or reverting // if amount_ is zero function _safeTransferFromERC20( IERC20Transferable contract_, address sender_, uint256 amount_ ) internal { if (amount_ == 0) { return; } if (address(contract_) == address(0x0)) { revert ERC20SafeTransferErrors.CannotCallContractMethodsOnZeroAddress(); } bool success = contract_.transferFrom(sender_, address(this), amount_); if (!success) { revert ERC20SafeTransferErrors.Erc20TransferFailed( address(contract_), sender_, address(this), amount_ ); } } // _safeTransferERC20 performs a transfer call against an erc20 contract in a safe manner // by reverting on failure // this function will return without performing a call or reverting // if amount_ is zero function _safeTransferERC20( IERC20Transferable contract_, address to_, uint256 amount_ ) internal { if (amount_ == 0) { return; } if (address(contract_) == address(0x0)) { revert ERC20SafeTransferErrors.CannotCallContractMethodsOnZeroAddress(); } bool success = contract_.transfer(to_, amount_); if (!success) { revert ERC20SafeTransferErrors.Erc20TransferFailed( address(contract_), address(this), to_, amount_ ); } } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/libraries/errors/ETHSafeTransferErrors.sol"; abstract contract EthSafeTransfer { /// @notice _safeTransferEth performs a transfer of Eth using the call /// method / this function is resistant to breaking gas price changes and / /// performs call in a safe manner by reverting on failure. / this function /// will return without performing a call or reverting, / if amount_ is zero function _safeTransferEth(address to_, uint256 amount_) internal { if (amount_ == 0) { return; } if (to_ == address(0)) { revert ETHSafeTransferErrors.CannotTransferToZeroAddress(); } address payable caller = payable(to_); (bool success, ) = caller.call{value: amount_}(""); if (!success) { revert ETHSafeTransferErrors.EthTransferFailed(address(this), to_, amount_); } } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/utils/MagicValue.sol"; import "contracts/interfaces/IMagicEthTransfer.sol"; abstract contract MagicEthTransfer is MagicValue { function _safeTransferEthWithMagic(IMagicEthTransfer to_, uint256 amount_) internal { to_.depositEth{value: amount_}(_getMagic()); } }
// SPDX-License-Identifier: MIT-open-group pragma solidity ^0.8.16; import "contracts/libraries/errors/MagicValueErrors.sol"; abstract contract MagicValue { // _MAGIC_VALUE is a constant that may be used to prevent // a user from calling a dangerous method without significant // effort or ( hopefully ) reading the code to understand the risk uint8 internal constant _MAGIC_VALUE = 42; modifier checkMagic(uint8 magic_) { if (magic_ != _getMagic()) { revert MagicValueErrors.BadMagic(magic_); } _; } // _getMagic returns the magic constant function _getMagic() internal pure returns (uint8) { return _MAGIC_VALUE; } }
{ "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "metadata": { "useLiteralContent": true }, "optimizer": { "enabled": true, "runs": 20000 }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CannotCallContractMethodsOnZeroAddress","type":"error"},{"inputs":[{"internalType":"address","name":"erc20Address","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Erc20TransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"stakingAmount","type":"uint256"},{"internalType":"uint256","name":"migratedAmount","type":"uint256"}],"name":"InvalidStakingAmount","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"expected","type":"address"}],"name":"OnlyALCA","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"expected","type":"address"}],"name":"OnlyFactory","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"expected","type":"address"}],"name":"OnlyPublicStaking","type":"error"},{"inputs":[],"name":"getLegacyTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_salt","type":"bytes32"},{"internalType":"address","name":"_factory","type":"address"}],"name":"getMetamorphicContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"migrationAmount_","type":"uint256"},{"internalType":"uint256","name":"stakingAmount_","type":"uint256"}],"name":"migrateAndStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"migrationAmount_","type":"uint256"},{"internalType":"uint256","name":"stakingAmount_","type":"uint256"}],"name":"migrateStakeAndLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"stakingAmount_","type":"uint256"}],"name":"stakeAndLock","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
61012060405234801561001157600080fd5b50336080526100c56c5075626c69635374616b696e6760981b61003360805190565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201526001600160601b0319606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be6055820181905260009160750160408051601f198184030181529190528051602090910120949350505050565b6001600160a01b0390811660a05260805160405163f39ec1f760e01b815263414c434160e01b600482015291169063f39ec1f790602401602060405180830381865afa158015610119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013d9190610240565b6001600160a01b031660c08190526001600160a01b031663035c70996040518163ffffffff1660e01b8152600401602060405180830381865afa158015610188573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ac9190610240565b6001600160a01b031660e05260805160405163f39ec1f760e01b81526504c6f636b75760d41b60048201526001600160a01b03919091169063f39ec1f790602401602060405180830381865afa15801561020a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061022e9190610240565b6001600160a01b031661010052610270565b60006020828403121561025257600080fd5b81516001600160a01b038116811461026957600080fd5b9392505050565b60805160a05160c05160e05161010051610be16102f66000396000818161014f015281816101c30152818161025a01526102ce015260008181606e015281816103e5015261042201526000818161022d015281816104490152818161054901528181610620015261065001526000818161068c0152610795015260005050610be16000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80631873799a116100505780631873799a146100cc5780633ce55336146100df5780638653a465146100f257600080fd5b8063035c70991461006c578063143f769b146100b7575b600080fd5b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6100ca6100c5366004610aad565b610105565b005b6100ca6100da366004610aad565b61012f565b6100ca6100ed366004610ae0565b610228565b61008e610100366004610b0a565b610331565b600061011133846103de565b905061011e8482846105be565b610128848361064c565b5050505050565b600061013b33846103de565b90506101488482846105be565b60006101747f00000000000000000000000000000000000000000000000000000000000000008461064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff87811660248301529192507f0000000000000000000000000000000000000000000000000000000000000000909116906314567f3790604401600060405180830381600087803b15801561020957600080fd5b505af115801561021d573d6000803e3d6000fd5b505050505050505050565b6102537f000000000000000000000000000000000000000000000000000000000000000033836107c4565b600061027f7f00000000000000000000000000000000000000000000000000000000000000008361064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff85811660248301529192507f0000000000000000000000000000000000000000000000000000000000000000909116906314567f3790604401600060405180830381600087803b15801561031457600080fd5b505af1158015610328573d6000803e3d6000fd5b50505050505050565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be605582018190526000916075016040516020818303038152906040528051906020012060001c9150505b92915050565b600061040b7f000000000000000000000000000000000000000000000000000000000000000084846107c4565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001663095ea7b37f00000000000000000000000000000000000000000000000000000000000000006040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af11580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190610b36565b506040517f0d213d310000000000000000000000000000000000000000000000000000000081523060048201526024810183905273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690630d213d31906044015b6020604051808303816000875af1158015610593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b79190610b58565b9392505050565b81811115610607576040517fa2dd20ef00000000000000000000000000000000000000000000000000000000815260048101829052602481018390526044015b60405180910390fd5b60006106138284610b71565b90508015610646576106467f00000000000000000000000000000000000000000000000000000000000000008583610927565b50505050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f00000000000000000000000000000000000000000000000000000000000000006040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190610b36565b506040517f2baf2acb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260248201849052600060448301527f00000000000000000000000000000000000000000000000000000000000000001690632baf2acb90606401610574565b806000036107d157505050565b73ffffffffffffffffffffffffffffffffffffffff831661081e576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015230602483015260448201839052600091908516906323b872dd906064016020604051808303816000875af115801561089d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c19190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015284166024820152306044820152606481018390526084016105fe565b8060000361093457505050565b73ffffffffffffffffffffffffffffffffffffffff8316610981576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390526000919085169063a9059cbb906044016020604051808303816000875af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e9190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015230602483015284166044820152606481018390526084016105fe565b803573ffffffffffffffffffffffffffffffffffffffff81168114610aa857600080fd5b919050565b600080600060608486031215610ac257600080fd5b610acb84610a84565b95602085013595506040909401359392505050565b60008060408385031215610af357600080fd5b610afc83610a84565b946020939093013593505050565b60008060408385031215610b1d57600080fd5b82359150610b2d60208401610a84565b90509250929050565b600060208284031215610b4857600080fd5b815180151581146105b757600080fd5b600060208284031215610b6a57600080fd5b5051919050565b818103818111156103d8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220614a2031c8ae98bb035008c35c7fe18df6c47ba3497814706dc944954bd6b82064736f6c63430008100033
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100675760003560e01c80631873799a116100505780631873799a146100cc5780633ce55336146100df5780638653a465146100f257600080fd5b8063035c70991461006c578063143f769b146100b7575b600080fd5b7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d8755b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6100ca6100c5366004610aad565b610105565b005b6100ca6100da366004610aad565b61012f565b6100ca6100ed366004610ae0565b610228565b61008e610100366004610b0a565b610331565b600061011133846103de565b905061011e8482846105be565b610128848361064c565b5050505050565b600061013b33846103de565b90506101488482846105be565b60006101747f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd0368461064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff87811660248301529192507f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd036909116906314567f3790604401600060405180830381600087803b15801561020957600080fd5b505af115801561021d573d6000803e3d6000fd5b505050505050505050565b6102537f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b33836107c4565b600061027f7f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd0368361064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff85811660248301529192507f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd036909116906314567f3790604401600060405180830381600087803b15801561031457600080fd5b505af1158015610328573d6000803e3d6000fd5b50505050505050565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be605582018190526000916075016040516020818303038152906040528051906020012060001c9150505b92915050565b600061040b7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d87584846107c4565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d8751663095ea7b37f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af11580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190610b36565b506040517f0d213d310000000000000000000000000000000000000000000000000000000081523060048201526024810183905273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b1690630d213d31906044015b6020604051808303816000875af1158015610593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b79190610b58565b9392505050565b81811115610607576040517fa2dd20ef00000000000000000000000000000000000000000000000000000000815260048101829052602481018390526044015b60405180910390fd5b60006106138284610b71565b90508015610646576106467f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b8583610927565b50505050565b60007f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f00000000000000000000000065683990415a669a7ecbd877240818ee458d0f096040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190610b36565b506040517f2baf2acb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260248201849052600060448301527f00000000000000000000000065683990415a669a7ecbd877240818ee458d0f091690632baf2acb90606401610574565b806000036107d157505050565b73ffffffffffffffffffffffffffffffffffffffff831661081e576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015230602483015260448201839052600091908516906323b872dd906064016020604051808303816000875af115801561089d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c19190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015284166024820152306044820152606481018390526084016105fe565b8060000361093457505050565b73ffffffffffffffffffffffffffffffffffffffff8316610981576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390526000919085169063a9059cbb906044016020604051808303816000875af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e9190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015230602483015284166044820152606481018390526084016105fe565b803573ffffffffffffffffffffffffffffffffffffffff81168114610aa857600080fd5b919050565b600080600060608486031215610ac257600080fd5b610acb84610a84565b95602085013595506040909401359392505050565b60008060408385031215610af357600080fd5b610afc83610a84565b946020939093013593505050565b60008060408385031215610b1d57600080fd5b82359150610b2d60208401610a84565b90509250929050565b600060208284031215610b4857600080fd5b815180151581146105b757600080fd5b600060208284031215610b6a57600080fd5b5051919050565b818103818111156103d8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220614a2031c8ae98bb035008c35c7fe18df6c47ba3497814706dc944954bd6b82064736f6c63430008100033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 35 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.