ETH Price: $1,929.11 (+1.14%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Migrate And Stak...187281322023-12-06 15:19:47461 days ago1701875987IN
0xB00b635f...f9bb562A7
0 ETH0.0187195562.38311895
Migrate And Stak...172838132023-05-18 3:37:23664 days ago1684381043IN
0xB00b635f...f9bb562A7
0 ETH0.0132708244.22518583
Migrate And Stak...169685782023-04-03 12:44:59708 days ago1680525899IN
0xB00b635f...f9bb562A7
0 ETH0.0061130420.37247738
Migrate And Stak...168959352023-03-24 7:40:23719 days ago1679643623IN
0xB00b635f...f9bb562A7
0 ETH0.0050779316.92227731
Migrate And Stak...168554692023-03-18 15:15:47724 days ago1679152547IN
0xB00b635f...f9bb562A7
0 ETH0.0058299819.42848575
Migrate And Stak...168472782023-03-17 11:36:47725 days ago1679053007IN
0xB00b635f...f9bb562A7
0 ETH0.0046880315.62390973
Migrate And Stak...168283962023-03-14 19:53:47728 days ago1678823627IN
0xB00b635f...f9bb562A7
0 ETH0.0110423936.79767652
Stake And Lock168070762023-03-11 20:01:11731 days ago1678564871IN
0xB00b635f...f9bb562A7
0 ETH0.0113770831.10627536
Migrate And Stak...166558042023-02-18 13:26:11752 days ago1676726771IN
0xB00b635f...f9bb562A7
0 ETH0.0089897629.95850067
Stake And Lock166378512023-02-16 0:55:23755 days ago1676508923IN
0xB00b635f...f9bb562A7
0 ETH0.0144238139.44123108
Stake And Lock166298242023-02-14 21:59:47756 days ago1676411987IN
0xB00b635f...f9bb562A7
0 ETH0.0234004363.16453161
Migrate And Stak...166225412023-02-13 21:32:47757 days ago1676323967IN
0xB00b635f...f9bb562A7
0 ETH0.0063244121.0761992
Stake And Lock163686632023-01-09 10:31:47792 days ago1673260307IN
0xB00b635f...f9bb562A7
0 ETH0.0059590816.2948189
Stake And Lock163348302023-01-04 17:10:59797 days ago1672852259IN
0xB00b635f...f9bb562A7
0 ETH0.0086771823.72735366
Migrate Stake An...163286662023-01-03 20:32:35798 days ago1672777955IN
0xB00b635f...f9bb562A7
0 ETH0.0085835521.64252695
Stake And Lock162904762022-12-29 12:40:47803 days ago1672317647IN
0xB00b635f...f9bb562A7
0 ETH0.0053074814.51353153
Migrate Stake An...162751902022-12-27 9:28:47805 days ago1672133327IN
0xB00b635f...f9bb562A7
0 ETH0.0050559511.98054997
Migrate And Stak...161895712022-12-15 10:43:11817 days ago1671100991IN
0xB00b635f...f9bb562A7
0 ETH0.0039987413.32713867
Migrate Stake An...161828322022-12-14 12:07:59818 days ago1671019679IN
0xB00b635f...f9bb562A7
0 ETH0.0063777416.08081846
Migrate And Stak...161809122022-12-14 5:40:35819 days ago1670996435IN
0xB00b635f...f9bb562A7
0 ETH0.0041322913.77092211
Migrate Stake An...161792592022-12-14 0:09:11819 days ago1670976551IN
0xB00b635f...f9bb562A7
0 ETH0.0058020514.62839312
Migrate Stake An...161772842022-12-13 17:32:23819 days ago1670952743IN
0xB00b635f...f9bb562A7
0 ETH0.0113384928.58708897
Stake And Lock161766572022-12-13 15:26:11819 days ago1670945171IN
0xB00b635f...f9bb562A7
0 ETH0.0080807921.81170307
Migrate Stake An...161766062022-12-13 15:15:59819 days ago1670944559IN
0xB00b635f...f9bb562A7
0 ETH0.0082743620.8629441
Migrate Stake An...161758092022-12-13 12:33:11819 days ago1670934791IN
0xB00b635f...f9bb562A7
0 ETH0.0075501619.03693168
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x61012060161699132022-12-12 16:48:11820 days ago1670863691  Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
StakingRouterV1

Compiler Version
v0.8.16+commit.07a7930e

Optimization Enabled:
Yes with 20000 runs

Other Settings:
default evmVersion
File 1 of 29 : StakingRouterV1.sol
// 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);
        }
    }
}

File 2 of 29 : IERC20.sol
// 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);
}

File 3 of 29 : IERC721.sol
// 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);
}

File 4 of 29 : IERC721Receiver.sol
// 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);
}

File 5 of 29 : ERC721Holder.sol
// 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;
    }
}

File 6 of 29 : IERC165.sol
// 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);
}

File 7 of 29 : BonusPool.sol
// 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;
    }
}

File 8 of 29 : IAliceNetFactory.sol
// SPDX-License-Identifier: MIT-open-group
pragma solidity ^0.8.16;

interface IAliceNetFactory {
    function lookup(bytes32 salt_) external view returns (address);
}

File 9 of 29 : IERC20Transferable.sol
// 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);
}

File 10 of 29 : IERC721Transferable.sol
// SPDX-License-Identifier: MIT-open-group
pragma solidity ^0.8.16;

interface IERC721Transferable {
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
}

File 11 of 29 : IMagicEthTransfer.sol
// SPDX-License-Identifier: MIT-open-group
pragma solidity ^0.8.16;

interface IMagicEthTransfer {
    function depositEth(uint8 magic_) external payable;
}

File 12 of 29 : IStakingNFT.sol
// 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);
}

File 13 of 29 : IStakingToken.sol
// 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;
}

File 14 of 29 : ERC20SafeTransferErrors.sol
// 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);
}

File 15 of 29 : ETHSafeTransferErrors.sol
// SPDX-License-Identifier: MIT-open-group
pragma solidity ^0.8.16;

library ETHSafeTransferErrors {
    error CannotTransferToZeroAddress();
    error EthTransferFailed(address from, address to, uint256 amount);
}

File 16 of 29 : LockupErrors.sol
// 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();
}

File 17 of 29 : MagicValueErrors.sol
// SPDX-License-Identifier: MIT-open-group
pragma solidity ^0.8.16;

library MagicValueErrors {
    error BadMagic(uint256 magic);
}

File 18 of 29 : AccessControlled.sol
// 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);
}

File 19 of 29 : Lockup.sol
// 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;
    }
}

File 20 of 29 : RewardPool.sol
// 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);
    }
}

File 21 of 29 : ImmutableALCA.sol
// 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;
    }
}

File 22 of 29 : ImmutableFactory.sol
// 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;
    }
}

File 23 of 29 : ImmutableFoundation.sol
// 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;
    }
}

File 24 of 29 : ImmutablePublicStaking.sol
// 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;
    }
}

File 25 of 29 : DeterministicAddress.sol
// 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_
                            )
                        )
                    )
                )
            );
    }
}

File 26 of 29 : ERC20SafeTransfer.sol
// 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_
            );
        }
    }
}

File 27 of 29 : EthSafeTransfer.sol
// 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_);
        }
    }
}

File 28 of 29 : MagicEthTransfer.sol
// 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());
    }
}

File 29 of 29 : MagicValue.sol
// 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;
    }
}

Settings
{
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "optimizer": {
    "enabled": true,
    "runs": 20000
  },
  "libraries": {}
}

Contract Security Audit

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"}]

61012060405234801561001157600080fd5b50336080526100c56c5075626c69635374616b696e6760981b61003360805190565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201526001600160601b0319606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be6055820181905260009160750160408051601f198184030181529190528051602090910120949350505050565b6001600160a01b0390811660a05260805160405163f39ec1f760e01b815263414c434160e01b600482015291169063f39ec1f790602401602060405180830381865afa158015610119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013d9190610240565b6001600160a01b031660c08190526001600160a01b031663035c70996040518163ffffffff1660e01b8152600401602060405180830381865afa158015610188573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ac9190610240565b6001600160a01b031660e05260805160405163f39ec1f760e01b81526504c6f636b75760d41b60048201526001600160a01b03919091169063f39ec1f790602401602060405180830381865afa15801561020a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061022e9190610240565b6001600160a01b031661010052610270565b60006020828403121561025257600080fd5b81516001600160a01b038116811461026957600080fd5b9392505050565b60805160a05160c05160e05161010051610be16102f66000396000818161014f015281816101c30152818161025a01526102ce015260008181606e015281816103e5015261042201526000818161022d015281816104490152818161054901528181610620015261065001526000818161068c0152610795015260005050610be16000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80631873799a116100505780631873799a146100cc5780633ce55336146100df5780638653a465146100f257600080fd5b8063035c70991461006c578063143f769b146100b7575b600080fd5b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6100ca6100c5366004610aad565b610105565b005b6100ca6100da366004610aad565b61012f565b6100ca6100ed366004610ae0565b610228565b61008e610100366004610b0a565b610331565b600061011133846103de565b905061011e8482846105be565b610128848361064c565b5050505050565b600061013b33846103de565b90506101488482846105be565b60006101747f00000000000000000000000000000000000000000000000000000000000000008461064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff87811660248301529192507f0000000000000000000000000000000000000000000000000000000000000000909116906314567f3790604401600060405180830381600087803b15801561020957600080fd5b505af115801561021d573d6000803e3d6000fd5b505050505050505050565b6102537f000000000000000000000000000000000000000000000000000000000000000033836107c4565b600061027f7f00000000000000000000000000000000000000000000000000000000000000008361064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff85811660248301529192507f0000000000000000000000000000000000000000000000000000000000000000909116906314567f3790604401600060405180830381600087803b15801561031457600080fd5b505af1158015610328573d6000803e3d6000fd5b50505050505050565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be605582018190526000916075016040516020818303038152906040528051906020012060001c9150505b92915050565b600061040b7f000000000000000000000000000000000000000000000000000000000000000084846107c4565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001663095ea7b37f00000000000000000000000000000000000000000000000000000000000000006040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af11580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190610b36565b506040517f0d213d310000000000000000000000000000000000000000000000000000000081523060048201526024810183905273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690630d213d31906044015b6020604051808303816000875af1158015610593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b79190610b58565b9392505050565b81811115610607576040517fa2dd20ef00000000000000000000000000000000000000000000000000000000815260048101829052602481018390526044015b60405180910390fd5b60006106138284610b71565b90508015610646576106467f00000000000000000000000000000000000000000000000000000000000000008583610927565b50505050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f00000000000000000000000000000000000000000000000000000000000000006040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190610b36565b506040517f2baf2acb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260248201849052600060448301527f00000000000000000000000000000000000000000000000000000000000000001690632baf2acb90606401610574565b806000036107d157505050565b73ffffffffffffffffffffffffffffffffffffffff831661081e576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015230602483015260448201839052600091908516906323b872dd906064016020604051808303816000875af115801561089d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c19190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015284166024820152306044820152606481018390526084016105fe565b8060000361093457505050565b73ffffffffffffffffffffffffffffffffffffffff8316610981576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390526000919085169063a9059cbb906044016020604051808303816000875af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e9190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015230602483015284166044820152606481018390526084016105fe565b803573ffffffffffffffffffffffffffffffffffffffff81168114610aa857600080fd5b919050565b600080600060608486031215610ac257600080fd5b610acb84610a84565b95602085013595506040909401359392505050565b60008060408385031215610af357600080fd5b610afc83610a84565b946020939093013593505050565b60008060408385031215610b1d57600080fd5b82359150610b2d60208401610a84565b90509250929050565b600060208284031215610b4857600080fd5b815180151581146105b757600080fd5b600060208284031215610b6a57600080fd5b5051919050565b818103818111156103d8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220614a2031c8ae98bb035008c35c7fe18df6c47ba3497814706dc944954bd6b82064736f6c63430008100033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100675760003560e01c80631873799a116100505780631873799a146100cc5780633ce55336146100df5780638653a465146100f257600080fd5b8063035c70991461006c578063143f769b146100b7575b600080fd5b7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d8755b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6100ca6100c5366004610aad565b610105565b005b6100ca6100da366004610aad565b61012f565b6100ca6100ed366004610ae0565b610228565b61008e610100366004610b0a565b610331565b600061011133846103de565b905061011e8482846105be565b610128848361064c565b5050505050565b600061013b33846103de565b90506101488482846105be565b60006101747f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd0368461064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff87811660248301529192507f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd036909116906314567f3790604401600060405180830381600087803b15801561020957600080fd5b505af115801561021d573d6000803e3d6000fd5b505050505050505050565b6102537f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b33836107c4565b600061027f7f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd0368361064c565b6040517f14567f370000000000000000000000000000000000000000000000000000000081526004810182905273ffffffffffffffffffffffffffffffffffffffff85811660248301529192507f000000000000000000000000d4e53d48cb943efd4f913862d38bdae3fcbbd036909116906314567f3790604401600060405180830381600087803b15801561031457600080fd5b505af1158015610328573d6000803e3d6000fd5b50505050505050565b6040517fff0000000000000000000000000000000000000000000000000000000000000060208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166021820152603581018390527f1c0bf703a3415cada9785e89e9d70314c3111ae7d8e04f33bb42eb1d264088be605582018190526000916075016040516020818303038152906040528051906020012060001c9150505b92915050565b600061040b7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d87584846107c4565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005b09a0371c1da44a8e24d36bf5deb1141a84d8751663095ea7b37f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af11580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190610b36565b506040517f0d213d310000000000000000000000000000000000000000000000000000000081523060048201526024810183905273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b1690630d213d31906044015b6020604051808303816000875af1158015610593573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b79190610b58565b9392505050565b81811115610607576040517fa2dd20ef00000000000000000000000000000000000000000000000000000000815260048101829052602481018390526044015b60405180910390fd5b60006106138284610b71565b90508015610646576106467f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b8583610927565b50505050565b60007f000000000000000000000000bb556b0ee2cbd89ed95ddea881477723a3aa8f8b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f00000000000000000000000065683990415a669a7ecbd877240818ee458d0f096040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602481018590526044016020604051808303816000875af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190610b36565b506040517f2baf2acb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260248201849052600060448301527f00000000000000000000000065683990415a669a7ecbd877240818ee458d0f091690632baf2acb90606401610574565b806000036107d157505050565b73ffffffffffffffffffffffffffffffffffffffff831661081e576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015230602483015260448201839052600091908516906323b872dd906064016020604051808303816000875af115801561089d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c19190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015284166024820152306044820152606481018390526084016105fe565b8060000361093457505050565b73ffffffffffffffffffffffffffffffffffffffff8316610981576040517f2518928d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390526000919085169063a9059cbb906044016020604051808303816000875af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e9190610b36565b905080610646576040517ff0798c5100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff808616600483015230602483015284166044820152606481018390526084016105fe565b803573ffffffffffffffffffffffffffffffffffffffff81168114610aa857600080fd5b919050565b600080600060608486031215610ac257600080fd5b610acb84610a84565b95602085013595506040909401359392505050565b60008060408385031215610af357600080fd5b610afc83610a84565b946020939093013593505050565b60008060408385031215610b1d57600080fd5b82359150610b2d60208401610a84565b90509250929050565b600060208284031215610b4857600080fd5b815180151581146105b757600080fd5b600060208284031215610b6a57600080fd5b5051919050565b818103818111156103d8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220614a2031c8ae98bb035008c35c7fe18df6c47ba3497814706dc944954bd6b82064736f6c63430008100033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
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.