FTM Price: $0.99 (-3.71%)
Gas: 64 GWei

Contract

0x0B90dd88692Ec4fd4A77584713E3770057272B38
 

Overview

FTM Balance

Fantom LogoFantom LogoFantom Logo0 FTM

FTM Value

$0.00

Token Holdings

Sponsored

Transaction Hash
Method
Block
From
To
Value
Redeem Referral ...400590472022-06-08 10:58:35659 days ago1654685915IN
0x0B90dd88...057272B38
0 FTM0.0012757714.46460326
Redeem Referral ...315162482022-02-20 12:35:08767 days ago1645360508IN
0x0B90dd88...057272B38
0 FTM0.00909574152.9725
Redeem Referral ...313206962022-02-18 11:04:38769 days ago1645182278IN
0x0B90dd88...057272B38
0 FTM0.01583702210.5707
Redeem Referral ...311651502022-02-16 19:40:06771 days ago1645040406IN
0x0B90dd88...057272B38
0 FTM0.01532684257.7673
Redeem Referral ...311121792022-02-16 6:26:55772 days ago1644992815IN
0x0B90dd88...057272B38
0 FTM0.01306152219.6691
Redeem Referral ...309863612022-02-14 23:26:02773 days ago1644881162IN
0x0B90dd88...057272B38
0 FTM0.01712586227.7073
Redeem Referral ...309515112022-02-14 14:47:51773 days ago1644850071IN
0x0B90dd88...057272B38
0 FTM0.01159632210.8422
Redeem Referral ...309262642022-02-14 8:32:18773 days ago1644827538IN
0x0B90dd88...057272B38
0 FTM0.0068379115
Redeem Referral ...309242392022-02-14 8:01:44773 days ago1644825704IN
0x0B90dd88...057272B38
0 FTM0.00767142102
Redeem Referral ...308959232022-02-14 1:21:02774 days ago1644801662IN
0x0B90dd88...057272B38
0 FTM0.03954301525.768
Redeem Referral ...308547812022-02-13 15:07:29774 days ago1644764849IN
0x0B90dd88...057272B38
0 FTM0.00783104176.2954
Redeem Referral ...308501502022-02-13 14:00:55774 days ago1644760855IN
0x0B90dd88...057272B38
0 FTM0.00937127155.7466
Redeem Referral ...308500692022-02-13 13:59:52774 days ago1644760792IN
0x0B90dd88...057272B38
0 FTM0.00689648155.2564
Redeem Referral ...308489242022-02-13 13:43:25774 days ago1644759805IN
0x0B90dd88...057272B38
0 FTM0.00728063163.9045
Redeem Referral ...308478592022-02-13 13:29:14774 days ago1644758954IN
0x0B90dd88...057272B38
0 FTM0.00702621158.1769
Create Referral308468862022-02-13 13:16:18774 days ago1644758178IN
0x0B90dd88...057272B38
0 FTM0.0060258120
Redeem Referral ...308464002022-02-13 13:09:57774 days ago1644757797IN
0x0B90dd88...057272B38
0 FTM0.00796206179.245
Redeem Referral ...308452832022-02-13 12:54:18774 days ago1644756858IN
0x0B90dd88...057272B38
0 FTM0.0110951184.396
Create Referral308452712022-02-13 12:54:09774 days ago1644756849IN
0x0B90dd88...057272B38
0 FTM0.00925495184.3066
Redeem Referral ...308452382022-02-13 12:53:42774 days ago1644756822IN
0x0B90dd88...057272B38
0 FTM0.01104575183.5758
Redeem Referral ...308440012022-02-13 12:36:34774 days ago1644755794IN
0x0B90dd88...057272B38
0 FTM0.00821287184.8913
Create Referral308431542022-02-13 12:25:29774 days ago1644755129IN
0x0B90dd88...057272B38
0 FTM0.0100011199.1656
Redeem Referral ...308426992022-02-13 12:19:24774 days ago1644754764IN
0x0B90dd88...057272B38
0 FTM0.00916317206.2849
Redeem Referral ...308425552022-02-13 12:17:14774 days ago1644754634IN
0x0B90dd88...057272B38
0 FTM0.00928742209.0821
Redeem Referral ...308425222022-02-13 12:16:48774 days ago1644754608IN
0x0B90dd88...057272B38
0 FTM0.00928862209.1091
View all transactions

Latest 1 internal transaction

Parent Txn Hash Block From To Value
195390282021-10-19 22:41:08891 days ago1634683268  Contract Creation0 FTM
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
SummitReferrals

Compiler Version
v0.6.12+commit.27d51765

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 21 : SummitReferrals.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "./Cartographer.sol";
import "./SummitToken.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

contract SummitReferrals {
    using SafeMath for uint256;

    address public cartographer;
    SummitToken public summit;
    address constant burnAdd = 0x000000000000000000000000000000000000dEaD;
    
    constructor(address _cartographer) public {
        require(_cartographer != address(0), "Cart required");
        cartographer = _cartographer;
    }
    
    function enable(address _summit) external onlyCartographer {
        summit = SummitToken(_summit);
        summit.approve(burnAdd, 1e50);
    }

    // REFERRALS
    uint256 roundIndex = 0; 
    mapping(address => address) public referrerOf;
    mapping(uint256 => mapping(address => uint256)) public pendingReferralRewards;
    mapping(address => uint256) public totalReferralRewards;


    // EVENTS
    event ReferralCreated(address indexed referrerAddress, address indexed refereeAddress);
    event ReferralRewardsRedeemed(address indexed referrerAddress, uint256 amount);
    event UnclaimedReferralRewardsBurned(address indexed burner, uint256 indexed round, uint256 amount);

    function createReferral(address referrerAddress) public {
        require(referrerAddress != msg.sender, "Cant refer yourself");
        require(referrerOf[msg.sender] == address(0), "Already been referred");
        require(referrerOf[referrerAddress] != msg.sender, "No reciprocal referrals");
        require(referrerOf[referrerOf[referrerAddress]] != msg.sender, "No 3 user cyclical referrals");
        referrerOf[msg.sender] = referrerAddress;
        emit ReferralCreated(referrerAddress, msg.sender);
    }  

    modifier onlyCartographer() {
        require(msg.sender == cartographer, "Only Cartographer can call function" );
        _;
    }
    function addReferralRewardsIfNecessary(address referee, uint256 amount) external onlyCartographer {
        if (referrerOf[referee] == address(0)) { return; }
        uint256 additionalReward = amount.div(100);
        pendingReferralRewards[roundIndex][referrerOf[referee]] += additionalReward;
        pendingReferralRewards[roundIndex][referee] += additionalReward;
        totalReferralRewards[referrerOf[referee]] += additionalReward;
        totalReferralRewards[referee] += additionalReward;
    }
    
    function getReferralRound() external view returns(uint256) {
        return roundIndex;
    }

    // Called after burn button pressed with correct code and tokens burned
    function burnUnclaimedReferralRewardsAndRolloverRound(address burner) external onlyCartographer {
        uint256 burnAmount = summit.balanceOf(address(this));

        if (burnAmount > 0) {
            summit.transfer(burnAdd, burnAmount);
        }
        
        roundIndex += 1;

        emit UnclaimedReferralRewardsBurned(burner, roundIndex - 1, burnAmount);
    }

    // To be called after rewards redeemed in Cartographer
    function redeemReferralRewards() public {
        require(pendingReferralRewards[roundIndex][msg.sender] > 0, "No referral rewards to redeem");
        uint256 toBeRedeemed = pendingReferralRewards[roundIndex][msg.sender];

        // Safety hatch if required summit has recently been burned, followed by a user harvesting rewards
        if (toBeRedeemed > summit.balanceOf(address(this))) {
            Cartographer(cartographer).referralRewardsMintSafetyHatch(toBeRedeemed);
        }

        safeSummitTransfer(msg.sender, toBeRedeemed);
        pendingReferralRewards[roundIndex][msg.sender] = 0;

        emit ReferralRewardsRedeemed(msg.sender, toBeRedeemed);
    }
    
    function getReferralRewardsToBeBurned() public view returns (uint256) {
        return summit.balanceOf(address(this));
    }

    function getPendingReferralRewards(address user) public view returns (uint256) {
        return pendingReferralRewards[roundIndex][user];
    }


    // UTIL
    function safeSummitTransfer(address _to, uint256 _amount) internal {
        uint256 summitBal = summit.balanceOf(address(this));
        bool transferSuccess = false;
        if (_amount > summitBal) {
            transferSuccess = summit.transfer(_to, summitBal);
        } else {
            transferSuccess = summit.transfer(_to, _amount);
        }
        require(transferSuccess, "SafeSummitTransfer: failed");
    }    
}

File 2 of 21 : Cartographer.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;
import "./CartographerOasis.sol";
import "./CartographerElevation.sol";
import "./CartographerExpedition.sol";
import "./ElevationHelper.sol";
import "./SummitReferrals.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./libs/IBEP20.sol";
import "./libs/SafeBEP20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./IPassthrough.sol";
import "./SummitToken.sol";
import "./libs/ILiquidityPair.sol";


/*
---------------------------------------------------------------------------------------------
--   S U M M I T . D E F I
---------------------------------------------------------------------------------------------


Summit is highly experimental.
It has been crafted to bring a new flavor to the defi world.
We hope you enjoy the Summit.defi experience.
If you find any bugs in these contracts, please claim the bounty (see docs)


Created with love by Architect and the Summit team





---------------------------------------------------------------------------------------------
--   S U M M I T   E C O S Y S T E M
---------------------------------------------------------------------------------------------


The Cartographer is the anchor of the summit ecosystem
The Cartographer is also the owner of the SUMMIT token


Features of the Summit Ecosystem
    - Cross pool compounding (Compound your winnings, even if they're not from the SUMMIT pool. Cross pool compounding vesting winnings locks them from withdrawl for the remainder of the round)
    - Standard Yield Farming (oasis farms mirror standard farms)

    - Yield Multiplying (yield is put into a pot, which allows winning of other user's yield reward)
    - Multiple elevations (2X elevation, 5X elevation, 10X elevation)
    - Shared token allocation (reward allocation is split by elevation multiplier and amount staked at elevation, to guarantee more rewards at higher elevation)
    - Reward vesting (No large dumps of SUMMIT token on wins)
    - Elevating (Deposit once, update strategy without paying fee, even in and out of expeditions)

    - Passthrough Strategy (to fund expeditions, on oasis and elevation farms)
    - Expedition (weekly drawings for summit holders to earn stablecoins and other high value tokens)

    - Random number generation immune to Block Withholding Attack through open source webserver
    - Stopwatch functionality through open source webserver
    
    - Referrals (
        . No limit to number of users referred
        . 1% bonus of each referred user's SUMMIT rewards
        . 1% bonus of own SUMMIT rewards if you have been referred
      )
    - Automatic unclaimed referral rewards burns

*/

contract Cartographer is Ownable, Initializable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeBEP20 for IBEP20;



    // ---------------------------------------
    // --   V A R I A B L E S
    // ---------------------------------------

    SummitToken public summit;
    ILiquidityPair public summitLp;
    bool public enabled = false;                                                // Whether the ecosystem has been enabled for earning

    uint256 public rolloverRewardInNativeToken = 5e18;                          // Amount of native token which will be rewarded for rolling over a round (will be converted into summit and minted)

    address public devAdd;                                                      // Treasury address, see docs for spend breakdown
    address public expedAdd;                                                    // Expedition Treasury address, intermediate address to convert to stablecoins
    address public trustedSeederAdd;                                            // Address that seeds the random number generation every 2 hours
    ElevationHelper elevationHelper;
    SummitReferrals summitReferrals;
    ISubCart cartographerOasis;
    ISubCart cartographerElevation;
    ISubCart cartographerExpedition;

    uint8 constant OASIS = 0;
    uint8 constant TWOTHOUSAND = 1;
    uint8 constant FIVETHOUSAND = 2;
    uint8 constant TENTHOUSAND = 3;
    uint8 constant EXPEDITION = 4;

    uint256 public launchTimestamp = 1641028149;                                // 2022-1-1, will be updated when summit ecosystem switched on
    uint256 public summitPerSecond;                                             // Amount of Summit minted per second to be distributed to users
    uint256 public devSummitPerSecond;                                          // Amount of Summit minted per second to the treasury
    uint256 public referralsSummitPerSecond;                                    // Amount of Summit minted per second as referral rewards

    uint16[] public poolIds;                                                    // List of all pool identifiers (PIDs)
    mapping(uint16 => uint8) public poolElevation;                              // Elevation map for each pool

    mapping(IBEP20 => address) public tokenPassthroughStrategy;                 // Passthrough strategy of each stakable token

    uint256 public totalSharedAlloc = 0;                                        // Total allocation points: sum of all allocation points in all pools.
    mapping(IBEP20 => bool) public tokenAllocExistence;                         // Whether an allocation has been created for a specific token
    address[] tokensWithAllocation;                                             // List of Token Addresses that have been assigned an allocation
    mapping(IBEP20 => uint256) public tokenBaseAlloc;                           // A tokens underlying allocation, which is modulated for each elevation
    mapping(IBEP20 => uint256) public tokenSharedAlloc;                         // Sum of the tokens modulated allocations across enabled elevations

    mapping(IBEP20 => mapping(uint8 => uint16)) public tokenElevationPid;       // Pool identifier for a token at an elevation
    mapping(IBEP20 => mapping(uint8 => bool)) public tokenElevationIsEarning;   // If a token is earning SUMMIT at a specific elevation






    // ---------------------------------------
    // --   E V E N T S
    // ---------------------------------------

    event TokenAllocCreated(address indexed token, uint256 alloc);
    event TokenAllocUpdated(address indexed token, uint256 alloc);
    event PoolCreated(uint16 indexed pid, uint8 elevation, address token);
    event PoolUpdated(uint16 indexed pid, bool live, uint256 depositFee, uint256 elevation);
    event ExpeditionCreated(uint16 indexed pid, address rewardToken, uint256 _rewardAmount, uint256 _rounds);
    event CrossCompound(address indexed user, uint16 indexed harvestPid, uint16 indexed crossCompoundPid, uint256 amount);
    event Deposit(address indexed user, uint16 indexed pid, uint256 amount, uint256 expeditionSummitLpAmount);
    event HarvestElevation(address indexed user, uint8 indexed elevation, bool crossCompound, uint256 totalHarvested);
    event Rollover(address indexed user, uint256 elevation);
    event RolloverReferral(address indexed user);
    event SwitchTotem(address indexed user, uint8 indexed elevation, uint8 totem);
    event Elevate(address indexed user, uint16 indexed currentPid, uint16 indexed newPid, uint8 totem, uint256 amount);
    event Withdraw(address indexed user, uint16 indexed pid, uint256 amount, uint256 expeditionSummitLpAmount);
    event RedeemRewards(address indexed user, uint256 amount);
    event SetExpeditionTreasuryAddress(address indexed user, address indexed newAddress);
    event SetTreasuryAddress(address indexed user, address indexed newAddress);
    event SetTrustedSeederAddress(address indexed user, address indexed newAddress);
    event PassthroughStrategySet(address indexed token, address indexed passthroughStrategy);
    event PassthroughStrategyRetired(address indexed token, address indexed passthroughStrategy);






    // ---------------------------------------
    // --  A D M I N I S T R A T I O N
    // ---------------------------------------


    /// @dev Constructor simply setting addresses on creation
    constructor(
        address _devAdd,
        address _expedAdd,
        address _trustedSeederAdd
    ) public {
        devAdd = _devAdd;
        expedAdd = _expedAdd;
        trustedSeederAdd = _trustedSeederAdd;

        // The first PID is left empty
        poolIds.push(0);
    }

    /// @dev Initialize, simply setting addresses, these contracts need the Cartographer address so it must be separate from the constructor
    function initialize(
        SummitToken _summit,
        ILiquidityPair _summitLp,
        address _ElevationHelper,
        address _SummitReferrals,
        address _CartographerOasis,
        address _CartographerElevation,
        address _CartographerExpedition
    )
        external
        initializer onlyOwner
    {
        require(
            address(_summit) != address(0) &&
            address(_summitLp) != address(0) &&
            _ElevationHelper != address(0) &&
            _SummitReferrals != address(0) &&
            _CartographerOasis != address(0) &&
            _CartographerElevation != address(0) &&
            _CartographerExpedition != address(0),
            "Contract is zero"
        );
        require(_summitLp.token0() == address(_summit) || _summitLp.token1() == address(_summit), "SUMMITLP is not SUMMIT liq pair");

        summit = _summit;
        summitLp = _summitLp;

        elevationHelper = ElevationHelper(_ElevationHelper);
        elevationHelper.setTrustedSeederAdd(trustedSeederAdd);
        summitReferrals = SummitReferrals(_SummitReferrals);
        cartographerOasis = ISubCart(_CartographerOasis);
        cartographerElevation = ISubCart(_CartographerElevation);
        cartographerExpedition = ISubCart(_CartographerExpedition);

        // Initialize the subCarts with the address of elevationHelper
        cartographerElevation.initialize(address(elevationHelper), address(_summit), address(_summitLp));
        cartographerExpedition.initialize(address(elevationHelper), address(_summit), address(_summitLp));

        // Initial value of summit minting
        setTotalSummitPerSecond(25e16);
    }


    /// @dev Enabling the summit ecosystem with the true summit token, turning on farming
    function enable() external onlyOwner {
        // Prevent multiple instances of enabling
        require(!enabled, "Already enabled");
        enabled = true;

        // Setting and propagating the true summit address and launch timestamp
        launchTimestamp = block.timestamp;
        elevationHelper.enable(launchTimestamp);
        summitReferrals.enable(address(summit));
        cartographerOasis.enable(launchTimestamp);
        cartographerExpedition.enable(launchTimestamp);
    }


    /// @dev Updating the dev address, can only be called by the current dev address
    /// @param _devAdd New dev address
    function setDevAdd(address _devAdd) public {
        require(_devAdd != address(0), "Missing address");
        require(msg.sender == devAdd, "Forbidden");

        devAdd = _devAdd;
        emit SetTreasuryAddress(msg.sender, _devAdd);
    }


    /// @dev Updating the expedition accumulator address
    /// @param _expedAdd New expedition accumulator address
    function setExpedAdd(address _expedAdd) public onlyOwner {
        require(_expedAdd != address(0), "Missing address");
        expedAdd = _expedAdd;
        emit SetExpeditionTreasuryAddress(msg.sender, _expedAdd);
    }

    /// @dev Update the amount of native token equivalent to reward for rolling over a round
    function setRolloverRewardInNativeToken(uint256 _reward) public onlyOwner {
        require(_reward < 10e18, "Exceeds max reward");
        rolloverRewardInNativeToken = _reward;
    }

    /// @dev Updating the trusted seeder address, can only be called by the owner
    /// @param _trustedSeederAdd New trustedSeeder address
    function setTrustedSeederAdd(address _trustedSeederAdd) public onlyOwner {
        require(_trustedSeederAdd != address(0), "Missing address");

        trustedSeederAdd = _trustedSeederAdd;
        elevationHelper.setTrustedSeederAdd(_trustedSeederAdd);
        emit SetTrustedSeederAddress(msg.sender, _trustedSeederAdd);
    }

    /// @dev Updating the total emission of the ecosystem
    /// @param _amount New total emission
    function setTotalSummitPerSecond(uint256 _amount) public onlyOwner {
        // Require non-zero and less than 1 SUMMIT per second
        require(_amount > 0 && _amount < 1e18, "Invalid emission");

        // New total emission is split into its component parts to save computation costs later
        // 92% goes to staking rewards, of which 2% goes to referrals
        summitPerSecond = _amount.mul(92).div(100).mul(98).div(100);
        referralsSummitPerSecond = _amount.mul(92).div(100).mul(2).div(100);
        devSummitPerSecond = _amount.mul(8).div(100);
    }

    /// @dev Updating the emission split profile
    /// @param _staking How much is reserved for staking
    /// @param _dev How much is reserved for the devs
    function setSummitDistributionProfile(uint256 _staking, uint256 _dev) public onlyOwner {
        // Require dev emission less than 25% of total emission
        require(_staking < 10000 && _dev < 10000 && _dev.mul(3) < _staking, "Invalid Distribution Profile");

        // Total amount of shares passed in is irrelevant, they are summed
        uint256 totalShares = _staking.add(_dev);
        // Total emission summed from component parts
        uint256 totalEmission = summitPerSecond.add(devSummitPerSecond).add(referralsSummitPerSecond);

        summitPerSecond = totalEmission.mul(_staking).div(totalShares).mul(98).div(100);
        referralsSummitPerSecond = totalEmission.mul(_staking).div(totalShares).mul(2).div(100);
        devSummitPerSecond = totalEmission.mul(_dev).div(totalShares);
    }






    // -----------------------------------------------------------------
    // --   M O D I F I E R S (Many are split to save contract size)
    // -----------------------------------------------------------------

    function _onlySubCartographer(address _add) internal view {
        require(_add == address(cartographerOasis) || _add == address(cartographerElevation) || _add == address(cartographerExpedition), "Only subCarts");
    }
    modifier onlySubCartographer() {
        _onlySubCartographer(msg.sender);
        _;
    }

    function _nonDuplicated(IBEP20 _token, uint8 _elevation) internal view {
        require(tokenElevationPid[_token][_elevation] == 0, "Duplicated");
    }
    modifier nonDuplicated(IBEP20 _token, uint8 _elevation) {
        _nonDuplicated(_token, _elevation);
        _;
    }

    modifier nonDuplicatedTokenAlloc(IBEP20 _token) {
        require(tokenAllocExistence[_token] == false, "Duplicated token alloc");
        _;
    }
    modifier tokenAllocExists(IBEP20 _token) {
        require(tokenAllocExistence[_token] == true, "Invalid token alloc");
        _;
    }
    modifier validAllocation(uint256 _allocation) {
        require(_allocation >= 0 && _allocation <= 10000, "Allocation must be <= 100X");
        _;
    }

    function _poolExists(uint16 _pid) internal view {
        require(_pid > 0 && _pid < poolIds.length, "Pool doesnt exist");

    }
    modifier poolExists(uint16 _pid) {
        _poolExists(_pid);
        _;
    }

    // Elevation validation with min and max elevations (inclusive)
    function _validElev(uint8 _elevation, uint8 _minElev, uint8 _maxElev) internal pure {
        require(_elevation >= _minElev && _elevation <= _maxElev, "Invalid elev");
    }
    modifier oasisOrElevation(uint8 _elevation) {
        _validElev(_elevation, 0, 3);
        _;
    }
    modifier elevationOrExpedition(uint8 _elevation) {
        _validElev(_elevation, 1, 4);
        _;
    }





    // ---------------------------------------
    // --   U T I L S (inlined for brevity)
    // ---------------------------------------

    /// @dev Total number of pools
    function poolsCount() external view returns (uint256) {
        return poolIds.length;
    }

    /// @dev Amount staked in a pool
    function stakedSupply(uint16 _pid) public view poolExists(_pid) returns (uint256) {
        return subCartographer(poolElevation[_pid]).supply(_pid);
    }

    /// @dev Token of a pool
    /// @param _pid Pool of token
    /// @param _isExpeditionSummitLp Handles Expedition multi-staking -> If true: Expeditions will return SUMMIT LP instead of SUMMIT
    function token(uint16 _pid, bool _isExpeditionSummitLp) public view returns (IBEP20) {
        return subCartographer(poolElevation[_pid]).token(_pid, _isExpeditionSummitLp);
    }

    /// @dev Deposit fee of a pool
    function depositFee(uint16 _pid) public view poolExists(_pid) returns (uint256) {
        return subCartographer(poolElevation[_pid]).depositFee(_pid);
    }

    /// @dev Whether a pool is earning SUMMIT or not
    function isEarning(uint16 _pid) public view returns (bool) {
        return subCartographer(poolElevation[_pid]).isEarning(_pid);
    }

    /// @dev A users totem information at an elevation
    /// @return (
    ///     totemInUse - Whether the user has any funds staked with a totem
    ///     selectedTotem - The totem the user has selected
    /// )
    function userTotem(uint8 _elevation, address _userAdd) public view returns (bool, uint8) {
        return (
            subCartographer(_elevation).isTotemInUse(_elevation, _userAdd),
            subCartographer(_elevation).selectedTotem(_elevation, _userAdd)
        );
    }

    /// @dev Timestamp when a round of an elevation ends
    function roundEndTimestamp(uint8 _elevation) public view elevationOrExpedition(_elevation) returns(uint256) {
        return elevationHelper.roundEndTimestamp(_elevation);
    }





    // ---------------------------------------------------------------
    // --   S U B   C A R T O G R A P H E R   S E L E C T O R
    // ---------------------------------------------------------------

    function subCartographer(uint8 _elevation) internal view returns (ISubCart) {
        require(_elevation >= 0 && _elevation <= 4, "Invalid elev");
        return _elevation == 0 ?
            cartographerOasis :
            _elevation == 4 ?
                cartographerExpedition :
                cartographerElevation;
    }





    // ---------------------------------------
    // --   T O K E N   A L L O C A T I O N
    // ---------------------------------------


    /// @dev Create a new base allocation for a token. Required before a pool for that token is created
    /// @param _token Token to create allocation for
    /// @param _allocation Allocation shares awarded to token
    function createTokenAllocation(IBEP20 _token, uint256 _allocation)
        public
        onlyOwner nonDuplicatedTokenAlloc(_token) validAllocation(_allocation)
    {
        // Token is marked as having an existing allocation
        tokenAllocExistence[_token] = true;
        tokensWithAllocation.push(address(_token));

        // Token's base allocation is set to the passed in value
        tokenBaseAlloc[_token] = _allocation;

        // Creating an allocation happens before any pools are created, so the shared allocation is 0 at present
        // No shared allocation needs to be added to the total allocation yet
        tokenSharedAlloc[_token] = 0;

        emit TokenAllocCreated(address(_token), _allocation);
    }

    /// @dev Update the allocation for a token. This modifies existing allocations for that token
    /// @param _token Token to update allocation for
    /// @param _allocation Updated allocation
    function setTokenSharedAlloc(IBEP20 _token, uint256 _allocation)
        public
        onlyOwner tokenAllocExists(_token)  validAllocation(_allocation)
    {
        // Recalculates the total shared allocation for the token
        //   Each elevation may already be live, so the shared allocation is the sum of the modulated base allocation at each live elevation
        uint256 updatedSharedAlloc = _allocation.mul(
            (tokenElevationIsEarning[_token][OASIS] ? 100 : 0) +
            (tokenElevationIsEarning[_token][TWOTHOUSAND] ? 110 : 0) +
            (tokenElevationIsEarning[_token][FIVETHOUSAND] ? 125 : 0) +
            (tokenElevationIsEarning[_token][TENTHOUSAND] ? 150 : 0)
        );

        // The current shared allocation of the token is removed from the total allocation across all tokens
        // The new shared allocation is then added back in
        totalSharedAlloc = totalSharedAlloc.sub(tokenSharedAlloc[_token]).add(updatedSharedAlloc);

        // Base and shared allocations are updated
        tokenSharedAlloc[_token] = updatedSharedAlloc;
        tokenBaseAlloc[_token] = _allocation;
        emit TokenAllocUpdated(address(_token), _allocation);
    }


    /// @dev Register pool at elevation as live, add to shared alloc
    /// @param _token Token of the pool
    /// @param _elevation Elevation of the pool
    /// @param _isEarning Whether token is earning SUMMIT at elevation
    function setIsTokenEarningAtElevation(IBEP20 _token, uint8 _elevation, bool _isEarning)
        external
        onlySubCartographer
    {
        // Fetches the modulated allocation for this token at elevation
        uint256 modAllocPoint = elevationHelper.elevationModulatedAllocation(tokenBaseAlloc[_token], _elevation);

        // Add / Remove the new allocation to the token's shared allocation
        tokenSharedAlloc[_token] = tokenSharedAlloc[_token]
            .add(_isEarning ? modAllocPoint : 0)
            .sub(_isEarning ? 0 : modAllocPoint);

        // Add / Remove the new allocation to the total shared allocation
        totalSharedAlloc = totalSharedAlloc
            .add(_isEarning ? modAllocPoint : 0)
            .sub(_isEarning ? 0 : modAllocPoint);

        // Mark the pool as earning
        tokenElevationIsEarning[_token][_elevation] = _isEarning;
    }


    /// @dev Sets the passthrough strategy for a given token
    /// @param _token Token passthrough strategy applies to
    /// @param _passthroughStrategy Address of the new passthrough strategy
    function setTokenPassthroughStrategy(IBEP20 _token, address _passthroughStrategy)
        public
        onlyOwner
    {
        // Validate that the strategy exists and tokens match
        require(_passthroughStrategy != address(0), "Passthrough strategy missing");
        require(address(IPassthrough(_passthroughStrategy).token()) == address(_token), "Token doesnt match passthrough strategy");

        _enactTokenPassthroughStrategy(_token, _passthroughStrategy);
    }


    /// @dev Retire passthrough strategy and return tokens to this contract
    /// @param _token Token whose passthrough strategy to remove
    function retireTokenPassthroughStrategy(IBEP20 _token)
        public
        onlyOwner
    {
        require(tokenPassthroughStrategy[_token] != address(0), "No passthrough strategy to retire");
        address retiredTokenPassthroughStrategy = tokenPassthroughStrategy[_token];
        _retireTokenPassthroughStrategy(_token);

        emit PassthroughStrategyRetired(address(_token), retiredTokenPassthroughStrategy);
    }


    function _enactTokenPassthroughStrategy(IBEP20 _token, address _passthroughStrategy)
        internal
    {
        // If strategy already exists for this pool, retire from it
        _retireTokenPassthroughStrategy(_token);

        // Deposit funds into new passthrough strategy
        IPassthrough(_passthroughStrategy).token().approve(_passthroughStrategy, uint256(-1));
        IPassthrough(_passthroughStrategy).enact();

        // Set token passthrough strategy in state
        tokenPassthroughStrategy[_token] = _passthroughStrategy;

        emit PassthroughStrategySet(address(_token), _passthroughStrategy);
    }


    /// @dev Internal functionality of retiring a passthrough strategy
    function _retireTokenPassthroughStrategy(IBEP20 _token) internal {
        // Early exit if token doesn't have passthrough strategy
        if(tokenPassthroughStrategy[_token] == address(0)) return;

        IPassthrough(tokenPassthroughStrategy[_token]).retire(expedAdd, devAdd);
        tokenPassthroughStrategy[_token] = address(0);
    }





    // ---------------------------------------
    // --   P O O L   M A N A G E M E N T
    // ---------------------------------------


    /// @dev Registers all existences of a pool (inlined for brevity)
    /// @param _token Token of pool
    /// @param _elevation Elevation of pool
    /// @return Pid of newly registered pool
    function registerPool(IBEP20 _token, uint8 _elevation) internal returns (uint16) {
        uint16 pid = uint16(poolIds.length);
        poolIds.push(pid);
        poolElevation[pid] = _elevation;
        tokenElevationPid[_token][_elevation] = pid;
        return pid;
    }


    /// @dev Creates a new pool for a token at a specific elevation
    /// @param _token Token to create the pool for
    /// @param _elevation The elevation to create this pool at
    /// @param _live Whether the pool is available for staking (independent of rounds / elevation constraints)
    /// @param _feeBP Fee for pool, max 1% is reserved for withdraw, rest is taken on deposit
    /// @param _withUpdate Whether to update all pools during this transaction
    function add(IBEP20 _token, uint8 _elevation, bool _live, uint16 _feeBP, bool _withUpdate)
        public
        onlyOwner tokenAllocExists(_token) oasisOrElevation(_elevation) nonDuplicated(_token, _elevation)
    {
        // Deposit fees will never be higher than 4%
        require(_feeBP <= 400, "Invalid fee");

        // Mass update if required
        if (_withUpdate) {
            massUpdatePools();
        }

        // Get the next available pool identifier and register pool
        uint16 pid = registerPool(_token, _elevation);

        // Create the pool in the appropriate sub cartographer
        subCartographer(_elevation).add(pid, _elevation, _live, _token, _feeBP);

        emit PoolCreated(pid, _elevation, address(_token));
    }


    /// @dev Create a new expedition
    /// @param _startRoundOffset Extra rounds to wait before opening the expedition
    /// @param _rewardToken Token that will be earned when staking Summit with the expedition
    /// @param _rewardAmount Total amount of _rewardToken that will be distributed over the total expedition duration
    /// @param _rounds Total number of rounds for this expedition to run
    function addExpedition(uint256 _startRoundOffset, IBEP20 _rewardToken, uint256 _rewardAmount, uint256 _rounds)
        public
        onlyOwner nonDuplicated(_rewardToken, EXPEDITION)
    {
        // Get the next available pid and register expedition
        uint16 pid = registerPool(_rewardToken, EXPEDITION);

        // Create expedition in cartographerExpedition
        cartographerExpedition.addExpedition(pid, true, _startRoundOffset, _rewardToken, _rewardAmount, _rounds);

        emit ExpeditionCreated(pid, address(_rewardToken), _rewardAmount, _rounds);
    }


    /// @dev Update pool's live status and deposit fee
    /// @param _pid Pool identifier
    /// @param _live Whether staking is permitted on this pool
    /// @param _feeBP Percentage fee on deposit / withdrawal
    /// @param _withUpdate whether to update all pools as part of this transaction
    function set(uint16 _pid, bool _live, uint16 _feeBP, bool _withUpdate)
        public
        onlyOwner oasisOrElevation(poolElevation[_pid]) poolExists(_pid)
    {
        require(_feeBP <= 400, "Invalid fee");

        // Mass update if required
        if (_withUpdate) {
            massUpdatePools();
        }

        // Updates the pool in the correct subcartographer
        // If the pool's live status changes, it will be funneled back to the cartographer in <enable|disabled>TokenAtElevation above
        subCartographer(poolElevation[_pid]).set(_pid, _live, _feeBP);

        emit PoolUpdated(_pid, _live, _feeBP, poolElevation[_pid]);
    }


    /// @dev Does what it says on the box
    function massUpdatePools() public {
        cartographerOasis.massUpdatePools();
        cartographerElevation.massUpdatePools();
    }





    // ---------------------------------------
    // --   P O O L   R E W A R D S
    // ---------------------------------------


    /// @dev The rewards breakdown earned by users in a given pool
    /// @param _pid Pool to check
    /// @param _userAdd User to check
    /// @return (
    ///     harvestableWinnings - Winnings the user has available to withdraw
    ///     vestingWinnings - Winnings the user has available to withdraw
    ///     vestingDuration - Total duration of the vesting period (used mostly for testing, not needed for frontend)
    ///     vestingStart - When the current vesting period started (used mostly for testing, not needed for frontend)
    /// )
    function rewards(uint16 _pid, address _userAdd)
        public view
        poolExists(_pid)
        returns (uint256, uint256, uint256, uint256)
    {
        return subCartographer(poolElevation[_pid]).rewards(_pid, _userAdd);
    }


    /// @dev The hypothetical rewards, and the hypothetical winnings from a given pool
    /// @param _pid Pool to check
    /// @param _userAdd User to check
    /// @return (
    ///     hypotheticalYield - The yield from staking, which has been risked during the current round
    ///     hypotheticalWinnings - If the user were to win the round, what their winnings would be based on:
    ///         . user's staking yield
    ///         . staking yield of each totem over the round
    ///         . staking yield of the entire pool over the round
    /// )
    function hypotheticalRewards(uint16 _pid, address _userAdd)
        public view
        poolExists(_pid)
        returns (uint256, uint256)
    {
        return subCartographer(poolElevation[_pid]).hypotheticalRewards(_pid, _userAdd);
    }





    // ------------------------------------------------------------------
    // --   R O L L O V E R   E L E V A T I O N   R O U N D
    // ------------------------------------------------------------------


    /// @dev Summit amount given as reward for rollover
    function rolloverRewardSummit() internal {
        (uint256 reserve0, uint256 reserve1,) = summitLp.getReserves();
        uint256 summitReserve = summitLp.token0() == address(summit) ? reserve0 : reserve1;
        uint256 nativeReserve = summitLp.token0() == address(summit) ? reserve1 : reserve0;
        if (rolloverRewardInNativeToken == 0 || nativeReserve == 0) return;
        summit.mintTo(msg.sender, rolloverRewardInNativeToken.mul(summitReserve).div(nativeReserve));
    }


    /// @dev Rolling over a round for an elevation and selecting winning totem.
    ///      Called by the webservice, but can also be called manually by any user (as failsafe)
    /// @param _elevation Elevation to rollover
    function rollover(uint8 _elevation)
        public
        elevationOrExpedition(_elevation)
    {
        // Ensure that the elevation is ready to be rolled over, ensures only a single user can perform the rollover
        elevationHelper.validateRolloverAvailable(_elevation);

        // Selects the winning totem for the round, storing it in the elevationHelper contract
        elevationHelper.selectWinningTotem(_elevation);

        // Update the round index in the elevationHelper, effectively starting the next round of play
        elevationHelper.rolloverElevation(_elevation);

        // Rollover active pools at the elevation
        subCartographer(_elevation).rollover(_elevation);

        // Give SUMMIT rewards to user that executed the rollover
        rolloverRewardSummit();

        emit Rollover(msg.sender, _elevation);
    }





    // -----------------------------------------------------
    // --   S U M M I T   E M I S S I O N
    // -----------------------------------------------------


    /// @dev Returns the time elapsed between two timestamps
    function timeDiffSeconds(uint256 _from, uint256 _to) public pure returns (uint256) {
        return _to.sub(_from);
    }

    /// @dev Returns the modulated allocation of a token at elevation, escaping early if the pool is not live
    /// @param _pid Pool identifier
    /// @return True allocation of the pool at elevation
    function elevationModulatedAllocation(uint16 _pid) public view returns (uint256) {
        // Escape early if the pool is not currently earning SUMMIT
        if (!isEarning(_pid)) { return 0; }

        // Fetch the modulated base allocation for the token at elevation
        return elevationHelper.elevationModulatedAllocation(tokenBaseAlloc[token(_pid, false)], poolElevation[_pid]);
    }


    /// @dev Shares of a token at elevation
    /// (@param _token, @param _elevation) Together identify the pool to calculate
    function tokenElevationShares(IBEP20 _token, uint8 _elevation) internal view returns (uint256) {
        // Escape early if the pool is not currently earning SUMMIT
        if (tokenElevationPid[_token][_elevation] == 0) return 0;

        // Gas Saver: This overlaps with same line in elevationModulatedAllocation, however elevationModulatedAllocation needs to be accurate independently for the frontend
        if (!isEarning(tokenElevationPid[_token][_elevation])) return 0;

        return stakedSupply(tokenElevationPid[_token][_elevation])
            .mul(elevationModulatedAllocation(tokenElevationPid[_token][_elevation]));
    }

    /// @dev The share of the total token at elevation emission awarded to the pool
    ///      Tokens share allocation to ensure that staking at higher elevation ALWAYS has higher APY
    ///      This is done to guarantee a higher ROI at higher elevations over a long enough time span
    ///      The allocation of each pool is based on the elevation, as well as the staked supply at that elevation
    /// @param _token The token (+ elevation) to evaluate emission for
    /// @param _elevation The elevation (+ token) to evaluate emission for
    /// @return The share of emission granted to the pool, raised to 1e12
    function tokenElevationEmissionMultiplier(IBEP20 _token, uint8 _elevation)
        public view
        returns (uint256)
    {
        // Shares for all elevation are summed. For each elevation the shares are calculated by:
        //   . The staked supply of the pool at elevation multiplied by
        //   . The modulated allocation of the pool at elevation
        uint256 totalTokenShares = tokenElevationShares(_token, OASIS)
            .add(tokenElevationShares(_token, TWOTHOUSAND))
            .add(tokenElevationShares(_token, FIVETHOUSAND))
            .add(tokenElevationShares(_token, TENTHOUSAND));

        // Escape early if nothing is staked in any of the token's pools
        if (totalTokenShares == 0) { return 0; }

        // Divide the target pool (token + elevation) shares by total shares (as calculated above)
        return tokenElevationShares(_token, _elevation).mul(1e12).div(totalTokenShares);
    }


    /// @dev Uses the tokenElevationEmissionMultiplier along with timeDiff and token allocation to calculate the overall emission multiplier of the pool
    /// @param _lastRewardTimestamp Calculate the difference to determine emission event count
    /// (@param _token, @param elevation) Pool identifier for calculation
    /// @return Share of overall emission granted to the pool, raised to 1e12
    function poolEmissionMultiplier(uint256 _lastRewardTimestamp, IBEP20 _token, uint8 _elevation)
        internal view
        returns (uint256)
    {
        // Escape early if no total allocation exists
        if (totalSharedAlloc == 0) { return 0; }

        // Calculate overall emission granted over time span, calculated by:
        //   . Time difference from last reward timestamp
        //   . Tokens allocation as a fraction of total allocation
        //   . Pool's emission multiplier
        return timeDiffSeconds(_lastRewardTimestamp, block.timestamp).mul(1e12)
            .mul(tokenSharedAlloc[_token]).div(totalSharedAlloc)
            .mul(tokenElevationEmissionMultiplier(_token, _elevation)).div(1e12);
    }


    /// @dev Uses poolEmissionMultiplier along with staking summit emission to calculate the pools summit emission over the time span
    /// @param _lastRewardTimestamp Used for time span
    /// (@param _token, @param _elevation) Pool identifier
    /// @return emission of SUMMIT, not raised to any power
    function poolSummitEmission(uint256 _lastRewardTimestamp, IBEP20 _token, uint8 _elevation)
        external view
        onlySubCartographer
        returns (uint256)
    {
        // Escape early if no time has passed
        if (_lastRewardTimestamp == block.timestamp) { return 0; }

        // Emission multiplier multiplied by summitPerSecond, finally reducing back to true exponential
        return poolEmissionMultiplier(_lastRewardTimestamp, _token, _elevation)
            .mul(summitPerSecond).div(1e12);
    }


    /// @dev Mints the total emission of pool and split respectively to destinations
    /// @param _lastRewardTimestamp Used for time span
    /// (@param _token, @param _elevation) Pool identifier
    /// @return only staking SUMMIT yield component of emission, not raised to any power
    function mintPoolSummit(uint256 _lastRewardTimestamp, IBEP20 _token, uint8 _elevation)
        external
        onlySubCartographer
        returns (uint256)
    {
        uint256 emissionMultiplier = poolEmissionMultiplier(_lastRewardTimestamp, _token, _elevation);

        // Mint summit to all destinations accordingly
        summit.mintTo(devAdd, emissionMultiplier.mul(devSummitPerSecond).div(1e12));
        summit.mintTo(address(summitReferrals), emissionMultiplier.mul(referralsSummitPerSecond).div(1e12));
        summit.mintTo(address(this), emissionMultiplier.mul(summitPerSecond).div(1e12));

        // This return value is used by pools to divy shares, and doesn't need the referrals or dev components included
        return emissionMultiplier.mul(summitPerSecond).div(1e12);
    }





    // -----------------------------------------------------
    // --   S W I T C H   T O T E M
    // -----------------------------------------------------


    /// @dev All funds at an elevation share a totem. This function allows switching staked funds from one totem to another
    /// @param _elevation Elevation to switch totem on
    /// @param _totem New target totem
    function switchTotem(uint8 _elevation, uint8 _totem)
        public
        nonReentrant elevationOrExpedition(_elevation)
    {
        // Totem must be less than the totem count of the elevation
        require(_totem < elevationHelper.totemCount(_elevation), "Invalid totem");

        // Executes the totem switch in the correct subcartographer
        subCartographer(_elevation).switchTotem(_elevation, _totem, msg.sender);

        emit SwitchTotem(msg.sender, _elevation, _totem);
    }





    // -----------------------------------------------------
    // --   P O O L   I N T E R A C T I O N S
    // -----------------------------------------------------


    /// @dev Helper function for getting the cross compound pool pid for a given elevation
    function getCrossCompoundPid(uint8 _elevation) internal view returns (uint16) {
        require(tokenElevationPid[summit][_elevation] != 0, "No cross compound target found");
        require(token(tokenElevationPid[summit][_elevation], false) == summit, "Cross compound pool must be SUMMIT");
        return tokenElevationPid[summit][_elevation];
    }


    /// @dev Harvest available rewards from any pool at any elevation (except expedition) and stake it in the SUMMIT pool automatically
    /// @param _pid Pool to harvest rewards from
    /// @param _totem Totem to deposit with, should already be known but passed in to ensure
    function crossCompound(uint16 _pid, uint8 _totem)
        public
        nonReentrant poolExists(_pid) oasisOrElevation(poolElevation[_pid])
    {
        // Fetch elevation of source 
        uint8 elevation = poolElevation[_pid];

        // Determine crossCompoundPid
        uint16 crossCompoundPid = (token(_pid, false) == summit) ? _pid : getCrossCompoundPid(elevation); 

        // Execute cross compound
        uint256 crossCompounded = subCartographer(elevation).crossCompound(_pid, crossCompoundPid, _totem, msg.sender);

        emit CrossCompound(msg.sender, _pid, crossCompoundPid, crossCompounded);
    }


    /// @dev Stake funds with a pool, is also used to harvest with a deposit of 0
    /// @param _pid Pool identifier
    /// @param _amount Amount to stake
    /// @param _expedSummitLpAmount Amount of SUMMIT LP to deposit in an expedition
    /// @param _totem Totem to deposit with, must match existing totem if anything staked at the elevation already
    function deposit(uint16 _pid, uint256 _amount, uint256 _expedSummitLpAmount, uint8 _totem)
        public
        nonReentrant poolExists(_pid)
    {
        // Validates that the totem is available at the elevation
        uint8 elevation = poolElevation[_pid];
        require(elevation == OASIS || _totem < elevationHelper.totemCount(poolElevation[_pid]), "Invalid totem");

        // Executes the deposit in the sub cartographer
        (uint256 amountAfterFee, uint256 amountExpedSummitLp) = subCartographer(elevation).deposit(_pid, _amount, _expedSummitLpAmount, _totem, msg.sender);

        emit Deposit(msg.sender, _pid, amountAfterFee, amountExpedSummitLp);
    }


    /// @dev Harvest all rewards (or cross compound) of an elevation (only elevation, not oasis or expedition)
    /// @param _elevation Elevation to harvest all rewards from
    /// @param _crossCompound Whether to harvest rewards directly into SUMMIT farm at {_elevation}
    function harvestElevation(uint8 _elevation, bool _crossCompound)
        public
        nonReentrant
    {
        // Ensure only being called on elevation farms
        _validElev(_elevation, 1, 3);

        // Get cross compound pid if attempting to cross compound
        uint16 crossCompoundPid = _crossCompound ? getCrossCompoundPid(_elevation) : 0;

        // Harvest across an elevation, return total amount harvested
        uint256 totalHarvested = subCartographer(_elevation).harvestElevation(_elevation, crossCompoundPid, msg.sender);
        
        emit HarvestElevation(msg.sender, _elevation, _crossCompound, totalHarvested);
    }


    /// @dev Withdraw staked funds from a pool
    /// @param _pid Pool identifier
    /// @param _amount Amount to withdraw, must be > 0 and <= staked amount
    /// @param _expedSummitLpAmount Amount of SUMMIT LP to withdraw from an expedition
    function withdraw(uint16 _pid, uint256 _amount, uint256 _expedSummitLpAmount)
        public
        nonReentrant poolExists(_pid)
    {
        // Executes the withdrawal in the sub cartographer
        (uint256 amountAfterFee, uint256 amountExpedSummitLp) = subCartographer(poolElevation[_pid]).withdraw(_pid, _amount, _expedSummitLpAmount, msg.sender);

        emit Withdraw(msg.sender, _pid, amountAfterFee, amountExpedSummitLp);
    }


    /// @dev Validation step of Elevate into separate function
    function validateElevate(uint16 _sourcePid, uint16 _targetPid, uint256 _amount, IBEP20 _token, uint8 _totem)
        internal
        nonReentrant poolExists(_sourcePid) poolExists(_targetPid)
    {
        // Validate that at least some amount is being elevated
        require(_amount > 0, "Transfer non zero amount");

        // Elevating funds must change elevation
        require(poolElevation[_sourcePid] != poolElevation[_targetPid], "Must change elev");

        // Validate target totem exists at elevation
        require(_totem < elevationHelper.totemCount(poolElevation[_targetPid]), "Invalid totem");

        // Validate deposit token
        (bool totemInUse, uint8 selectedTotem) = userTotem(poolElevation[_targetPid], msg.sender);
        require(!totemInUse || selectedTotem == _totem, "Cant switch totem during elevate");

        // Validate pools share same token
        bool isSummitLpElevate = _token == summitLp && (poolElevation[_sourcePid] == EXPEDITION || poolElevation[_targetPid] == EXPEDITION);
        require(token(_sourcePid, isSummitLpElevate) == token(_targetPid, isSummitLpElevate), "Different token");
    }


    /// @dev Allows funds to be transferred between elevations without forcing users to pay a deposit fee
    /// @param _sourcePid Current pool to withdraw funds from
    /// @param _targetPid Pool to then deposit these funds into
    /// @param _amount Amount of funds to transfer across elevations, must be > 0
    /// @param _token Token to be elevated
    /// @param _totem Totem to deposit funds into in new pool
    function elevate(uint16 _sourcePid, uint16 _targetPid, uint256 _amount, IBEP20 _token, uint8 _totem)
        public
    {
        validateElevate(_sourcePid, _targetPid, _amount, _token, _totem);

        // Withdraw {_amount} of {_token} to {_sourcePid} pool / expedition at sourcePoolElevation to elevate it
        uint256 elevatedAmount = subCartographer(poolElevation[_sourcePid])
            .elevateWithdraw(
                _sourcePid,
                _amount,
                address(_token),
                msg.sender
            );
        
        // Deposit withdrawn amount of {_token} from source pool {elevatedAmount} into {_targetPid} pool / expedition
        elevatedAmount = subCartographer(poolElevation[_targetPid])
            .elevateDeposit(
                _targetPid,
                elevatedAmount,
                address(_token),
                _totem,
                msg.sender
            );

        emit Elevate(msg.sender, _sourcePid, _targetPid, _totem, elevatedAmount);
    }





    // -----------------------------------------------------
    // --   T O K E N   M A N A G E M E N T
    // -----------------------------------------------------

    /// @dev Utility function for depositing tokens into passthrough strategy
    function passthroughDeposit(IBEP20 _token, uint256 _amount) internal returns (uint256) {
        if (tokenPassthroughStrategy[_token] == address(0)) return _amount;
        return IPassthrough(tokenPassthroughStrategy[_token]).deposit(_amount, expedAdd, devAdd);
    }

    /// @dev Utility function for withdrawing tokens from passthrough strategy
    /// @param _token Token to withdraw from it's passthrough strategy
    /// @param _amount Amount requested to withdraw
    /// @return The true amount withdrawn from the passthrough strategy after the passthrough's fee was taken (if any)
    function passthroughWithdraw(IBEP20 _token, uint256 _amount) internal returns (uint256) {
        if (tokenPassthroughStrategy[_token] == address(0)) return _amount;
        return IPassthrough(tokenPassthroughStrategy[_token]).withdraw(_amount, expedAdd, devAdd);
    }

    /// @dev Utility function to transfer Summit
    function safeSummitTransfer(address _to, uint256 _amount) internal {
        uint256 summitBal = summit.balanceOf(address(this));
        bool transferSuccess = false;
        if (_amount > summitBal) {
            transferSuccess = summit.transfer(_to, summitBal);
        } else {
            transferSuccess = summit.transfer(_to, _amount);
        }
        require(transferSuccess, "SafeSummitTransfer: failed");
    }


    /// @dev Utility function to handle harvesting Summit rewards with referral rewards
    function redeemRewards(address _userAdd, uint256 _amount) external onlySubCartographer {
        // Transfers rewards to user
        safeSummitTransfer(_userAdd, _amount);

        // If the user has been referred, add the 1% bonus to that user and their referrer
        summitReferrals.addReferralRewardsIfNecessary(_userAdd, _amount);

        emit RedeemRewards(_userAdd, _amount);
    }


    /// @dev Takes the deposit fee where applicable
    /// @param _token Token to take fee out of
    /// @param _feeBP Fee to take
    /// @param _amount Deposit amount to take fee from
    /// @return Amount deposited after fee
    function takeDepositFee(IBEP20 _token, uint256 _feeBP, uint256 _amount)
        internal
        returns (uint256)
    {
        uint256 trueFee = _feeBP <= 50 ? 0 : _feeBP - 50;
        if (trueFee == 0) return _amount;

        // Calculate deposit fee
        uint256 _depositFee = _amount.mul(trueFee).div(10000);

        // Half of deposit fee is sent to expedition accumulator, other half sent to devs
        uint256 _depositFeeHalf = _depositFee.div(2);
        _token.safeTransfer(expedAdd, _depositFeeHalf);
        _token.safeTransfer(devAdd, _depositFeeHalf);
        return _amount.sub(_depositFee);
    }


    /// @dev Transfers funds from user on deposit
    /// @param _userAdd Depositing user
    /// @param _token Token to deposit
    /// @param _feeBP Deposit fee if applicable
    /// @param _amount Deposit amount before fee
    /// @param _expedSummitLpAmount Deposit expedition SUMMIT LP amount before fee
    /// @return Deposit amount after fee
    function depositTokenManagement(address _userAdd, IBEP20 _token, uint256 _feeBP, uint256 _amount, uint256 _expedSummitLpAmount)
        external
        onlySubCartographer
        returns (uint256)
    {
        // Expedition Withdraw of SUMMIT LP
        if (_expedSummitLpAmount > 0) {
            IBEP20(address(summitLp)).safeTransferFrom(_userAdd, address(this), _expedSummitLpAmount);
        }

        // Transfers total deposit amount
        _token.safeTransferFrom(_userAdd, address(this), _amount);

        // Takes deposit fee within cartographer if no passthrough strategy
        uint256 amountAfterFee = takeDepositFee(_token, _feeBP, _amount);

        // Deposits into passthrough target, if there are any passthrough strategy fees (shouldn't be any), they are taken
        amountAfterFee = passthroughDeposit(_token, amountAfterFee);


        return amountAfterFee;
    }


    /// @dev Takes the remaining withdrawal fee (difference between total withdrawn amount and the amount expected to be withdrawn after the remaining fee)
    /// @param _token Token to withdraw
    /// @param _amount Funds above the amount after remaining withdrawal fee that was returned from the passthrough strategy
    function takeWithdrawFeeAmount(IBEP20 _token, uint256 _amount)
        internal
    {
        _token.safeTransfer(devAdd, _amount.div(2));
        _token.safeTransfer(expedAdd, _amount.div(2));
    }

    /// @dev Transfers funds to user on withdraw
    /// @param _userAdd Withdrawing user
    /// @param _token Token to withdraw
    /// @param _feeBP The overall pool fee, 1% of which is taken on withdrawal
    /// @param _amount Withdraw amount
    /// @param _expedSummitLpAmount Exped SUMMIT LP withdraw amount
    /// @return Amount withdrawn after fee
    function withdrawalTokenManagement(address _userAdd, IBEP20 _token, uint256 _feeBP, uint256 _amount, uint256 _expedSummitLpAmount)
        external
        onlySubCartographer
        returns (uint256)
    {
        // Expedition Withdraw of SUMMIT LP
        if (_expedSummitLpAmount > 0) {
            IBEP20(address(summitLp)).safeTransfer(_userAdd, _expedSummitLpAmount);
        }


        // Withdraw full amount from passthrough (if any), if there is a fee that isn't covered by the increase in vault value this may be less than expected full amount
        uint256 amountAfterFee = passthroughWithdraw(_token, _amount);

        // Amount user expects to receive after fee taken
        uint256 remainingFee = _feeBP > 50 ? 50 : _feeBP;
        uint256 expectedWithdrawnAmount = _amount.mul(uint256(10000).sub(remainingFee)).div(10000);

        // Take any remaining fee (gap between what was actually withdrawn, and what the user expects to receive)
        if (amountAfterFee > expectedWithdrawnAmount) {
            takeWithdrawFeeAmount(_token, amountAfterFee.sub(expectedWithdrawnAmount));
            amountAfterFee = expectedWithdrawnAmount;
        }

        // Transfer funds back to user
        _token.safeTransfer(_userAdd, amountAfterFee);

        return amountAfterFee;
    }





    // -----------------------------------------------------
    // --   R E F E R R A L S
    // -----------------------------------------------------


    /// @dev Roll over referral round and burn unclaimed referral rewards
    function rolloverReferral()
        public
    {
        // Validate that a referral burn is unlocked
        elevationHelper.validateReferralBurnAvailable();

        // Burn unclaimed rewards and rollover round
        summitReferrals.burnUnclaimedReferralRewardsAndRolloverRound(msg.sender);

        // Rollover round number in elevation helper
        elevationHelper.rolloverReferralBurn();

        // Give SUMMIT rewards to user that executed the rollover
        rolloverRewardSummit();

        emit RolloverReferral(msg.sender);
    }

    /// @dev Referral burn timestamp for frontend
    function referralBurnTimestamp() public view returns(uint256) {
        return elevationHelper.referralBurnTimestamp();
    }

    /// @dev Safety hatch for referral reward round rollover burning needed summit
    function referralRewardsMintSafetyHatch(uint256 _amount) public {
        // Only callable by summitReferrals contract
        require(msg.sender == address(summitReferrals), "Only Summit Referrals");
        require(_amount > 0, "Non zero");

        summit.mintTo(address(summitReferrals), _amount);
    }
}

File 3 of 21 : SummitToken.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./libs/BEP20.sol";

// SummitToken with Governance.
contract SummitToken is BEP20('summitdefi.com', 'SUMMIT') {

    /// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef).
    function mintTo(address _to, uint256 _amount) public onlyOwner {
        _mint(_to, _amount);
    }
}

File 4 of 21 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) return (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}

File 5 of 21 : CartographerOasis.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./Cartographer.sol";
import "./ISubCart.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./libs/IBEP20.sol";
import "./libs/SafeBEP20.sol";
import "./SummitToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";


/*
---------------------------------------------------------------------------------------------
--   S U M M I T . D E F I
---------------------------------------------------------------------------------------------


Summit is highly experimental.
It has been crafted to bring a new flavor to the defi world.
We hope you enjoy the Summit.defi experience.
If you find any bugs in these contracts, please claim the bounty (see docs)


Created with love by Architect and the Summit team





---------------------------------------------------------------------------------------------
--   O A S I S   E X P L A N A T I O N
---------------------------------------------------------------------------------------------


The OASIS is the safest of the elevations.
OASIS pools exactly mirror standard yield farming experiences of other projects.
OASIS pools guarantee yield, and no multiplying or risk takes place at this elevation.
The OASIS does not have totems in the contract, however in the frontend funds staked in the OASIS are represented by the OTTER.

*/
contract CartographerOasis is ISubCart, Ownable, Initializable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeBEP20 for IBEP20;
    


    // ---------------------------------------
    // --   V A R I A B L E S
    // ---------------------------------------

    Cartographer cartographer;
    uint256 public launchTimestamp = 1641028149;                        // 2022-1-1, will be updated when summit ecosystem switched on
    
    struct UserInfo {
        uint256 debt;                                                   // Debt is (accSummitPerShare * staked) at time of staking and is used in the calculation of yield.
        uint256 staked;                                                 // The amount a user invests in an OASIS pool
    }

    struct OasisPoolInfo {
        uint16 pid;                                                     // Pool identifier
        IBEP20 token;                                                   // Reward token yielded by the pool
        uint256 supply;                                                 // Running total of the amount of tokens staked in the pool
        bool live;                                                      // Turns on and off the pool
        uint256 lastRewardTimestamp;                                    // Latest timestamp that SUMMIT distribution occurred
        uint256 accSummitPerShare;                                      // Accumulated SUMMIT per share, raised to 1e12
        uint16 feeBP;                                                   // Fee of the pool, 1% taken on withdraw, remainder on deposit
    }


    uint8 constant OASIS = 0;                                           // Named constant to make reusable elevation functions easier to parse visually
    uint16[] public oasisPIDs;                                          // List of all pools in the oasis
    mapping(uint16 => bool) public pidExistence;                        // Whether a specific pool identifier exists in the oasis
    mapping(IBEP20 => bool) public poolExistence;                       // Whether a pool exists for a token at the oasis
    mapping(uint16 => OasisPoolInfo) public oasisPoolInfo;              // Pool info for each oasis pool
    mapping(uint16 => mapping(address => UserInfo)) public userInfo;    // Users running staking information
    
    





    // ---------------------------------------
    // --  A D M I N I S T R A T I O N
    // ---------------------------------------


    /// @dev Constructor simply setting address of the cartographer
    constructor(address _Cartographer)
        public
    {
        require(_Cartographer != address(0), "Cartographer required");
        cartographer = Cartographer(_Cartographer);
    }

    /// @dev Unused initializer as part of the SubCartographer interface
    function initialize(address _ElevationHelper, address, address) external override initializer onlyCartographer {}

    /// @dev Enables the Summit ecosystem with a timestamp, called by the Cartographer
    function enable(uint256 _launchTimestamp)
        external override
        onlyCartographer
    {
        launchTimestamp = _launchTimestamp;
    }
    





    // -----------------------------------------------------------------
    // --   M O D I F I E R S (Many are split to save contract size)
    // -----------------------------------------------------------------

    modifier onlyCartographer() {
        require(msg.sender == address(cartographer), "Only cartographer");
        _;
    }
    modifier validUserAdd(address userAdd) {
        require(userAdd != address(0), "User not 0");
        _;
    }
    modifier nonDuplicated(uint16 _pid, IBEP20 _token) {
        require(pidExistence[_pid] == false && poolExistence[_token] == false, "duplicated!");
        _;
    }
    modifier poolExists(uint16 _pid) {
        require(pidExistence[_pid], "Pool doesnt exist");
        _;
    }
    




    // ---------------------------------------
    // --   U T I L S (inlined for brevity)
    // ---------------------------------------
    

    function supply(uint16 _pid) external view override returns (uint256) {
        return oasisPoolInfo[_pid].supply;
    }
    function token(uint16 _pid, bool) external view override returns (IBEP20) {
        return oasisPoolInfo[_pid].token;
    }
    function depositFee(uint16 _pid) external view override returns (uint256) {
        return oasisPoolInfo[_pid].feeBP;
    }
    function isEarning(uint16 _pid) external view override returns (bool) {
        return oasisPoolInfo[_pid].live;
    }
    function selectedTotem(uint8, address) external view override returns (uint8) {
        return 0;
    }



    // ---------------------------------------
    // --   P O O L   M A N A G E M E N T
    // ---------------------------------------


    /// @dev Registers pool everywhere needed
    /// @param _pid Pool identifier
    /// @param _token Token to register with
    /// @param _live Whether pool is enabled at time of creation
    function registerPool(uint16 _pid, IBEP20 _token, bool _live) internal {
        // Marks token as enabled at elevation, and adds token allocation to token's shared allocation and total allocation
        if (_live) cartographer.setIsTokenEarningAtElevation(_token, OASIS, true);

        // Add pid to lookup list for iterative functions
        oasisPIDs.push(_pid);

        // Prevent duplicate pools for a single token
        poolExistence[_token] = true;

        // Used to verify if pool exists in other functions
        pidExistence[_pid] = true;
    }


    /// @dev Creates a pool at the oasis
    /// @param _pid Pool identifier selected by cartographer
    /// @param _live Whether the pool is enabled initially
    /// @param _token Token yielded by pool
    /// @param _feeBP Deposit fee taken
    function add(uint16 _pid, uint8,  bool _live, IBEP20 _token, uint16 _feeBP)
        external override
        onlyCartographer nonDuplicated(_pid, _token)
    {
        // Register pid and token where needed
        registerPool(_pid, _token, _live);

        // Create the initial state of the pool
        oasisPoolInfo[_pid] = OasisPoolInfo({
            pid: _pid,
            token: _token,
            supply: 0,
            live: _live,
            accSummitPerShare: 0,
            lastRewardTimestamp: block.timestamp,
            feeBP: _feeBP
        });
    }

    
    /// @dev Unused expedition functionality
    function addExpedition(uint16, bool, uint256, IBEP20, uint256, uint256) external override onlyCartographer {}

    
    /// @dev Update a given pools deposit or live status
    /// @param _pid Pool identifier
    /// @param _live If pool is available for staking
    /// @param _feeBP Deposit fee of pool
    function set(uint16 _pid, bool _live, uint16 _feeBP)
        external override
        onlyCartographer poolExists(_pid)
    {
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];
        updatePool(_pid);

        // If live status of pool changes, update cartographer allocations
        if (pool.live != _live) cartographer.setIsTokenEarningAtElevation(pool.token, OASIS, _live);

        // Update internal pool states
        pool.live = _live;
        pool.feeBP = _feeBP;
    }


    /// @dev Update all pools to current timestamp before other pool management transactions
    function massUpdatePools()
        external override
        onlyCartographer
    {
        for (uint16 i = 0; i < oasisPIDs.length; i++) {
            updatePool(oasisPIDs[i]);
        }
    }
    

    /// @dev Bring reward variables of given pool current
    /// @param _pid Pool identifier to update
    function updatePool(uint16 _pid)
        public
        poolExists(_pid)
    {
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];

        // Early exit if pool already current
        if (pool.lastRewardTimestamp == block.timestamp) { return; }

        // Early exit if pool not launched, has 0 supply, or isn't live.
        // Still update last rewarded timestamp to prevent over emitting on first block on return to live
        if (block.timestamp < launchTimestamp || pool.supply == 0 || !pool.live) {
            pool.lastRewardTimestamp = block.timestamp;
            return;
        }

        // Ensure that pool doesn't earn rewards from before summit ecosystem launched
        if (pool.lastRewardTimestamp < launchTimestamp) {
            pool.lastRewardTimestamp = launchTimestamp;
        }

        // Mint Summit according to pool allocation and token share in pool, retrieve amount of summit minted for staking
        uint256 summitReward = cartographer.mintPoolSummit(pool.lastRewardTimestamp, pool.token, OASIS);

        // Update accSummitPerShare with the amount of staking summit minted.
        pool.accSummitPerShare = pool.accSummitPerShare.add(summitReward.mul(1e12).div(pool.supply));

        // Bring last reward timestamp current
        pool.lastRewardTimestamp = block.timestamp;
    }
    




    // ---------------------------------------
    // --   P O O L   R E W A R D S
    // ---------------------------------------


    /// @dev Fetch guaranteed yield rewards of the pool
    /// @param _pid Pool to fetch rewards from
    /// @param _userAdd User requesting rewards info
    /// @return (
    ///     harvestableRewards - Amount of Summit available to harvest
    ///     vestingWinnings - Not applicable in OASIS
    ///     vestingDuration - Not applicable in OASIS
    ///     vestingStart - Not applicable in OASIS
    /// )
    function rewards(uint16 _pid, address _userAdd)
        external view override
        poolExists(_pid) validUserAdd(_userAdd)
    returns (uint256, uint256, uint256, uint256) {
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Temporary accSummitPerShare to bring rewards current if last reward timestamp is behind current timestamp
        uint256 accSummitPerShare = pool.accSummitPerShare;

        // Bring current if last reward timestamp is in past
        if (block.timestamp > launchTimestamp && block.timestamp > pool.lastRewardTimestamp && pool.supply != 0 && pool.live) {

            // Fetch the pool's summit yield emission to bring current
            uint256 poolSummitEmission = cartographer.poolSummitEmission(
                pool.lastRewardTimestamp < launchTimestamp ? launchTimestamp : pool.lastRewardTimestamp,
                pool.token,
                OASIS);

            // Recalculate accSummitPerShare with additional yield emission included
            accSummitPerShare = accSummitPerShare.add(poolSummitEmission.mul(1e12).div(pool.supply));
        }

        // Return harvestableRewards, other rewards variables are not applicable in the OASIS
        return (user.staked.mul(accSummitPerShare).div(1e12).sub(user.debt), 0, 0, 0);
    }






    // ------------------------------------------------------------------
    // --   Y I E L D    G A M B L I N G   S T U B S
    // ------------------------------------------------------------------
    

    function hypotheticalRewards(uint16, address) external view override returns (uint256, uint256) { return (uint256(0), 0); }
    function rollover(uint8) external override {}
    function switchTotem(uint8, uint8, address) external override {}
    function isTotemInUse(uint8, address) external view override returns (bool) {}





    // -----------------------------------------------------
    // --   P O O L   I N T E R A C T I O N S
    // -----------------------------------------------------


    /// @dev Harvest any available rewards, and return that amount to be deposited in SUMMIT pool at same elevation
    /// @param _harvestPid Pool to harvest and cross compound rewards from
    /// @param _summitPid Pool to cross compound from
    /// @param _userAdd User requesting cross compound
    /// @return Amount cross compounded
    function crossCompound(uint16 _harvestPid, uint16 _summitPid, uint8, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_harvestPid) poolExists(_summitPid) validUserAdd(_userAdd)
        returns (uint256)
    {
        // HARVEST REWARDS
        OasisPoolInfo storage harvestPool = oasisPoolInfo[_harvestPid];
        OasisPoolInfo storage summitPool = oasisPoolInfo[_summitPid];
        UserInfo storage harvestUser = userInfo[_harvestPid][_userAdd];
        UserInfo storage summitUser = userInfo[_summitPid][_userAdd];
        
        updatePool(harvestPool.pid);

        // Calculate harvestable rewards
        uint256 harvestable = harvestUser.staked.mul(harvestPool.accSummitPerShare).div(1e12).sub(harvestUser.debt);
        require(harvestable > 0, "Nothing to cross compound");

        // Update users debt to prevent double dipping
        harvestUser.debt = harvestUser.staked.mul(harvestPool.accSummitPerShare).div(1e12);

        // Deposit harvestable into summit pool at this elevation
        return unifiedDeposit(summitPool, summitUser, harvestable, _userAdd, true);
    }

    /// @dev Stub for oasis (can have more than 12 active pools at oasis so no way to harvest all)
    function harvestElevation(uint8, uint16, address) external override returns (uint256) {}

    /// @dev Stake funds in an OASIS pool
    /// @param _pid Pool to stake in
    /// @param _amount Amount to stake
    /// @param _userAdd User wanting to stake
    /// @return Amount deposited after deposit fee taken
    function deposit(uint16 _pid, uint256 _amount, uint256, uint8, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256, uint256)
    {
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Used shared deposit functionality with elevateDeposit
        return (unifiedDeposit(pool, user, _amount, _userAdd, false), 0);
    }


    /// @dev Elevate funds to the OASIS through the elevate pipeline
    /// @param _pid Pool to deposit funds in
    /// @param _amount Amount to elevate to OASIS
    /// @param _userAdd User elevating funds
    /// @return Amount deposited after fee taken
    function elevateDeposit(uint16 _pid, uint256 _amount, address, uint8, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256)
    {
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];   

        // Use shared deposit functionality with standard deposit 
        return unifiedDeposit(pool, user, _amount, _userAdd, true);
    }


    /// @dev Internal shared deposit functionality for elevate or standard deposit
    /// @param pool OasisPoolInfo of pool to deposit into
    /// @param user UserInfo of depositing user
    /// @param _amount Amount to deposit
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality if transfer is exclusively within summit ecosystem
    /// @return Amount deposited after fee taken
    function unifiedDeposit(OasisPoolInfo storage pool, UserInfo storage user, uint256 _amount, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256)
    {
        updatePool(pool.pid);

        // If user has previous amount staked, then harvest the rewards of that staked amount
        if (user.staked > 0) {
            // Calculate pending rewards for user
            uint256 harvestable = user.staked.mul(pool.accSummitPerShare).div(1e12).sub(user.debt);
            // If user has harvestable rewards, redeem them
            if (harvestable > 0) {
                cartographer.redeemRewards(_userAdd, harvestable);
            }
        }

        uint256 amountAfterFee = _amount;

        // Handle taking fees and adding to running supply if amount depositing is non zero
        if (_amount > 0) {

            // Only move tokens (and take fee) on external transactions
            if (!_isInternalTransfer) {
                amountAfterFee = cartographer.depositTokenManagement(_userAdd, pool.token, pool.feeBP, _amount, 0);
            }
            
            // Increment running pool supply with amount after fee taken
            pool.supply = pool.supply.add(amountAfterFee);
        }
        
        // Update user info with new staked value, and calculate new debt
        user.staked = user.staked.add(amountAfterFee);
        user.debt = user.staked.mul(pool.accSummitPerShare).div(1e12);

        // Return amount staked after fee        
        return amountAfterFee;
    }


    /// @dev Withdraw staked funds from pool
    /// @param _pid Pool to withdraw from
    /// @param _amount Amount to withdraw
    /// @param _userAdd User withdrawing
    /// @return True amount withdrawn
    function withdraw(uint16 _pid, uint256 _amount, uint256, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256, uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];

        // Validate amount attempting to withdraw
        require(_amount > 0 && user.staked >= _amount, "Bad withdrawal");
        
        // Shared functionality withdraw with elevateWithdraw
        return (unifiedWithdraw(pool,  user, _amount, _userAdd, false), 0);
    }


    /// @dev Withdraw staked funds to elevate them to another elevation
    /// @param _pid Pool to elevate funds from
    /// @param _amount Amount of funds to elevate
    /// @param _userAdd User elevating
    /// @return Amount withdrawn to be elevated
    function elevateWithdraw(uint16 _pid, uint256 _amount, address, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];       
        OasisPoolInfo storage pool = oasisPoolInfo[_pid];

        // Validate the amount to elevate
        require(_amount > 0 && user.staked >= _amount, "Bad transfer");

        // Shared withdraw functionality with standard withdraw
        return unifiedWithdraw(pool, user, _amount, _userAdd, true);
    }

    
    /// @dev Withdraw functionality shared between standardWithdraw and elevateWithdraw
    /// @param pool OasisPoolInfo of pool to withdraw from
    /// @param user UserInfo of withdrawing user
    /// @param _amount Amount to withdraw
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality for elevate withdraw
    /// @return Amount withdrawn
    function unifiedWithdraw(OasisPoolInfo storage pool, UserInfo storage user, uint256 _amount, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256)
    {
        updatePool(pool.pid);

        // Check harvestable rewards and withdraw if applicable
        uint256 harvestable = user.staked.mul(pool.accSummitPerShare).div(1e12).sub(user.debt);
        if (harvestable > 0) {
            cartographer.redeemRewards(_userAdd, harvestable);
        }

        // Update pool running supply total with amount withdrawn
        pool.supply = pool.supply.sub(_amount);   

        // Update user's staked and debt     
        user.staked = user.staked.sub(_amount);
        user.debt = user.staked.mul(pool.accSummitPerShare).div(1e12);
        

        // Signal cartographer to perform withdrawal function if not elevating funds
        // Elevated funds remain in the cartographer, or in the passthrough target, so no need to withdraw from anywhere as they would be immediately re-deposited
        uint256 amountAfterFee = _amount;
        if (!_isInternalTransfer) {
            amountAfterFee = cartographer.withdrawalTokenManagement(_userAdd, pool.token, pool.feeBP, _amount, 0);
        }

        // Return amount withdrawn
        return amountAfterFee;
    }
}

File 6 of 21 : CartographerElevation.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./Cartographer.sol";
import "./ElevationHelper.sol";
import "./ISubCart.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./libs/IBEP20.sol";
import "./libs/SafeBEP20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";


/*
---------------------------------------------------------------------------------------------
--   S U M M I T . D E F I
---------------------------------------------------------------------------------------------


Summit is highly experimental.
It has been crafted to bring a new flavor to the defi world.
We hope you enjoy the Summit.defi experience.
If you find any bugs in these contracts, please claim the bounty (see docs)


Created with love by Architect and the Summit team





---------------------------------------------------------------------------------------------
--   Y I E L D   G A M B L I N G   E X P L A N A T I O N
---------------------------------------------------------------------------------------------

Funds are staked in elevation farms, and the resulting yield is risked to earn a higher yield multiplier
The staked funds are safe from risk, and cannot ever be lost

STAKING:
    . 3 tiers exist: 2K - plains / 5K - mesa / 10K - summit
    . Each tier has a set of TOTEMs
    . Users select a totem to represent them at the 'multiplying table', shared by all pools at that elevation
    . Funds are staked / withdrawn in the same way as traditional pools / farms, represented by their selected totem
    . Over time, a user's BET builds up as traditional staking does
    . Instead of the staking yield being immediately available, it is risked against the yields of other stakers
    . BETs build over the duration of a ROUND
    . The summed BETs of all users is considered the POT for that round

ROUNDS:
    . Each tier has a different round duration: 2 hours - plains / 4 hours - mesa / 10 hours - summit
    . At the end of each round, the round is ROLLED OVER
    . The ROLLOVER selects a TOTEM as the winner for that round
    . All users represented by that TOTEM are considered winners of that round
    . The winning TOTEM wins the entire pot
    . Winning users split the whole pot, effectively earning the staking rewards of the other users
    . Winnings vest over the duration of the next round
    

    


---------------------------------------------------------------------------------------------
--   Y I E L D   G A M B L I N G   C A L C U L A T I O N S   O V E R V I E W
---------------------------------------------------------------------------------------------



POOL:
    . At the end of each round, during the 'rollover' process, the following is saved in `poolRoundInfo` to be used in user's winnings calculations:
        - endAccSummitPerShare - the accSummitPerShare when the round ended
        - winningsMultiplier - how much each user's yield reward is multiplied by: (pool roundRewards) / (pool winning totem roundRewards)
        - precomputedFullRoundMult - (the change in accSummitPerShare over the whole round) * (winningsMultiplier)


USER:
    . The user's funds can be left in a pool over multiple rounds without any interaction
    . On the fly calculation of all previous rounds winnings (if any) must be fast and efficient
    

    . Any user interaction with a pool updates the following in UserInfo:
        - user.prevInteractedRound - Marks that the current round is the user last interaction with this pool
        - user.staked - Amount staked in the pool
        - user.roundDebt - The current accSummitPerShare, used to calculate the rewards earned by the user from the current mid-round point, to the end of the round
        - user.roundRew - The user may interact with the same round multiple times without losing any existing farmed rewards, this stores any accumulated rewards that have built up mid round, and is increased with each subsequent round interaction in the same round
    

USER VESTING:
    . Following a win, the users winnings vest over the duration of the next round, and are 100% vested by the end of that round
    . Fully vested winnings are permanently available at the end of the following round, even if the user wins more rounds subsequently, it is only the latest rounds winnings that require vesting
    . The users vesting amounts are calculated in two ways, depending on whether the user has interacted with the current round
        . <innate vesting> If the user has not interacted during the vesting period, the vested amount is:
            - (Winnings from the previous round) * (percentage through current round)

        . <re-vested> If the user has already interacted with a round that has winnings vesting, the vested portion of the winnings has been harvested, and the remaining un-vested portion reVested into userInfo
            - (user.reVestedAmount) * (timestamp - user.reVestStart) / (user.reVestDuration)

    . Users can have both reVested winnings from many rounds ago, as well as innate vesting from the previous round's winnings
        . When the user interacts with the current round, the reVested winnings (long since fully vested) will be withdrawn, and the innate vesting winnings will be reVested

*/

contract CartographerElevation is ISubCart, Ownable, Initializable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeBEP20 for IBEP20;



    // ---------------------------------------
    // --   V A R I A B L E S
    // ---------------------------------------

    Cartographer cartographer;
    ElevationHelper elevationHelper;
   
    struct UserInfo {
        // Yield Multiplying
        uint256 prevInteractedRound;                // Round the user last made a deposit / withdrawal / harvest
        uint256 staked;                             // The amount of token the user has in the pool
        uint256 roundDebt;                          // Used to calculate user's first interacted round reward
        uint256 roundRew;                           // Running sum of user's rewards earned in current round

        uint256 winningsDebt;                       // AccWinnings of user's totem at time of deposit

        // ReVesting                                // When a user interacts with a round where some funds are already vesting, the remaining amount to vest is re-vested here
        uint256 reVestAmt;                          // The amount of SUMMIT reward to vest over a duration
        uint256 reVestStart;                        // The start of the current re-vested period, updated on interaction
        uint256 reVestDur;                          // How long the current re-vested stint lasts
    }
    
    struct RoundInfo {                              
        uint256 endAccSummitPerShare;               // The accSummitPerShare at the end of the round, used for back calculations
        uint256 winningsMultiplier;                 // Rewards multiplier: TOTAL POOL STAKED / WINNING TOTEM STAKED
        uint256 precomputedFullRoundMult;           // Gas optimization for back calculation: accSummitPerShare over round multiplied by winnings multiplier
    }

    struct ElevationPoolInfo {
        uint16 pid;                                 // Pool identifier
        bool launched;                              // If the start round of the pool has passed and it is open for staking / rewards
        bool live;                                  // If the pool is running, in lieu of allocPoint
        bool active;                                // Whether the pool is active, used to keep pool alive until round rollover
        IBEP20 token;                               // Address of reward token contract
        uint256 supply;                             // Running total of the token amount staked in this pool at elevation
        uint256 lastRewardTimestamp;                // Last timestamp that SUMMIT distribution occurs.
        uint256 accSummitPerShare;                  // Accumulated SUMMIT per share, raised 1e12. See below.
        uint16 feeBP;                               // Fee of pool, 1% taken on withdrawal, remainder on deposit
        uint8 elevation;                            // The elevation of this pool

        uint256[] totemSupplies;                    // Running total of LP in each totem to calculate rewards
        uint256 roundRewards;                       // Rewards of entire pool accum over round
        uint256[] totemRoundRewards;                // Rewards of each totem accum over round

        uint256[] totemRunningPrecomputedMult;      // Running winnings per share for each totem
    }

    mapping(uint8 => mapping(uint256 => uint256)) public elevRoundWinningsMult; // The historical winning multipliers of an elevation

    uint16[] public elevationPIDs;                                              // List of all elevation pools for indexing
    mapping(uint16 => bool) public pidExistence;                                // If a specific pool exists as an elevation pool
    mapping(uint16 => ElevationPoolInfo) public elevationPoolInfo;              // Pool info for each elevation pool
    mapping(uint16 => mapping(uint256 => RoundInfo)) public poolRoundInfo;      // The round end information for each round of each pool
    mapping(uint8 => uint16[]) public poolsAtElevation;                         // Indexer for iterating all pools at an elevation
    mapping(IBEP20 => mapping(uint8 => bool)) public poolExistence;             // Whether a pool exists for a token + elevation
    mapping(uint16 => mapping(address => UserInfo)) public userInfo;            // Users running staking / vesting information
    mapping(address => mapping(uint8 => uint8)) public userTotem;               // User's totem, shared among all pools at a given elevation

    mapping(address => mapping(uint8 => uint256)) public userElevInteractingCount;   // User's interacting pools amount across each elevation to track cap, switch totem, and harvest all rewards at an elevation
    mapping(address => mapping(uint8 => uint16[12])) public userElevInteractingPools;// The pools at each elevation the user is actively interacting with

    // A pool becomes active as soon as it becomes live (whether it is launched or not)
    // However when a pool is turned off, it will only be marked as inactive at the end of the current round so that it is still included in end of round rollover
    mapping(uint8 => uint256) public elevActivePoolsCount;                      // Number Pools active at an elevation (only concerned about pool.live, not launched)
    mapping(uint8 => uint16[24]) public elevActivePools;                        // Pools active at an elevation (only concerned about pool.live, not launched)

    





    // ---------------------------------------
    // --  A D M I N I S T R A T I O N
    // ---------------------------------------


    /// @dev Constructor, setting address of cartographer
    constructor(address _Cartographer)
        public
    {
        require(_Cartographer != address(0), "Cartographer required");
        cartographer = Cartographer(_Cartographer);
    }


    /// @dev Set address of ElevationHelper during initialization
    function initialize(address _ElevationHelper, address, address)
        external override
        initializer onlyCartographer
    {
        require(_ElevationHelper != address(0), "Contract is zero");
        elevationHelper = ElevationHelper(_ElevationHelper);
    }

    /// @dev Unused enable summit stub
    function enable(uint256) external override {}
    





    // ------------------------------------------------------
    // --   M O D I F I E R S 
    // ------------------------------------------------------

    function _onlyCartographer() internal view {
        require(msg.sender == address(cartographer), "Only cartographer");
    }
    modifier onlyCartographer() {
        _onlyCartographer();
        _;
    }
    function _validUserAdd(address _userAdd) internal pure {
        require(_userAdd != address(0), "User address is zero");
    }
    modifier validUserAdd(address _userAdd) {
        _validUserAdd(_userAdd);
        _;
    }
    modifier nonDuplicated(IBEP20 _token, uint8 _elevation) {
        require(poolExistence[_token][_elevation] == false, "Duplicated");
        _;
    }
    modifier validTotem(uint8 _elevation, uint8 _totem) {
        require(_totem < elevationHelper.totemCount(_elevation), "Invalid totem");
        _;
    }
    function _elevationInteractionsAvailable(uint8 _elevation) internal view {
        require(!elevationHelper.endOfRoundLockoutActive(_elevation), "Elev locked until rollover");
    }
    modifier elevationInteractionsAvailable(uint8 _elevation) {
        _elevationInteractionsAvailable(_elevation);
        _;
    }
    function _poolExists(uint16 _pid) internal view {
        require(pidExistence[_pid], "Pool doesnt exist");
    }
    modifier poolExists(uint16 _pid) {
        _poolExists(_pid);
        _;
    }
    modifier poolExistsAndLaunched(uint16 _pid) {
        require(pidExistence[_pid], "Pool doesnt exist");
        require(elevationPoolInfo[_pid].launched, "Pool not launched yet");
        _;
    }
    




    // ---------------------------------------
    // --   U T I L S (inlined for brevity)
    // ---------------------------------------
    

    function supply(uint16 _pid) external view override returns (uint256) {
        return elevationPoolInfo[_pid].supply;
    }
    function token(uint16 _pid, bool) external view override returns (IBEP20) {
        return elevationPoolInfo[_pid].token;
    }
    function depositFee(uint16 _pid) external view override returns (uint256) {
        return elevationPoolInfo[_pid].feeBP;
    }
    function isEarning(uint16 _pid) external view override returns (bool) {
        return elevationPoolInfo[_pid].live && elevationPoolInfo[_pid].launched;
    }
    function selectedTotem(uint8 _elevation, address _userAdd) external view override returns (uint8) {
        return userTotem[_userAdd][_elevation];
    }
    
    function totemSupplies(uint16 _pid) public view poolExists(_pid) returns (uint256[10] memory) {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        return [
            pool.elevation >= 1 ? pool.totemSupplies[0] : 0,
            pool.elevation >= 1 ? pool.totemSupplies[1] : 0,
            pool.elevation >= 2 ? pool.totemSupplies[2] : 0,
            pool.elevation >= 2 ? pool.totemSupplies[3] : 0,
            pool.elevation >= 2 ? pool.totemSupplies[4] : 0,
            pool.elevation >= 3 ? pool.totemSupplies[5] : 0,
            pool.elevation >= 3 ? pool.totemSupplies[6] : 0,
            pool.elevation >= 3 ? pool.totemSupplies[7] : 0,
            pool.elevation >= 3 ? pool.totemSupplies[8] : 0,
            pool.elevation >= 3 ? pool.totemSupplies[9] : 0
        ];
    }


    /// @dev Calculate the emission to bring the selected pool current
    function emissionToBringPoolCurrent(ElevationPoolInfo memory pool) internal view returns (uint256) {
        // Early escape if pool is already up to date or not live
        if (block.timestamp == pool.lastRewardTimestamp || pool.supply == 0 || !pool.live || !pool.launched) return 0;
        
        // Get the (soon to be) awarded summit emission for this pool over the timespan that would bring current
        return cartographer.poolSummitEmission(pool.lastRewardTimestamp, pool.token, pool.elevation);
    }


    /// @dev Calculates up to date pool round rewards and totem round rewards with pool's emission
    /// @param _pid Pool identifier
    /// @return Up to date versions of round rewards.
    ///         [poolRoundRewards, ...totemRoundRewards 1 - 10]
    function totemRoundRewards(uint16 _pid)
        public view
        poolExists(_pid)
        returns (uint256[11] memory)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        uint8 totemCount = elevationHelper.totemCount(pool.elevation);

        // Gets emission that would bring the pool current from last reward timestamp
        uint256 emissionToBringCurrent = emissionToBringPoolCurrent(pool);

        // Create return array
        uint256[11] memory finalTotemRewards;

        // Add total emission to bring current to pool round rewards
        finalTotemRewards[0] = pool.roundRewards.add(emissionToBringCurrent);

        // For each totem, increase round rewards proportionally to amount staked in that totem compared to full pool's amount staked
        for (uint8 i = 0; i < 10; i++) {

            // If totem out of range for elevation, return 0
            if (i >= totemCount)
                finalTotemRewards[i + 1] = 0;

            // If pool or totem doesn't have anything staked, the totem's round rewards won't change with the new emission
            else if (pool.supply == 0 || pool.totemSupplies[i] == 0)
                finalTotemRewards[i + 1] = pool.totemRoundRewards[i];

            // Increase the totem's round rewards with a proportional amount of the new emission
            else
                finalTotemRewards[i + 1] = pool.totemRoundRewards[i]
                    .add(emissionToBringCurrent.mul(pool.totemSupplies[i]).div(pool.supply));
        }

        // Return up to date round rewards
        return finalTotemRewards;
    }
    




    // ---------------------------------------
    // --   P O O L   M A N A G E M E N T
    // ---------------------------------------


    /// @dev Registers pool everywhere needed
    /// @param _pid Pool identifier
    /// @param _token Token to register with
    /// @param _elevation Elevation of new pool
    function registerPool(uint16 _pid, IBEP20 _token, uint8 _elevation)
        internal
    {
        poolExistence[_token][_elevation] = true;
        pidExistence[_pid] = true;
        poolsAtElevation[_elevation].push(_pid);
        elevationPIDs.push(_pid);
    }

    function markIsPoolActive(ElevationPoolInfo storage pool, bool _active) internal {
        if (pool.active == _active) return;

        require(!_active || (elevActivePoolsCount[pool.elevation] < 24), "Too many active pools");

        pool.active = _active;
        elevActivePoolsCount[pool.elevation] = elevActivePoolsCount[pool.elevation]
            .add(_active ? 1 : 0)
            .sub(_active ? 0 : 1);

        (uint16 targetPid, uint16 replacementPid) = _active ? (uint16(0), pool.pid) : (pool.pid, uint16(0));

        // Iterate through list of active pools and add pid to first available activePoolSlot
        for (uint8 activePoolSlot = 0; activePoolSlot < 24; activePoolSlot++) {
            if (elevActivePools[pool.elevation][activePoolSlot] == targetPid) {
                elevActivePools[pool.elevation][activePoolSlot] = replacementPid;
                return;
            }
        }
    }


    /// @dev Creates a new elevation yield multiplying pool
    /// @param _pid Pool identifier selected by cartographer
    /// @param _live Whether the pool is enabled initially
    /// @param _token Token yielded by pool
    /// @param _feeBP Deposit fee taken
    function add(uint16 _pid, uint8 _elevation, bool _live, IBEP20 _token, uint16 _feeBP)
        external override
        onlyCartographer nonDuplicated(_token, _elevation)
    {
        // Register pool in state variables
        registerPool(_pid, _token, _elevation);

        // Create the initial state of the elevation pool
        elevationPoolInfo[_pid] = ElevationPoolInfo({
            pid: _pid,
            launched: false,
            token: _token,
            supply: 0,
            live: _live,
            active: false, // Will be made active in the add active pool below if _live is true
            accSummitPerShare : 0,
            lastRewardTimestamp : block.timestamp,
            feeBP : _feeBP,
            elevation : _elevation,

            totemSupplies : new uint256[](elevationHelper.totemCount(_elevation)),
            roundRewards : 0,
            totemRoundRewards : new uint256[](elevationHelper.totemCount(_elevation)),

            totemRunningPrecomputedMult: new uint256[](elevationHelper.totemCount(_elevation))
        });

        if (_live) markIsPoolActive(elevationPoolInfo[_pid], true);
    }
    
    
    /// @dev Unused expedition functionality stub
    function addExpedition(uint16, bool, uint256, IBEP20, uint256, uint256) external override onlyCartographer {}


    /// @dev Update a given pools deposit or live status
    /// @param _pid Pool identifier
    /// @param _live If pool is available for staking
    /// @param _feeBP Deposit fee of pool
    function set(uint16 _pid, bool _live, uint16 _feeBP)
        external override
        onlyCartographer poolExists(_pid)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        updatePool(_pid);

        // If live status changes
        if (pool.live != _live) {
            // If pool is already launched when live status changes, update cartographer allocations
            if (pool.launched) cartographer.setIsTokenEarningAtElevation(pool.token, pool.elevation, _live);

            // If pool is becoming live and isn't already active, add to active pools list
            if (_live && !pool.active) markIsPoolActive(pool, true);
            // Else pool is becoming inactive, which will be reflected at the end of the round in pool rollover function
        }

        // Update internal pool states
        pool.live = _live;
        pool.feeBP = _feeBP;
    }


    /// @dev Update all pools to current timestamp before other pool management transactions
    function massUpdatePools()
        external override
        onlyCartographer
    {
        for (uint16 i = 0; i < elevationPIDs.length; i++) {
            updatePool(elevationPIDs[i]);
        }
    }


    /// @dev Bring reward variables of given pool current
    /// @param _pid Pool identifier to update
    function updatePool(uint16 _pid)
        public
        poolExists(_pid)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        // Early exit if the pool is already current
        if (pool.lastRewardTimestamp == block.timestamp) return;

        // Early exit if pool not launched, not live, or supply is 0
        // Timestamp still updated before exit to prevent over emission on return to live
        if (!pool.launched || pool.supply == 0 || !pool.live) {
            pool.lastRewardTimestamp = block.timestamp;
            return;
        }

        // Mint Summit according to time delta, pools token share and elevation, and tokens allocation share
        uint256 summitReward = cartographer.mintPoolSummit(pool.lastRewardTimestamp, pool.token, pool.elevation);

        // Update accSummitPerShare with amount of summit minted for pool
        pool.accSummitPerShare = pool.accSummitPerShare.add(summitReward.mul(1e12).div(pool.supply));
        
        // Update the overall pool summit rewards for the round (used in winnings multiplier at end of round)
        pool.roundRewards = pool.roundRewards.add(summitReward);

        // Update each totem's summit rewards for the round (used in winnings multiplier at end of round)
        for (uint8 i = 0; i < pool.totemRoundRewards.length; i++) {
            pool.totemRoundRewards[i] = pool.totemRoundRewards[i]
                .add(summitReward.mul(pool.totemSupplies[i]).div(pool.supply));
        }     

        // Update last reward timestamp   
        pool.lastRewardTimestamp = block.timestamp;
    }
    




    // ---------------------------------------
    // --   P O O L   R E W A R D S
    // ---------------------------------------


    /// @dev Fetch guaranteed yield rewards of the pool
    /// @param _pid Pool to fetch rewards from
    /// @param _userAdd User requesting rewards info
    /// @return (
    ///     harvestableRewards - Amount of Summit available to harvest
    ///     vestingWinnings - Winnings of the previous round that will become available by the end of the current round
    ///     vestingStart - When the current vesting period began
    ///     vestingDuration - Time remaining until all winnings are vested
    /// )
    function rewards(uint16 _pid, address _userAdd)
        external view override
        onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256, uint256, uint256, uint256)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Fetch vesting information
        (uint256 vestingWinnings, uint256 vestingDuration, uint256 currentVestingPeriodStart) = winningsToVest(pool, user, _userAdd);

        // Return rewards values
        return (harvestableWinnings(pool, user, _userAdd), vestingWinnings, currentVestingPeriodStart, vestingDuration);
    }



    /// @dev The hypothetical rewards, and the hypothetical winnings from a given pool
    /// @param _pid Pool to check
    /// @param _userAdd User to check
    /// @return (
    ///     hypotheticalYield - The yield from staking, which has been risked during the current round
    ///     hypotheticalWinnings - If the user were to win the round, what their winnings would be based on:
    ///         . user's staking yield
    ///         . staking yield of each totem over the round
    ///         . staking yield of the entire pool over the round
    /// )
    function hypotheticalRewards(uint16 _pid, address _userAdd) external view override poolExists(_pid) validUserAdd(_userAdd) returns (uint256, uint256) {
        ElevationPoolInfo memory pool = elevationPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Allows hypothetical rewards calls to succeed even if the user isn't staked - frontend optimization
        if (user.staked == 0) return (0, 0);

        uint8 totem = userTotem[_userAdd][pool.elevation];
        
        // Calculate current accSummitPerShare, and the emission to bring the pool current
        (uint256 accSummitPerShare, uint256 emissionToBringCurrent) = liveAccSummitPerShare(pool);

        // Get hypothetical yield (what the user would have earned if this was standard staking) with brought current accSummitPerShare
        uint256 yield = hypotheticalYield(pool, user, accSummitPerShare);

        // Calculate current roundRewards and totemRoundRewards with the emission to bring current
        // User totem round rewards are the round rewards of the user's selected totem
        (uint256 roundRewards, uint256 userTotemRoundRewards) = liveRoundRewards(_pid, totem, emissionToBringCurrent);

        // Escape early to prevent div by 0 if no yield exists
        if (yield == 0 || userTotemRoundRewards == 0) return (0, 0);

        // Return hypotheticalYield, hypotheticalWinnings
        return (yield, userTotemRoundRewards > 0 ? yield.mul(roundRewards).div(userTotemRoundRewards) : yield);
    }


    /// @dev Calculates the accSummitPerShare if the pool was brought current and awarded summit emissions
    /// @param pool Elevation pool
    /// @return (
    ///     liveAccSummitPerShare - What the current accSummitPerShare of the pool would be if brought current
    ///     summitEmission - The awarded emission to the pool that would bring it current
    /// )
    function liveAccSummitPerShare(ElevationPoolInfo memory pool)
        internal view
        returns (uint256, uint256)
    {
        // Calculate emission to bring the pool up to date
        uint256 emissionToBringCurrent = emissionToBringPoolCurrent(pool);

        // Calculate the new accSummitPerShare with the emission to bring current, and return both values
        return (pool.accSummitPerShare.add(emissionToBringCurrent.mul(1e12).div(pool.supply)), emissionToBringCurrent);
    }


    /// @dev The staking rewards that would be earned during the current round under standard staking conditions
    /// @param pool Pool info
    /// @param user User info
    /// @param accSummitPerShare Is brought current before call
    function hypotheticalYield(ElevationPoolInfo memory pool, UserInfo storage user, uint256 accSummitPerShare)
        internal view
        returns (uint256)
    {
        uint256 currRound = elevationHelper.roundNumber(pool.elevation);
        return user.prevInteractedRound == currRound ?

            // Change in accSummitPerShare from current timestamp to users previous interaction timestamp (factored into user.roundDebt)
            user.staked.mul(accSummitPerShare).div(1e12).sub(user.roundDebt).add(user.roundRew) :

            // Change in accSummitPerShare from current timestamp to beginning of round's timestamp (stored in previous round's endAccRewPerShare)
            user.staked.mul(accSummitPerShare.sub(poolRoundInfo[pool.pid][currRound - 1].endAccSummitPerShare)).div(1e12);
    }


    /// @dev Brings the pool's round rewards, and the user's selected totem's round rewards current
    ///      Round rewards are the total amount of yield generated by a pool over the duration of a round
    ///      totemRoundRewards is the total amount of yield generated by the funds staked in each totem of the pool
    /// @param _pid Pool identifier
    /// @param _totem User's selected totem to bring current
    /// @param _emissionToBringCurrent The emission that would be granted if the pool was brought current, used to increment the round rewards of the pool and each totem
    /// @return (
    ///     liveRoundRewards - The brought current round rewards of the pool
    ///     liveUserTotemRoundRewards - The brought current round rewards of the user's selected totem
    /// )
    function liveRoundRewards(uint16 _pid, uint8 _totem, uint256 _emissionToBringCurrent)
        internal view
        poolExists(_pid)
        returns (uint256, uint256)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        return (
            // Round rewards with the total emission to bring current added
            pool.roundRewards.add(_emissionToBringCurrent),

            // Calculate user's totem's round rewards
            pool.supply == 0 || pool.totemSupplies[_totem] == 0 ? 
                
                // Early exit with current round rewards of user's totem if pool or user's totem has 0 supply (would cause div/0 error)
                pool.totemRoundRewards[_totem] :

                // Add the proportion of total emission that would be granted to the user's selected totem to that totem's round rewards
                // Proportion of total emission earned by each totem is (totem's staked supply / pool's staked supply)
                pool.totemRoundRewards[_totem].add(
                    _emissionToBringCurrent.mul(1e12)
                    .mul(pool.totemSupplies[_totem]).div(pool.supply)
                    .div(1e12))
        );
    }





    // ------------------------------------------------------------------
    // --   R O L L O V E R   E L E V A T I O N   R O U N D
    // ------------------------------------------------------------------
    
    
    /// @dev Sums the total rewards and winning totem rewards from each pool and determines the elevations winnings multiplier, then rolling over all active pools
    /// @param _elevation Elevation to rollover
    function rollover(uint8 _elevation)
        external override
        onlyCartographer
    {
        uint256 currRound = elevationHelper.roundNumber(_elevation);
        uint8 winningTotem = elevationHelper.winningTotem(_elevation, currRound - 1);


        // Iterate through active pools of elevation, sum total rewards earned (all totems), and winning totems's rewards
        uint256 elevTotalRewards = 0;
        uint256 winningTotemRewards = 0;
        for (uint16 activePoolSlot = 0; activePoolSlot < 24; activePoolSlot++) {
            // Early exit if active pool slot is empty
            if (elevActivePools[_elevation][activePoolSlot] == 0) continue;

            // Bring pool current
            updatePool(elevActivePools[_elevation][activePoolSlot]);

            // Add round rewards of pool and winning totem to elevation round reward accumulators
            elevTotalRewards += elevationPoolInfo[elevActivePools[_elevation][activePoolSlot]].roundRewards;
            winningTotemRewards += elevationPoolInfo[elevActivePools[_elevation][activePoolSlot]].totemRoundRewards[winningTotem];
        }

        // Calculate the winnings multiplier of the round that just ended from the combined reward amounts
        uint256 elevWinningsMult = winningTotemRewards == 0 ? 0 : elevTotalRewards.mul(1e12).div(winningTotemRewards);
        elevRoundWinningsMult[_elevation][currRound - 1] = elevWinningsMult;

        // Update and rollover all active pools
        for (uint16 activePoolSlot = 0; activePoolSlot < 24; activePoolSlot++) {
            // Early exit if active pool slot is empty
            if (elevActivePools[_elevation][activePoolSlot] == 0) continue;

            // Rollover Pool
            rolloverPool(elevActivePools[_elevation][activePoolSlot], currRound - 1, elevWinningsMult);
        }
    }
    
    
    /// @dev Roll over a single pool and create a new poolRoundInfo entry
    /// @param _pid Pool to roll over
    /// @param _prevRound Round index of the round that just ended
    /// @param _winningsMultiplier Winnings mult of the winning totem based on rewards of entire elevation
    function rolloverPool(uint16 _pid, uint256 _prevRound, uint256 _winningsMultiplier)
        internal
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        // Remove pool from active pool list if it has been marked for removal
        if (!pool.live && pool.active) markIsPoolActive(pool, false);

        // Launch pool if it hasn't been, early exit since it has no earned rewards before launch
        if (!pool.launched) {
            pool.launched = true;
            if (pool.live) cartographer.setIsTokenEarningAtElevation(pool.token, pool.elevation, true);
            return;
        }

        // The change in accSummitPerShare from the end of the previous round to the end of the current round
        uint256 deltaAccSummitPerShare = pool.accSummitPerShare.sub(poolRoundInfo[_pid][_prevRound - 1].endAccSummitPerShare);

        // Adding a new entry to the pool's poolRoundInfo for the most recently closed round
        poolRoundInfo[_pid][_prevRound] = RoundInfo({
            endAccSummitPerShare: pool.accSummitPerShare,
            winningsMultiplier: _winningsMultiplier,
            precomputedFullRoundMult: deltaAccSummitPerShare.mul(_winningsMultiplier).div(1e12)
        });

        // Round before recently closed round has finished vesting, add the full round multiplier of the vested round to the winning totem's accumulator
        if (_prevRound > 0)
            pool.totemRunningPrecomputedMult[elevationHelper.winningTotem(pool.elevation, _prevRound - 1)] += poolRoundInfo[_pid][_prevRound - 1].precomputedFullRoundMult;

        // Resetting round reward accumulators to begin accumulating over the next round
        pool.roundRewards = 0;
        pool.totemRoundRewards = new uint256[](elevationHelper.totemCount(pool.elevation));
    }
    




    // -------------------------------------------
    // --   V E S T I N G   G E T T E R S
    // -------------------------------------------
    
    
    /// @dev Getter function to return any vesting coming from UserInfo
    ///      If a user has some amount vesting (won previous round), and then harvests the vested rewards
    ///      The amount remaining to vest (yet un-harvested) is RE-VESTED, and stored in UserInfo, where this pulls from
    /// @param user UserInfo containing the re-vested info
    /// @return This function returns ONLY the re-vested winnings from UserInfo
    function reVestedWinnings(UserInfo storage user)
        internal view
        returns (uint256)
    {
        // Early escape if the user doesn't have any reVested winnings
        if (user.reVestAmt == 0) return 0;

        // Return the full reVested amount if the vesting period has finished
        if (block.timestamp >= user.reVestStart.add(user.reVestDur)) return user.reVestAmt;

        // Return a fraction of the reVested amount if the vesting period is still ongoing
        return user.reVestAmt.mul(block.timestamp.sub(user.reVestStart)).div(user.reVestDur);
    }


    /// @dev Convenience function to determine how much of the winnings are harvestable
    ///      Innate vesting comes from winnings that haven't yet been interacted with (would be reVested)
    /// @param _amount Winnings to determine vested of
    /// @param _elevation Elevation to check round duration and progress of
    /// @return Portion of winnings that are harvestable, will increase over duration of current round
    function innateVestedRoundWinnings(uint256 _amount, uint8 _elevation)
        internal view
        returns (uint256)
    {
        return _amount.mul(elevationHelper.fractionRoundComplete(_elevation)).div(1e12);
    }
    


    

    // ------------------------------------------------------------
    // --   W I N N I N G S   C A L C U L A T I O N S 
    // ------------------------------------------------------------


    /// @dev Totem precomputed multiplier for a pool round
    /// @param pool Pool info
    /// @param _totem Totem to determine winnings change
    /// @param _roundIndex Round to determine delta for
    function totemPrecomputedMultForRound(ElevationPoolInfo storage pool, uint8 _totem, uint256 _roundIndex)
        internal view
        returns (uint256)
    {
        // Early escape if round lost
        if (_totem != elevationHelper.winningTotem(pool.elevation, _roundIndex)) return 0;

        // Round won, so poolRoundInfo delta is for requested totem
        return poolRoundInfo[pool.pid][_roundIndex].precomputedFullRoundMult;
    }
    
    
    /// @dev Calculation of round rewards of the first round interacted
    /// @param pool Pool info
    /// @param user Users staking info
    /// @param round Passed in instead of used inline in this function to prevent stack too deep error
    /// @param _totem Totem to determine if round was won and winnings warranted
    /// @return Winnings from round
    function userFirstInteractedRoundWinnings(ElevationPoolInfo storage pool, UserInfo storage user, RoundInfo memory round, uint8 _totem)
        internal view
        returns (uint256)
    {
        if (_totem != elevationHelper.winningTotem(pool.elevation, user.prevInteractedRound)) return 0;

        return user.staked.mul(round.endAccSummitPerShare).div(1e12)
            .sub(user.roundDebt)
            .add(user.roundRew)
            .mul(round.winningsMultiplier).div(1e12);
    }

    
    /// @dev Generalized rewards from a round, including full amount vesting, escapes if round not won
    /// @param pool Pool info
    /// @param user User info
    /// @param _roundIndex Round to calculate winnings for
    /// @param _userAdd Address of user for validation
    /// @return Generalized winnings from round
    function rawRoundWinnings(ElevationPoolInfo storage pool, UserInfo storage user, uint256 _roundIndex, address _userAdd)
        internal view
        returns (uint256)
    {
        // Differentiate calculation of winnings if roundIndex is user's previous interacted round
        uint8 totem = userTotem[_userAdd][pool.elevation];

        return user.prevInteractedRound == _roundIndex ?
            userFirstInteractedRoundWinnings(pool, user, poolRoundInfo[pool.pid][_roundIndex], totem) :
            user.staked.mul(totemPrecomputedMultForRound(pool, totem, _roundIndex)).div(1e12);
    } 


    /// @dev Calculation of winnings that are available to be harvested
    /// @param pool Pool info
    /// @param user UserInfo
    /// @param _userAdd User's address passed through for win check
    /// @return Total harvestable winnings for a user, including vesting on previous round's winnings (if any)
    function harvestableWinnings(ElevationPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal view
        returns (uint256)
    {
        uint256 currRound = elevationHelper.roundNumber(pool.elevation);

        // Exit early if no previous round exists to have winnings
        if (!pool.launched) return 0;

        // Seed harvestable with the harvestable re-vested winnings from a previous interaction
        uint256 harvestable = reVestedWinnings(user);

        // If user interacted in current round, any harvestable winnings will come only from reVestedWinnings from previous round
        if (user.prevInteractedRound == currRound) return harvestable;
        
        // Exit early with vesting if user interacted in previous round
        uint8 totem = userTotem[_userAdd][pool.elevation];
        if (user.prevInteractedRound == currRound - 1) return harvestable
            .add(innateVestedRoundWinnings(
                userFirstInteractedRoundWinnings(pool, user, poolRoundInfo[pool.pid][user.prevInteractedRound], totem),
                pool.elevation)
            );

        // Get winnings from first user interacted round if it was won
        harvestable += userFirstInteractedRoundWinnings(pool, user, poolRoundInfo[pool.pid][user.prevInteractedRound], totem);

        // Add Vesting from most recent completed round if it was won
        harvestable += innateVestedRoundWinnings(
            user.staked.mul(totemPrecomputedMultForRound(pool, totem, currRound - 1)).div(1e12),
            pool.elevation
        );

        // Calculate true winnings debt, which is precomputed mult debt at time of interaction + first round's precomputed mult (which is bypassed by first round calculation above)        
        uint256 trueDebt = user.winningsDebt + totemPrecomputedMultForRound(pool, totem, user.prevInteractedRound);

        // Add multiple rounds of precomputed mult delta for all rounds between first interacted and most recent round
        harvestable += user.staked.mul(pool.totemRunningPrecomputedMult[totem].sub(trueDebt)).div(1e12);

        return harvestable;
    }
    
    
    /// @dev Innately vesting winnings that need to be reVested on user interaction
    /// @param pool Pool info
    /// @param user User info
    /// @param _userAdd User's address passed through for win check
    /// @return (
    ///     reVestAmount - Currently vesting funds that have not completed vesting yet
    ///     reVestDuration - Duration of the new vesting period for the reVestedAmount
    ///     reVestStart - The start timestamp of the new vesting period
    /// )
    function winningsToVest(ElevationPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal view
        returns (uint256, uint256, uint256)
    {
        uint256 currRound = elevationHelper.roundNumber(pool.elevation);

        // Escape early if the pool hasn't completed the first round with winnings
        if (!pool.launched) return (0, 0, 0);

        // RE-VEST
        // User has already interacted this round, so ReVest the already reVested winnings that have yet to become harvestable
        if (user.prevInteractedRound == currRound) {
            // Vesting info comes from UserInfo
            uint256 harvestable = reVestedWinnings(user);

            // ReVest original reVested amount reduced by harvestable, over time remaining in current round
            // Vesting start remains the same at the beginning of current round timestamp
            return (user.reVestAmt.sub(harvestable), elevationHelper.timeRemainingInRound(pool.elevation), user.reVestStart);
        }

        // Amount to reVest comes from the current innate vesting of the previous round's winnings
        // currRound is used for `_mostRecentCompletedRoundIndex` to retrieve the full round's winnings, not just harvestable winnings
        uint256 roundWinnings = rawRoundWinnings(pool, user, currRound - 1, _userAdd);

        // Escape early if previous round was lost
        if (roundWinnings == 0) return (0, 0, 0);

        // INNATE VEST
        // Return amount of winnings remaining to be vested, the time remaining in the current round as duration, and the start of the current round as the start period of the new vesting
        return (
            roundWinnings.mul(elevationHelper.fractionRoundRemaining(pool.elevation)).div(1e12),
            elevationHelper.timeRemainingInRound(pool.elevation),
            elevationHelper.currentRoundStartTime(pool.elevation)
        );
    }
    


    

    // ------------------------------------------------------------
    // --   W I N N I N G S   I N T E R A C T I O N S
    // ------------------------------------------------------------
    
    
    /// @dev Harvest any available winnings, and 
    /// @param pool Pool info
    /// @param user User info
    /// @param _userAdd USer's address used for redeeming rewards and checking for if rounds won
    function harvest(ElevationPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal
    {
        // Get user's winnings available for harvest
        uint256 harvestable = harvestableWinnings(pool, user, _userAdd);

        // Harvest winnings if any available
        if (harvestable > 0) {
            cartographer.redeemRewards(_userAdd, harvestable);
        }
    }

    /// @dev Vest or ReVest any winnings currently vesting but not yet vested
    /// @param pool Pool info
    /// @param user User info
    /// @param _userAdd User's address to vest
    function vest(ElevationPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal
    {
        // Calculate the amount to reVest, and the duration to reVest over, the reVestStart is thrown away as the vesting begins from the current timestamp
        (uint256 vestAmount, uint256 reVestDur,) = winningsToVest(pool, user, _userAdd);

        // Update user info with new reVesting info
        user.reVestAmt = vestAmount;
        user.reVestDur = reVestDur;
        user.reVestStart = block.timestamp;
    }


    /// @dev Totem AccWinningsPerShare at end of current round (adds vesting)
    /// @param pool Pool info
    /// @param _totem Totem to get winnings per share for
    function totemRunningPrecomputedMultWithVesting(ElevationPoolInfo storage pool, uint8 _totem)
        internal view
        returns (uint256)
    {
        // Early escape if current round is first round
        if (elevationHelper.roundNumber(pool.elevation) == 0) return pool.totemRunningPrecomputedMult[_totem];

        // Return base + prev round total delta amount
        return pool.totemRunningPrecomputedMult[_totem]
            .add(totemPrecomputedMultForRound(pool, _totem, elevationHelper.roundNumber(pool.elevation) - 1));
    }


    /// @dev Update the users round interaction
    /// @param pool Pool info
    /// @param user User info
    /// @param _totem Users selected totem
    /// @param _amount Amount depositing / withdrawing
    /// @param _isDeposit Flag to differentiate deposit / withdraw
    function updateUserRoundInteraction(ElevationPoolInfo storage pool, UserInfo storage user, uint8 _totem, uint256 _amount, bool _isDeposit)
        internal
    {
        uint256 currRound = elevationHelper.roundNumber(pool.elevation);

        // User already interacted this round, update the current round reward by adding staking rewards between two interactions this round
        if (user.prevInteractedRound == currRound) {
            user.roundRew = user.roundRew.add(user.staked.mul(pool.accSummitPerShare).div(1e12).sub(user.roundDebt));

        // User has no staked value, create a fresh round reward
        } else if (user.staked == 0) {
            user.roundRew = 0;

        // User interacted in some previous round, create a fresh round reward based on the current staked amount's staking rewards from the beginning of this round to the current point
        } else {
            // The accSummitPerShare at the beginning of this round. This is known to exist because a user has already interacted in a previous round
            uint256 roundStartAccSummitPerShare = poolRoundInfo[pool.pid][currRound - 1].endAccSummitPerShare;

            // Round rew is the current staked amount * delta accSummitPerShare from the beginning of the round until now
            user.roundRew = user.staked.mul(pool.accSummitPerShare.sub(roundStartAccSummitPerShare)).div(1e12);
        }
        
        // Update the user's staked amount with either the deposit or withdraw amount
        if (_isDeposit) user.staked = user.staked.add(_amount);
        else user.staked = user.staked.sub(_amount);
        
        // Fresh calculation of round debt from the new staked amount
        user.roundDebt = user.staked.mul(pool.accSummitPerShare).div(1e12);

        // Acc Winnings Per Share of the user's totem
        user.winningsDebt = totemRunningPrecomputedMultWithVesting(pool, _totem);

        // Update the user's previous interacted round to be this round
        user.prevInteractedRound = currRound;
    }

    


    

    // ------------------------------------------------------------
    // --   E L E V A T I O N   T O T E M S
    // ------------------------------------------------------------


    /// @dev Increments or decrements user's pools at elevation staked, and adds to  / removes from users list of staked pools
    function markIsUserInteractingWithPool(ElevationPoolInfo storage pool, bool _interacting, address _userAdd) internal {
        require(!_interacting || userElevInteractingCount[_userAdd][pool.elevation] < 12, "Staked pool cap (12) reached");

        userElevInteractingCount[_userAdd][pool.elevation] = userElevInteractingCount[_userAdd][pool.elevation]
            .add(_interacting ? 1 : 0)
            .sub(_interacting ? 0 : 1);

        (uint16 targetPid, uint16 replacementPid) = _interacting ? (uint16(0), pool.pid) : (pool.pid, uint16(0));
        for (uint8 stakingSlot = 0; stakingSlot < 12; stakingSlot++) {
            if (userElevInteractingPools[_userAdd][pool.elevation][stakingSlot] == targetPid) {
                userElevInteractingPools[_userAdd][pool.elevation][stakingSlot] = replacementPid;
                return;
            }
        }
    }


    /// @dev Whether the user actively has any funds staked with their selected totem
    /// @param _elevation The elevation to check the totem of
    /// @param _userAdd User checking totem
    /// @return If user has any funds staked at this elevation
    function isTotemInUse(uint8 _elevation, address _userAdd) external view override returns (bool) {
        return userElevInteractingCount[_userAdd][_elevation] > 0;
    }
    
    
    /// @dev All funds at an elevation share a totem. This function allows switching staked funds from one totem to another
    /// @param _elevation Elevation to switch totem on
    /// @param _totem New target totem
    /// @param _userAdd User requesting switch
    function switchTotem(uint8 _elevation, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer validTotem(_elevation, _totem) validUserAdd(_userAdd) elevationInteractionsAvailable(_elevation)
    {
        uint8 prevTotem = userTotem[_userAdd][_elevation];

        // Early exit if totem is same as current
        require(prevTotem != _totem, "Totem must be different");

        for (uint8 stakingSlot = 0; stakingSlot < 12; stakingSlot++) {
        // Iterate through pools the user is interacting with and update totem
        // Iterate through pools the user is interacting with and update totem
            if (userElevInteractingPools[_userAdd][_elevation][stakingSlot] != 0) {
                // Switch funds to new totem in each pool
                switchTotemForPool(userElevInteractingPools[_userAdd][_elevation][stakingSlot], prevTotem, _totem, _userAdd);
            }
        }

        // Update user's totem in state
        userTotem[_userAdd][_elevation] = _totem;
    }


    /// @dev Switch users funds (if any staked) to the new totem
    /// @param _pid Pool identifier
    /// @param _prevTotem Totem the user is leaving
    /// @param _newTotem Totem the user is moving to
    /// @param _userAdd User doing the switch
    function switchTotemForPool(uint16 _pid, uint8 _prevTotem, uint8 _newTotem, address _userAdd)
        internal
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        // Harvest any available funds in this pool        
        harvest(pool, user, _userAdd);

        // Vest or reVest any winnings yet to become available
        vest(pool, user, _userAdd);

        // Update the users interaction in this pool
        updateUserRoundInteraction(pool, user, _newTotem, 0, true);

        // Transfer supply and round rewards from previous totem to new totem
        pool.totemSupplies[_prevTotem] = pool.totemSupplies[_prevTotem].sub(user.staked);
        pool.totemSupplies[_newTotem] = pool.totemSupplies[_newTotem].add(user.staked);
        pool.totemRoundRewards[_prevTotem] = pool.totemRoundRewards[_prevTotem].sub(user.roundRew);
        pool.totemRoundRewards[_newTotem] = pool.totemRoundRewards[_newTotem].add(user.roundRew);
    }
    


    

    // ------------------------------------------------------------
    // --   P O O L   I N T E R A C T I O N S
    // ------------------------------------------------------------


    /// @dev Validate cross compound pools
    function crossCompoundValidatePools(uint16 _harvestPid, uint16 _summitPid)
        internal view
        poolExists(_harvestPid) poolExists(_summitPid) elevationInteractionsAvailable(elevationPoolInfo[_summitPid].elevation) 
    { return; }


    /// @dev Harvest any available rewards, and return that amount to be deposited in SUMMIT pool at same elevation
    /// @param _harvestPid Pool to harvest and cross compound rewards from
    /// @param _summitPid Pool to cross compound from
    /// @param _totem Totem to deposit rewards into (already known but for verification)
    /// @param _userAdd User requesting cross compound
    /// @return Amount cross compounded
    function crossCompound(uint16 _harvestPid, uint16 _summitPid, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer validUserAdd(_userAdd)
        returns (uint256)
    {
        crossCompoundValidatePools(_harvestPid, _summitPid);

        // --   HARVEST AVAILABLE SUMMIT WINNINGS   --

        ElevationPoolInfo storage harvestPool = elevationPoolInfo[_harvestPid];

        // Exit early if different totem already has staked value
        require(userElevInteractingCount[_userAdd][harvestPool.elevation] == 0 || userTotem[_userAdd][harvestPool.elevation] == _totem, "Cant switch totem during deposit");

        ElevationPoolInfo storage summitPool = elevationPoolInfo[_summitPid];
        UserInfo storage harvestUser = userInfo[_harvestPid][_userAdd];
        UserInfo storage summitUser = userInfo[_harvestPid][_userAdd];
        
        updatePool(harvestPool.pid);

        // Calculate harvestable rewards
        // Doesn't call harvest() because the funds should stay in the cartographer instead of being transferred back to the user
        uint256 harvestable = harvestableWinnings(harvestPool, harvestUser, _userAdd);
        require(harvestable > 0, "Nothing to cross compound");

        // Vest or reVest any winnings not yet available
        vest(harvestPool, harvestUser, _userAdd);

        // Update users round interaction without depositing or withdrawing anything, bring up to date
        updateUserRoundInteraction(harvestPool, harvestUser, _totem, 0, false);



        // --   CROSS COMPOUND INTO SUMMIT POOL   --

        // Deposit harvestable winnings into SUMMIT pool at this elevation
        return unifiedDeposit(summitPool, summitUser, harvestable, _totem, _userAdd, true);
    }


    /// @dev Harvest an entire elevation (or cross compound)
    /// @param _elevation To harvest
    /// @param _crossCompoundPid If 0: No cross compound, else the validated cross compound target pool pid
    /// @param _userAdd User harvesting
    function harvestElevation(uint8 _elevation, uint16 _crossCompoundPid, address _userAdd)
        external override
        validUserAdd(_userAdd) elevationInteractionsAvailable(_elevation) onlyCartographer
        returns (uint256)
    {
        // Harvest rewards of users active pools
        uint8 totem = userTotem[_userAdd][_elevation];
        uint256 harvestable = 0;
        ElevationPoolInfo storage pool;

        // Iterate through pools the user is interacting, get harvestable amount, update pool
        for (uint8 stakingSlot = 0; stakingSlot < 12; stakingSlot++) {
            // Exit early if pool doesn't exist (pid == 0)
            if (userElevInteractingPools[_userAdd][_elevation][stakingSlot] == 0) continue;

            pool = elevationPoolInfo[userElevInteractingPools[_userAdd][_elevation][stakingSlot]];

            // Harvest winnings
            updatePool(pool.pid);
            harvestable += harvestableWinnings(pool, userInfo[pool.pid][_userAdd], _userAdd);
            vest(pool, userInfo[pool.pid][_userAdd], _userAdd);
            updateUserRoundInteraction(pool, userInfo[pool.pid][_userAdd], totem, 0, true);
        }

        // Cross compound, else harvest to user
        if (harvestable > 0) {
            if (_crossCompoundPid != 0){
                unifiedDeposit(elevationPoolInfo[_crossCompoundPid], userInfo[_crossCompoundPid][_userAdd], harvestable, totem, _userAdd, true);
            } else {
                cartographer.redeemRewards(_userAdd, harvestable);
            }
        }
        
        return harvestable;
    }

    
    /// @dev Wrapper around cartographer token management on deposit
    function depositTokenManagement(uint16 _pid, uint256 _amount, address _userAdd) internal returns (uint256) {
        return cartographer.depositTokenManagement(_userAdd, elevationPoolInfo[_pid].token, elevationPoolInfo[_pid].feeBP, _amount, 0);
    }

    function depositValidate(uint16 _pid, uint8 _totem, address _userAdd)
        internal view
        poolExistsAndLaunched(_pid) validTotem(elevationPoolInfo[_pid].elevation, _totem) validUserAdd(_userAdd) elevationInteractionsAvailable(elevationPoolInfo[_pid].elevation)
    { return; }
    
    /// @dev Stake funds in a yield multiplying elevation pool
    /// @param _pid Pool to stake in
    /// @param _amount Amount to stake
    /// @param _totem Totem to stake with
    /// @param _userAdd User wanting to stake
    /// @return Amount deposited after deposit fee taken
    function deposit(uint16 _pid, uint256 _amount, uint256, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer
        returns (uint256, uint256)
    {
        depositValidate(_pid, _totem, _userAdd);
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Exit early if different totem already has staked value
        require(userElevInteractingCount[_userAdd][pool.elevation] == 0 || userTotem[_userAdd][pool.elevation] == _totem, "Cant switch totem during deposit");

        // Pass info to unified deposit to handle remainder of deposit functionality
        return (unifiedDeposit(pool, user, _amount, _totem, _userAdd, false), 0);
    }


    /// @dev Elevate funds to a yield multiplying pool through the elevate pipeline
    /// @param _pid Pool to deposit funds in
    /// @param _amount Amount to elevate
    /// @param _totem Totem to stake funds with
    /// @param _userAdd User elevating funds
    /// @return Amount deposited after fee taken
    function elevateDeposit(uint16 _pid, uint256 _amount, address, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExistsAndLaunched(_pid) validUserAdd(_userAdd) elevationInteractionsAvailable(elevationPoolInfo[_pid].elevation)
        returns (uint256)
    {
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        // Pass info through to unified deposit to handle remainder of functionality
        return unifiedDeposit(pool, user, _amount, _totem, _userAdd, true);
    }


    /// @dev User interacting with pool getter
    /// User.staked checks if the user has any funds in the pool
    /// User.roundRew will only be > 0 after an interaction if the user has any rewards contributed to the pot during a round
    /// If any of the 3 are true, then the user still has interactions other than 'deposit' that can be meaningfully called on the pool
    /// User.reVestAmt will only be > 0 if the user has any remaining amount of winnings that can be withdrawn
    /// User.reVestAmt will only be > 0 if the user has any remaining amount of winnings that can be withdrawn
    /// This is used to keep `userElevInteractingPools` up to date for switchTotem and harvestElevation
    /// This is also used on the frontend (through public wrapper) to boost the sort order of pools the user is interacting with
    function _userInteractingWithPool(UserInfo storage user) internal view returns (bool) {
        return user.staked.add(user.roundRew).add(user.reVestAmt) > 0;
    }
    function userInteractingWithPool(uint16 _pid) public view poolExists(_pid) returns (bool) {
        return _userInteractingWithPool(userInfo[_pid][msg.sender]);
    }


    /// @dev Internal shared deposit functionality for elevate or standard deposit
    /// @param pool Pool info of pool to deposit into
    /// @param user UserInfo of depositing user
    /// @param _amount Amount to deposit
    /// @param _totem Totem to stake funds with
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality for elevate deposit
    /// @return Amount deposited after fee taken
    function unifiedDeposit(ElevationPoolInfo storage pool, UserInfo storage user, uint256 _amount, uint8 _totem, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256)
    {
        updatePool(pool.pid);

        // If the user is interacting with this pool at the beginning of the txn
        bool interactingWithPoolInit = _userInteractingWithPool(user);

        // Harvest any available rewards and vest any remaining for pool
        harvest(pool, user, _userAdd);

        vest(pool, user, _userAdd);

        uint256 amountAfterFee = _amount;

        // Take deposit fee and add to running supplies if amount is non zero
        if (_amount > 0) {

            // Only take deposit fee on standard deposit
            if (!_isInternalTransfer)
                amountAfterFee = depositTokenManagement(pool.pid, _amount, _userAdd);

            // Adding staked amount to running supply accumulators
            pool.totemSupplies[_totem] += amountAfterFee;
            pool.supply += amountAfterFee;
        }
        
        // Update / create users interaction with the pool
        updateUserRoundInteraction(pool, user, _totem, amountAfterFee, true);

        // Set the users totem at this elevation
        userTotem[_userAdd][pool.elevation] = _totem;

        // If the user is interacting with this pool after the meat of the transaction completes
        bool interactingWithPoolFinal = _userInteractingWithPool(user);

        // If the user's interacting status has changed, update it in the users active pools list
        if (interactingWithPoolInit != interactingWithPoolFinal)
            markIsUserInteractingWithPool(pool, interactingWithPoolFinal, _userAdd);

        // Return true amount deposited in pool
        return amountAfterFee;
    }


    /// @dev Withdraw staked funds from pool
    /// @param _pid Pool to withdraw from
    /// @param _amount Amount to withdraw
    /// @param _userAdd User withdrawing
    /// @return True amount withdrawn
    function withdraw(uint16 _pid, uint256 _amount, uint256, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd) elevationInteractionsAvailable(elevationPoolInfo[_pid].elevation)
        returns (uint256, uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        // Shared withdraw functionality
        return (unifiedWithdraw(pool, user, _amount, _userAdd, false), 0);
    }


    /// @dev Withdraw staked funds to elevate them to another elevation
    /// @param _pid Pool to elevate funds from
    /// @param _amount Amount of funds to elevate
    /// @param _userAdd User elevating
    /// @return Amount withdrawn to be elevated
    function elevateWithdraw(uint16 _pid, uint256 _amount, address, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd) elevationInteractionsAvailable(elevationPoolInfo[_pid].elevation)
        returns (uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];       
        ElevationPoolInfo storage pool = elevationPoolInfo[_pid];

        // Shared withdraw functionality
        return unifiedWithdraw(pool, user, _amount, _userAdd, true);
    }  


    /// @dev Withdraw functionality shared between standardWithdraw and elevateWithdraw
    /// @param pool Pool to withdraw from
    /// @param user UserInfo of withdrawing user
    /// @param _amount Amount to withdraw
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality for elevate withdraw
    /// @return Amount withdrawn
    function unifiedWithdraw(ElevationPoolInfo storage pool, UserInfo storage user, uint256 _amount, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256)
    {
        // Validate amount attempting to withdraw
        require(_amount > 0 && user.staked >= _amount, "Bad withdrawal");
        
        updatePool(pool.pid);

        // If the user is interacting with this pool at the beginning of the txn
        bool interactingWithPoolInit = _userInteractingWithPool(user);
        
        // Harvest any available winnings
        harvest(pool, user, _userAdd);

        // Vest or reVest any winnings not yet available
        vest(pool, user, _userAdd);

        // Update the users interaction in the pool
        updateUserRoundInteraction(pool, user, userTotem[_userAdd][pool.elevation], _amount, false);
        
        // Signal cartographer to perform withdrawal function if not elevating funds
        // Elevated funds remain in the cartographer, or in the passthrough target, so no need to withdraw from anywhere as they would be immediately re-deposited
        uint256 amountAfterFee = _amount;
        if (!_isInternalTransfer) {
            amountAfterFee = cartographer.withdrawalTokenManagement(_userAdd, pool.token, pool.feeBP, _amount, 0);
        }
        
        uint8 totem = userTotem[_userAdd][pool.elevation];

        // Remove withdrawn amount from pool's running supply accumulators
        pool.totemSupplies[totem] = pool.totemSupplies[totem].sub(_amount);
        pool.supply = pool.supply.sub(_amount);

        // If the user is interacting with this pool after the meat of the transaction completes
        bool interactingWithPoolFinal = _userInteractingWithPool(user);

        // If the user's interacting status has changed, update it in the users active pools list
        if (interactingWithPoolInit != interactingWithPoolFinal)
            markIsUserInteractingWithPool(pool, interactingWithPoolFinal, _userAdd);

        // Return amount withdraw
        return amountAfterFee;
    }  
}

File 7 of 21 : CartographerExpedition.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./Cartographer.sol";
import "./ElevationHelper.sol";
import "./SummitToken.sol";
import "./ISubCart.sol";
import "./libs/ILiquidityPair.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./libs/IBEP20.sol";
import "./libs/SafeBEP20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";


/*
---------------------------------------------------------------------------------------------
--   S U M M I T . D E F I
---------------------------------------------------------------------------------------------


Summit is highly experimental.
It has been crafted to bring a new flavor to the defi world.
We hope you enjoy the Summit.defi experience.
If you find any bugs in these contracts, please claim the bounty on immunefi.com


Created with love by Architect and the Summit team





---------------------------------------------------------------------------------------------
--   E X P E D I T I O N   E X P L A N A T I O N
---------------------------------------------------------------------------------------------


Expeditions offer a reward for holders of Summit.
Stake SUMMIT or SUMMIT LP (see MULTI-STAKING) in an expedition for a chance to win stablecoins and other high value tokens.
Expedition pots build during the week from passthrough staking rewards and deposit fees

Deposits open 24 hours before a round closes, at which point deposits are locked, and the winner chosen
After each round, the next round begins immediately (if the expedition hasn't ended)

Expeditions take place on the weekends, and each have 3 rounds (FRI / SAT / SUN)
Two DEITIES decide the fate of each round:


DEITIES (COSMIC BULL vs COSMIC BEAR):
    . Each round has a different chance of succeeding, between 50 - 90%
    . If the expedition succeeds, COSMIC BULL earns the pot, else COSMIC BEAR steals it
    
    . COSMIC BULL is a safer deity, always has a higher chance of winning
    . Users are more likely to stake with the safer deity so it's pot will be higher, thus the winnings per SUMMIT staked lower

    . COSMIC BEAR is riskier, with a smaller chance of winning, potentially as low as 10%
    . Users are less likely to steak with BULL as it may be outside their risk tolerance to shoot for a small % chance of win

    . Thus BEAR will usually have less staked, making it both riskier, and more rewarding on win

    . The SUMMIT team expect that because switching between DEITIES is both free and unlimited,
        users will collectively 'arbitrage' the two deities based on the chance of success.

    . For example, if a round's chance of success is 75%, we expect 75% of the staked funds in the pool to be with BULL (safer)
        though this is by no means guaranteed


MULTI-STAKING
    . Users can stake both their SUMMIT token and SUMMIT LP token in an expedition
    . This prevents users from needing to break and re-make their LP for every expedition
    . SUMMIT and SUMMIT LP can be staked simultaneously
    . Both SUMMIT and SUMMIT LP can be elevated into and out of the Expedition
    . The equivalent amount of SUMMIT within the staked SUMMIT LP is treated as the SUMMIT token, and can earn winnings
    . The equivalent amount of SUMMIT in staked SUMMIT LP is determined from the SUMMIT LP pair directly
    . We have also added summitInLpIncentiveMultiplier (between 1X - 2X) which can increase the equivalent amount of SUMMIT in SUMMIT LP (updated on a 72 hour timelock)
    . summitInLpIncentiveMultiplier will be updated to ensure users are willing to stake their SUMMIT LP rather than break it (this may never be necessary and will be actively monitored)

WINNINGS:
    . The round reward is split amongst all members of the winning DEITY, based on their percentage of the total amount staked in that deity
    . Calculations section omitted because it is simply division
    . Users may exit the pool at any time without fee
    . Users are not forced to collect their winnings between rounds, and are entered into the next round automatically (same deity) if they do not exit


*/



contract CartographerExpedition is ISubCart, Ownable, Initializable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeBEP20 for IBEP20;



    // ---------------------------------------
    // --   V A R I A B L E S
    // ---------------------------------------


    SummitToken public summit;
    ILiquidityPair public summitLp;
    Cartographer cartographer;
    ElevationHelper elevationHelper;
    uint8 constant EXPEDITION = 4;


    uint256 public summitInLpIncentiveMultiplier = 100;     // If SUMMIT LP in Expedition isn't incentivised enough for users to stake LP instead of breaking before depositing SUMMIT token itself, this can be updated
    
    struct UserInfo {
        uint256 prevInteractedRound;                        // Tracks users interaction across multiple rounds
        uint256 summitStaked;                               // The amount of token the user has in the pool
        uint256 lpStaked;                                   // The amount of lp staked in the pool
        uint256 summitWinningsDebt;                         // Winnings acc at time of interaction
        uint256 lpWinningsDebt;                             // LpWinnings at time of interaction
    }

    struct ExpeditionPoolInfo {
        uint16 pid;                                         // Pool identifier
        bool launched;                                      // If the start round of the pool has passed and it is open for betting
        bool live;                                          // If the pool is manually enabled / disabled
        bool active;                                        // Whether the pool should be rolled over at end of round
        IBEP20 rewardToken;                                 // Address of Reward token to be distributed.
        uint256 roundEmission;                              // The amount of Reward token for each round
        uint256 totalRoundsCount;                           // Number of rounds of this expedition to run.
        uint256 totalRewardAmount;                          // Total amount of reward token to be distributed over 7 days.
        uint256 rewardsMarkedForDist;                       // Rewards marked to be distributed but not yet withdrawn by users.

        uint256 startRound;                                 // The first round of the pool

        uint256 summitSupply;                               // Running total of SUMMIT staked in the expedition
        uint256 lpSupply;                                   // The amount of SUMMIT lp the user has staked
        uint256[] totemSummitSupply;                        // Running total of combined equivalent SUMMIT in each totem to calculate rewards
        uint256[] totemLpSupply;                            // Running total of combined equivalent SUMMIT in each totem to calculate rewards

        uint256[] totemRunningSummitWinningsMult;           // Running winnings per share for each totem, increased at round end with snapshot of SUMMIT LP incentive multiplier and SUMMIT token to SUMMIT LP ratio
        uint256[] totemRunningLpWinningsMult;               // Running winnings per lp share for each totem, increased at round end with snapshot of SUMMIT LP incentive multiplier and SUMMIT token to SUMMIT LP ratio
    }


    uint16[] public expeditionPIDs;                                         // List of all expeditions for indexing
    mapping(uint16 => bool) public pidExistence;                            // If a specific expedition exists
    mapping(IBEP20 => bool) public poolExistence;                           // If an expedition exists for a reward token
    mapping(uint16 => ExpeditionPoolInfo) public expeditionPoolInfo;        // Expedition info
    mapping(uint16 => mapping(address => UserInfo)) public userInfo;        // Users running staked information
    mapping(address => uint8) public userTotem;                             // Users expedition totem (sometimes referred to as deity for expedition)

    mapping(address => uint256) public userExpedInteractingCount;           // User's pools interacting with count, capped at 12, used for switching totems
    mapping(address => uint16[12]) public userInteractingExpeditions;       // The expeditions the user is staked in or has winnings for


    // An expedition becomes active as soon as it becomes live (whether it is launched or not)
    // However when an expedition is turned off, it will only be marked as inactive at the end of the current round so that it is still included in end of round rollover
    uint256 public activeExpedsCount;    // Number Expeds active at an elevation (only concerned about expedition.live, not launched)
    uint16[24] public activeExpeds;      // Expeds active at an elevation (only concerned about expedition.live, not launched)






    // ---------------------------------------
    // --   E V E N T S
    // ---------------------------------------

    event ExpeditionExtended(uint256 indexed pid, address rewardToken, uint256 _rewardAmount, uint256 _rounds);
    event ExpeditionRestarted(uint256 indexed pid, address rewardToken, uint256 _rewardAmount, uint256 _rounds);
    event SetSummitInLpIncentiveMultiplier(address indexed user, uint256 indexed newIncentiveMultiplier);
    





    // ---------------------------------------
    // --  A D M I N I S T R A T I O N
    // ---------------------------------------


    /// @dev Constructor, setting address of cartographer
    constructor(address _Cartographer)
        public
    {
        require(_Cartographer != address(0), "Cartographer required");
        cartographer = Cartographer(_Cartographer);
    }

    /// @dev Set address of ElevationHelper during initialization
    function initialize(address _ElevationHelper, address _summit, address _summitLp)
        external override
        initializer onlyCartographer
    {
        require(_ElevationHelper != address(0), "Contract is zero");
        elevationHelper = ElevationHelper(_ElevationHelper);
        summit = SummitToken(_summit);
        summitLp = ILiquidityPair(_summitLp);
    }

    /// @dev Stub for enabling summit ecosystem
    function enable(uint256) external override onlyCartographer {}


    /// @dev Updating the SUMMIT LP in expedition incentive multiplier
    ///      WILL BE GIVEN A 72 HOUR SPECIFIC DELAY IN TIMELOCK (OWNER OF CARTOGRAPHER)
    /// @param _newIncentive New incentivization multiplier
    function setSummitInLpIncentiveMultiplier(uint256 _newIncentive) public onlyOwner {
        require(_newIncentive >= 100 && _newIncentive <= 200, "Incentive multiplier must be between 1x and 2x");
        summitInLpIncentiveMultiplier = _newIncentive;
        emit SetSummitInLpIncentiveMultiplier(msg.sender, _newIncentive);
    }






    // ------------------------------------------------------
    // --   M O D I F I E R S 
    // ------------------------------------------------------

    function _onlyCartographer() internal view {
        require(msg.sender == address(cartographer), "Only cartographer");
    }
    modifier onlyCartographer() {
        _onlyCartographer();
        _;
    }
    function _validUserAdd(address _userAdd) internal pure {
        require(_userAdd != address(0), "User address is zero");
    }
    modifier validUserAdd(address _userAdd) {
        _validUserAdd(_userAdd);
        _;
    }
    modifier nonDuplicated(IBEP20 _rewardToken) {
        require(!poolExistence[_rewardToken], "Duplicated");
        _;
    }
    function _poolExists(uint16 _pid) internal view {
        require(pidExistence[_pid], "Pool doesnt exist");
    }
    modifier poolExists(uint16 _pid) {
        _poolExists(_pid);
        _;
    }
    modifier poolExistsAndLaunched(uint16 _pid) {
        require(pidExistence[_pid], "Pool doesnt exist");
        require(expeditionPoolInfo[_pid].launched, "Pool not launched yet");
        _;
    }
    modifier validTotem(uint8 totem) {
        require(totem < elevationHelper.totemCount(EXPEDITION), "Invalid totem");
        _;
    }
    modifier expeditionInteractionsAvailable() {
        require(!elevationHelper.endOfRoundLockoutActive(EXPEDITION), "Elev locked until rollover");
        _;
    }
    




    // ---------------------------------------
    // --   U T I L S (inlined for brevity)
    // ---------------------------------------
    

    function expeditionsCount() public view returns (uint256) {
        return expeditionPIDs.length;
    }
    function supply(uint16 _pid) external view override poolExists(_pid) returns (uint256) {
        return expeditionPoolInfo[_pid].summitSupply;
    }
    function token(uint16 _pid, bool _isExpeditionSummitLp) external view override poolExists(_pid) returns (IBEP20) {
        return _isExpeditionSummitLp ? IBEP20(summitLp) : summit;
    }
    function depositFee(uint16) external view override returns (uint256) {
        return uint256(0);
    }
    function isEarning(uint16 _pid) external view override returns (bool) {
        return expeditionPoolInfo[_pid].live;
    }
    function selectedTotem(uint8, address _userAdd) external view override returns (uint8) {
        return userTotem[_userAdd];
    }

    /// @dev Divider is random number 50 - 90 that sets the random chance of each of the deities winning the round
    function currentDeityDivider(uint16 _pid) public view poolExists(_pid) returns (uint8) {
        return elevationHelper.currentDeityDivider();
    }


    /// @dev The amount of reward token that exists to be rewarded by an expedition
    /// @param _pid Expedition identifier
    function remainingRewards(uint16 _pid)
        public view
        poolExists(_pid)
        returns (uint256)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Exit early if pool not launched yet
        if (!pool.launched) return pool.totalRewardAmount;

        // Total expedition runtime so far
        uint256 currRound = elevationHelper.roundNumber(EXPEDITION);
        uint256 roundsCompleted = currRound - pool.startRound;

        // Return total emissions allocated to users so far
        return pool.totalRewardAmount.sub(roundsCompleted.mul(pool.roundEmission));
    }

    function totemSupplies(uint16 _pid) public view poolExists(_pid) returns (uint256[2] memory) {        
        // Return the equivalent SUMMIT of each of the totems (SUMMIT token + SUMMIT LP equivalent)
        return [
            combinedEquivalentSUMMIT(
                expeditionPoolInfo[_pid].totemSummitSupply[0],
                expeditionPoolInfo[_pid].totemLpSupply[0]
            ),
            combinedEquivalentSUMMIT(
                expeditionPoolInfo[_pid].totemSummitSupply[1],
                expeditionPoolInfo[_pid].totemLpSupply[1]
            )
        ];
    }





    // ---------------------------------------
    // --   P O O L   M A N A G E M E N T
    // ---------------------------------------


    /// @dev Registers pool everywhere needed
    /// @param _pid Pool identifier
    /// @param _rewardToken Token to register with
    function registerPool(uint16 _pid, IBEP20 _rewardToken) internal {
        poolExistence[_rewardToken] = true;
        pidExistence[_pid] = true;
        expeditionPIDs.push(_pid);
    }

    function markIsExpeditionActive(ExpeditionPoolInfo storage pool, bool _active) internal {
        if (pool.active == _active) return;

        require(!_active || (activeExpedsCount < 24), "Too many active expeditions");

        pool.active = _active;
        activeExpedsCount = activeExpedsCount
            .add(_active ? 1 : 0)
            .sub(_active ? 0 : 1);

        (uint16 targetPid, uint16 replacementPid) = _active ? (uint16(0), pool.pid) : (pool.pid, uint16(0));

        // Iterate through list of active pools and add pid to first available activePoolSlot
        for (uint8 activePoolSlot = 0; activePoolSlot < 24; activePoolSlot++) {
            if (activeExpeds[activePoolSlot] == targetPid) {
                activeExpeds[activePoolSlot] = replacementPid;
                return;
            }
        }
    }

    /// @dev Stub for creating a new standard pool
    function add(uint16, uint8, bool, IBEP20, uint16) external override onlyCartographer {}


    /// @dev Creates a expedition
    /// @param _pid Pool identifier selected by cartographer
    /// @param _live Whether the pool is enabled initially
    /// @param _startRoundOffset Round to start the expedition during
    /// @param _rewardToken Token yielded by pool
    /// @param _rewardAmount Total reward token to be distributed over duration of pool, divided evenly between each round
    /// @param _rounds Number of rounds for this expedition to run
    function addExpedition(uint16 _pid, bool _live, uint256 _startRoundOffset, IBEP20 _rewardToken, uint256 _rewardAmount, uint256 _rounds)
        external override
        onlyCartographer nonDuplicated(_rewardToken)
    {
        // Ensure that this contract has enough reward token to cover the entire duration of the expedition
        require(_rewardToken.balanceOf(address(this)) >= _rewardAmount, "Must have funds to cover expedition");
        require(_startRoundOffset <= 7, "Must start within the week");

        registerPool(_pid, _rewardToken);

        uint8 totemCount = elevationHelper.totemCount(EXPEDITION);

        // Create the initial state of the expedition
        expeditionPoolInfo[_pid] = ExpeditionPoolInfo({
            pid: _pid,
            launched: false,
            live: _live,
            active: false, // Will be made active in add active expedition below if _live is true
            rewardToken: _rewardToken,
            totalRoundsCount: _rounds,
            totalRewardAmount: _rewardAmount,
            roundEmission: _rewardAmount.div(_rounds),
            rewardsMarkedForDist: 0,
            startRound: elevationHelper.nextRound(EXPEDITION).add(_startRoundOffset),

            summitSupply: 0,
            lpSupply: 0,
            totemSummitSupply: new uint256[](totemCount),
            totemLpSupply: new uint256[](totemCount),

            totemRunningSummitWinningsMult: new uint256[](totemCount),
            totemRunningLpWinningsMult: new uint256[](totemCount)
        });

        if (_live) markIsExpeditionActive(expeditionPoolInfo[_pid], true);
    }

    /// @dev Stub for updating standard pools
    function set(uint16, bool, uint16) external override onlyCartographer {
        require(false, "Set doesnt exist for expedition");
    }


    /// @dev Stub for updating pools
    function massUpdatePools() external override {}


    /// @dev Turn off an expedition if necessary
    function disableExpeditionPool(uint16 _pid)
        public
        onlyOwner poolExistsAndLaunched(_pid)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];
        require(pool.live, "Pool already disabled");
        pool.live = false;
        // Removing this pool from the active list will happen at the round rollover, this ensures it is still rolled over
    }

    /// @dev Turn on a turned off expedition
    function enableExpeditionPool(uint16 _pid)
        public
        onlyOwner poolExistsAndLaunched(_pid)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];
        require(!pool.live, "Expedition already enabled");
        pool.live = true;
        markIsExpeditionActive(pool, true); // Will be early exited if pool.active is already true, meaning this exped was disabled this round
    }


    /// @dev Extend a currently running expedition pool
    /// @param _pid Pool identifier
    /// @param _additionalRewardAmount Additional reward token to distribute over extended rounds
    /// @param _additionalRounds Number of rounds to add to the expedition
    function extendExpeditionPool(uint16 _pid, uint256 _additionalRewardAmount, uint256 _additionalRounds)
        public
        onlyOwner poolExistsAndLaunched(_pid)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];
        require(pool.live, "Expedition disabled");

        // Number of rounds of current expedition already completed
        uint256 currRound = elevationHelper.roundNumber(EXPEDITION);
        uint256 roundsCompleted = currRound - pool.startRound;

        // Calculate the amount of rewards remaining to be distributed over the remaining expedition rounds
        uint256 rewardsRemaining = pool.totalRewardAmount.sub(roundsCompleted.mul(pool.roundEmission));

        // The total rewards after adding new additional rewards
        uint256 rewardsRemainingWithAdditional = rewardsRemaining.add(_additionalRewardAmount);

        // Ensure that the cartographerExpedition contract has enough reward token to cover what remains to be distributed, as well as full reward amount of next expedition
        uint256 unmarkedRewards = pool.rewardToken.balanceOf(address(this)).sub(pool.rewardsMarkedForDist);
        require(unmarkedRewards > rewardsRemainingWithAdditional, "Must have funds to cover expedition");
        
        // Total rounds remaining with the extra rounds added
        uint256 roundsRemaining = pool.totalRoundsCount.add(_additionalRounds).sub(roundsCompleted);

        // Calculate the new reward emission per block
        uint256 newRoundEmission = rewardsRemainingWithAdditional.div(roundsRemaining);

        // Update expedition state variables
        pool.totalRewardAmount = pool.totalRewardAmount.add(_additionalRewardAmount);
        pool.totalRoundsCount = pool.totalRoundsCount.add(_additionalRounds);
        pool.roundEmission = newRoundEmission;

        emit ExpeditionExtended(_pid, address(pool.rewardToken), pool.totalRewardAmount, pool.totalRoundsCount);
    }


    /// @dev Restart an expedition that has finished. Essentially creating a new elevation from scratch with a already used rewardToken
    /// @param _pid Expedition to restart
    /// @param _startRoundOffset Round to start the expedition on, valid from next round to next week
    /// @param _rewardAmount Amount of reward token to distribute over full duration of all rounds
    /// @param _rounds Rounds to add to the expedition
    function restartExpeditionPool(uint16 _pid, uint256 _startRoundOffset, uint256 _rewardAmount, uint256 _rounds)
        public onlyOwner
        poolExists(_pid)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];
        require(!pool.launched, "Expedition already running");
        require(_startRoundOffset <= 7, "Must start within the week");

        // Ensure that the cartographerExpedition contract has enough reward token to cover what remains to be distributed, as well as full reward amount of next expedition
        uint256 unmarkedRewards = pool.rewardToken.balanceOf(address(this)).sub(pool.rewardsMarkedForDist);
        require(unmarkedRewards >= _rewardAmount, "Must have funds to cover expedition");

        // Set state variables of restarted expedition
        pool.launched = false;
        pool.live = true;
        pool.active = false;
        pool.totalRoundsCount = _rounds;
        pool.totalRewardAmount = _rewardAmount;
        pool.roundEmission = _rewardAmount.div(_rounds);
        pool.startRound = elevationHelper.nextRound(EXPEDITION).add(_startRoundOffset);

        markIsExpeditionActive(pool, true);

        emit ExpeditionRestarted(_pid, address(pool.rewardToken), pool.totalRewardAmount, pool.totalRoundsCount);
    }





    // ---------------------------------------------------
    // --   T O K E N   &   L P   C O M B I N E D
    // ---------------------------------------------------
    

    /// @dev Equivalent amount of SUMMIT in an amount of SUMMIT LP
    ///      This amount is pulled directly from the SUMMIT LP token's internal data
    ///      As well as the incentiveMultiplier if required
    function equivalentSUMMITInLp(uint256 _amount)
        internal view
        returns (uint256)
    {
        if (summitLp.totalSupply() == 0) return 0;
        (uint256 reserve0, uint256 reserve1,) = summitLp.getReserves();
        uint256 summitReserve = summitLp.token0() == address(summit) ? reserve0 : reserve1;
        return _amount.mul(summitReserve).mul(summitInLpIncentiveMultiplier).div(100).div(summitLp.totalSupply());
    }

    /// @dev Combined equivalent SUMMIT from a SUMMIT LP and SUMMIT token source
    function combinedEquivalentSUMMIT(uint256 _summitAmount, uint256 _lpAmount)
        internal view
        returns (uint256)
    {
        return _summitAmount.add(equivalentSUMMITInLp(_lpAmount));
    }





    // ---------------------------------------
    // --   P O O L   R E W A R D S
    // ---------------------------------------
    
    function rewards(uint16 _pid, address _userAdd)
        external view override
        onlyCartographer poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256, uint256, uint256, uint256)
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];
        
        // Calculate and return the harvestable winnings for this expedition
        return (harvestableWinnings(pool, user, _userAdd), 0, 0, 0);
    }


    /// @dev User's staked amount, and how much they will win with that stake amount
    /// @param _pid Expedition to check
    /// @param _userAdd User to check
    /// @return (
    ///     hypotheticalYield - Users staked amount
    ///     hypotheticalWinnings - If user wins, their return based on staking and round emission
    /// )
    function hypotheticalRewards(uint16 _pid, address _userAdd)
        external view override
        poolExists(_pid) validUserAdd(_userAdd)
        returns (uint256, uint256)
    {
        ExpeditionPoolInfo memory pool = expeditionPoolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_userAdd];

        if (!pool.live) return (0, 0);

        // Calc amount of equivalent SUMMIT the user has from the token itself and LP
        uint256 combinedStaked = combinedEquivalentSUMMIT(user.summitStaked, user.lpStaked);

        // Equivalent SUMMIT (token + LP) of the user's totem
        uint256 userTotemCombinedSupply = combinedEquivalentSUMMIT(
            pool.totemSummitSupply[userTotem[_userAdd]],
            pool.totemLpSupply[userTotem[_userAdd]]
        );

        // Exit early if div/0 or pool not launched
        if (userTotemCombinedSupply == 0 || !pool.launched) return (combinedStaked, 0);

        // Calculate and return hypothetical winnings
        return (combinedStaked, combinedStaked.mul(pool.roundEmission).div(userTotemCombinedSupply));
    }




    // ------------------------------------------------------------------
    // --   R O L L O V E R   E L E V A T I O N   R O U N D
    // ------------------------------------------------------------------
    
    
    /// @dev Rolling over all expeditions
    ///      Expeditions set to open (expedition.startRound == nextRound) are enabled
    ///      Expeditions set to end are disabled
    function rollover(uint8)
        external override
        onlyCartographer
    {
        uint256 currRound = elevationHelper.roundNumber(EXPEDITION);

        // Update and rollover all active pools
        for (uint16 activePoolSlot = 0; activePoolSlot < 24; activePoolSlot++) {
            // Early exit if active pool slot is empty
            if (activeExpeds[activePoolSlot] == 0) continue;
            rolloverExpedition(activeExpeds[activePoolSlot], currRound);
        }
    }
    

    /// @dev Roll over a single expedition
    /// @param _pid Expedition to roll over
    /// @param _currRound Current round
    function rolloverExpedition(uint16 _pid, uint256 _currRound)
        internal
    {
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // If the pool is marked as not live (it was marked as not live during previous round if this is the case)
        // OR if pool is ending naturally (its finished all its rounds)
        // Unlaunch the pool, and remove it from the active expeditions list
        if (!pool.live || (_currRound >= pool.startRound + pool.totalRoundsCount)) {
            pool.launched = false;
            markIsExpeditionActive(pool, false);
        }

        // If the pool isn't launched, launch it and exit early (no emissions before launch)
        else if (_currRound >= pool.startRound && !pool.launched) {
            pool.launched = true;
            return;
        }

        uint8 winningTotem = elevationHelper.winningTotem(EXPEDITION, _currRound - 1);

        // Supply of the selected winner
        uint256 winningTotemSummitSupply = pool.totemSummitSupply[winningTotem];
        uint256 winningTotemLpEquivalentSummitSupply = equivalentSUMMITInLp(pool.totemLpSupply[winningTotem]);
        uint256 combinedWinningTotemSupply = winningTotemSummitSupply.add(winningTotemLpEquivalentSummitSupply);

        // Calculate winnings multiplier or escape if div/0
        bool winningsDivBy0 = !pool.live || combinedWinningTotemSupply == 0;
        uint256 combinedWinningsMultiplier = winningsDivBy0 ? 0 : pool.roundEmission.mul(1e36).div(combinedWinningTotemSupply);
        uint256 summitWinningsMultiplier = winningsDivBy0 ? 0 : combinedWinningsMultiplier;
        uint256 winningsLpMultiplier = winningsDivBy0 ? 0 : equivalentSUMMITInLp(combinedWinningsMultiplier);

        // Mark current round's emission to be distributed
        pool.rewardsMarkedForDist = pool.rewardsMarkedForDist.add(
            winningsDivBy0 ? 0 : pool.roundEmission
        );

        // Update winning totems running winnings mult
        pool.totemRunningSummitWinningsMult[winningTotem] += summitWinningsMultiplier;
        pool.totemRunningLpWinningsMult[winningTotem] += winningsLpMultiplier;
    }
    


    

    // ------------------------------------------------------------
    // --   W I N N I N G S   C A L C U L A T I O N S 
    // ------------------------------------------------------------


    /// @dev Calculation of winnings that are availabel to be harvested
    /// @param pool Pool info
    /// @param user UserInfo
    /// @param _userAdd User's address passed through for win check
    /// @return Total winnings for a user, including vesting on previous round's winnings (if any)
    function harvestableWinnings(ExpeditionPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal view
        poolExists(pool.pid)
        returns (uint256)
    {
        uint256 currRound = elevationHelper.roundNumber(EXPEDITION);

        // Escape early if no previous round exists with available winnings
        if (currRound <= pool.startRound) return 0;

        // If user interacted in current round, no winnings available
        if (user.prevInteractedRound == currRound) return 0;

        // Calculate user's winnings over rounds they have participated in
        // SUMMIT and SUMMIT LP have separate debts and multipliers over time
        uint256 summitStakedWinnings = user.summitStaked.mul(pool.totemRunningSummitWinningsMult[userTotem[_userAdd]].sub(user.summitWinningsDebt)).div(1e36);
        uint256 lpStakedWinnings = user.lpStaked.mul(pool.totemRunningLpWinningsMult[userTotem[_userAdd]].sub(user.lpWinningsDebt)).div(1e36);

        return summitStakedWinnings.add(lpStakedWinnings);
    }
    


    

    // ------------------------------------------------------------
    // --   W I N N I N G S   I N T E R A C T I O N S
    // ------------------------------------------------------------
    
    
    /// @dev Harvest any available winnings
    /// @param pool Pool info
    /// @param user User info
    /// @param _userAdd USer's address used for redeeming rewards and checking for if rounds won
    function harvestWinnings(ExpeditionPoolInfo storage pool, UserInfo storage user, address _userAdd)
        internal
    {
        // Get calculated harvestable winnings
        uint256 winnings = harvestableWinnings(pool, user, _userAdd);

        // Early escape if no winnings available to harvest;
        if (winnings == 0) return;

        // Transfer winnings to user
        pool.rewardToken.safeTransfer(_userAdd, winnings);

        // Mark harvested winnings as withdrawn
        pool.rewardsMarkedForDist = pool.rewardsMarkedForDist.sub(winnings);
    }


    /// @dev Update the users round interaction
    /// @param pool Pool info with winnings mult
    /// @param user User info
    /// @param _totem Users selected totem
    /// @param _summitAmount Amount of SUMMIT token depositing / withdrawing
    /// @param _lpAmount Amount of SUMMIT LP depositing / withdrawing
    /// @param _isDeposit Flag to differentiate deposit / withdraw
    function updateUserRoundInteraction(ExpeditionPoolInfo storage pool, UserInfo storage user, uint8 _totem, uint256 _summitAmount, uint256 _lpAmount, bool _isDeposit)
        internal
    {
        uint256 currRound = elevationHelper.roundNumber(EXPEDITION);
        
        // Update the user's staked amount with either the deposit or withdraw amount
        if (_isDeposit) {
            user.summitStaked = user.summitStaked.add(_summitAmount);
            user.lpStaked = user.lpStaked.add(_lpAmount);
        } else {
            user.summitStaked = user.summitStaked.sub(_summitAmount);
            user.lpStaked = user.lpStaked.sub(_lpAmount);
        }

        // Acc winnings per share of user's totem of both SUMMIT token and SUMMIT LP
        user.summitWinningsDebt = pool.totemRunningSummitWinningsMult[_totem];
        user.lpWinningsDebt = pool.totemRunningLpWinningsMult[_totem];

        // Update the user's previous interacted round to be this round
        user.prevInteractedRound = currRound;
    }

    


    

    // ------------------------------------------------------------
    // --   E L E V A T I O N   T O T E M S
    // ------------------------------------------------------------


    /// @dev Increments or decrements user's pools at elevation staked, and adds to  / removes from users list of staked pools
    function markIsUserInteractingWithPool(ExpeditionPoolInfo storage pool, bool _interacting, address _userAdd) internal {
        require(!_interacting || userExpedInteractingCount[_userAdd] < 12, "Staked pool cap (12) reached");

        userExpedInteractingCount[_userAdd] = userExpedInteractingCount[_userAdd]
            .add(_interacting ? 1 : 0)
            .sub(_interacting ? 0 : 1);

        (uint16 targetPid, uint16 replacementPid) = _interacting ? (uint16(0), pool.pid) : (pool.pid, uint16(0));

        for (uint8 stakingSlot = 0; stakingSlot < 12; stakingSlot++) {
            if (userInteractingExpeditions[_userAdd][stakingSlot] == targetPid) {
                userInteractingExpeditions[_userAdd][stakingSlot] = replacementPid;
                return;
            }
        }
    }


    /// @dev Whether the user actively has any funds staked with their selected totem      
    /// @param _userAdd User checking totem
    /// @return If any expedition has funds staked with this totem
    function isTotemInUse(uint8, address _userAdd) external view override returns (bool) {
        return userExpedInteractingCount[_userAdd] > 0;
    }

    /// @dev All expeditions share a totem. This function allows switching staked funds from one totem to another
    /// @param _totem New target totem
    /// @param _userAdd User requesting switch
    function switchTotem(uint8, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer validTotem(_totem) validUserAdd(_userAdd) expeditionInteractionsAvailable()
    {
        // Early exit if totem is same as current
        require(userTotem[_userAdd] != _totem, "Totem must be different");

        // Iterate through expeditions and switch totem for each user staked with
        for (uint16 stakingSlot = 0; stakingSlot < 12; stakingSlot++) {
            if (userInteractingExpeditions[_userAdd][stakingSlot] != 0) {

                // Switch funds to new totem for expedition
                switchTotemForExpedition(userInteractingExpeditions[_userAdd][stakingSlot], _totem, _userAdd);
            }
        }

        // Update user totem in state
        userTotem[_userAdd] = _totem;
    }

    /// @dev Switch users funds (if any staked) to the new totem
    /// @param _pid Expedition identifier
    /// @param _totem Totem the user is leaving
    /// @param _userAdd User doing the switch
    function switchTotemForExpedition(uint16 _pid, uint8 _totem, address _userAdd)
        internal
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Harvest any winnings in this expedition
        harvestWinnings(pool, user, _userAdd);
        
        // Update user's interaction in this pool
        updateUserRoundInteraction(pool, user, _totem, 0, 0, true);
        
        // Transfer supply and round rewards from previous totem to new totem
        if (user.summitStaked > 0) {
            pool.totemSummitSupply[userTotem[_userAdd]] = pool.totemSummitSupply[userTotem[_userAdd]].sub(user.summitStaked);
            pool.totemSummitSupply[_totem] = pool.totemSummitSupply[_totem].add(user.summitStaked);
        }
        if (user.lpStaked > 0) {
            pool.totemLpSupply[userTotem[_userAdd]] = pool.totemLpSupply[userTotem[_userAdd]].sub(user.lpStaked);
            pool.totemLpSupply[_totem] = pool.totemLpSupply[_totem].add(user.lpStaked);
        }
    }
    


    

    // ------------------------------------------------------------
    // --   P O O L   I N T E R A C T I O N S
    // ------------------------------------------------------------


    /// @dev Stub to handle cross compounding in staking pools, doesn't exist in Expedition
    function crossCompound(uint16, uint16, uint8, address) external override returns (uint256) {}

    /// @dev Stub because no need to harvest multiple expeditions (only 1 running at once and convenience function not needed)
    function harvestElevation(uint8, uint16, address) external override returns (uint256) {}

   

    /// @dev Stake funds in a yield multiplying elevation pool
    /// @param _pid Pool to stake in
    /// @param _summitAmount Amount of SUMMIT token to stake
    /// @param _lpAmount Amount of SUMMIT LP to stake
    /// @param _totem Totem to stake with
    /// @param _userAdd User wanting to stake
    /// @return Amount deposited after deposit fee taken of both SUMMIT token and SUMMIT LP
    function deposit(uint16 _pid, uint256 _summitAmount, uint256 _lpAmount, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExistsAndLaunched(_pid) validTotem(_totem) validUserAdd(_userAdd) expeditionInteractionsAvailable()
    returns (uint256, uint256) {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Exit early if different totem already has staked value
        require(userExpedInteractingCount[_userAdd] == 0 || userTotem[_userAdd] == _totem, "Cant switch totem during deposit");

        // Pass info to unified deposit to handle remainder of deposit functionality
        return unifiedDeposit(pool, user, _summitAmount, _lpAmount, _totem, _userAdd, false);
    }
    

    /// @dev Elevate funds into the expedition
    /// @param _pid Expedition to elevate into
    /// @param _amount Amount of SUMMIT or SUMMIT LP token to elevate
    /// @param _token Address of the depositing token, already validated to be SUMMIT or SUMMIT LP
    /// @param _totem Deity to stake funds with
    /// @param _userAdd User elevating funds
    /// @return Amount deposited after fee taken of both SUMMIT token and SUMMIT LP
    function elevateDeposit(uint16 _pid, uint256 _amount, address _token, uint8 _totem, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExistsAndLaunched(_pid) validTotem(_totem) validUserAdd(_userAdd) expeditionInteractionsAvailable()
        returns (uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Use {_token} address to determine whether to withdraw SUMMIT or SUMMIT LP
        uint256 summitAmount = _token == address(summit) ? _amount : 0;
        uint256 lpAmount = _token == address(summitLp) ? _amount : 0;

        // Perform Elevate withdraw with the diffed withdraw amounts
        (summitAmount, lpAmount) = unifiedDeposit(pool, user, summitAmount, lpAmount, _totem, _userAdd, true);

        // Only one of SUMMIT or SUMMIT LP was deposited in the elevate flow: results can be summed and returned
        return summitAmount.add(lpAmount);
    }


    /// @dev User interacting with pool getter
    /// User.summitStaked and user.lpStaked checks if the user has any funds in the expedition
    /// harvestableWinnings will only be non zero if the user has some amount of winnings to withdraw
    function _userInteractingWithPool(ExpeditionPoolInfo storage pool, UserInfo storage user, address _userAdd) internal view returns (bool) {
        return user.summitStaked.add(user.lpStaked).add(harvestableWinnings(pool, user, _userAdd)) > 0;
    }
    function userInteractingWithPool(uint16 _pid) public view poolExists(_pid) returns (bool) {
        return _userInteractingWithPool(expeditionPoolInfo[_pid], userInfo[_pid][msg.sender], msg.sender);
    }


    

    /// @dev Internal shared deposit functionality for elevating into the expedition
    /// @param pool Expedition to elevate into
    /// @param user UserInfo of depositing user
    /// @param _summitAmount Amount of SUMMIT token to deposit
    /// @param _lpAmount Amount of SUMMIT LP to deposit
    /// @param _totem Deity to stake funds with
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality for elevate deposit
    /// @return Amount deposited after fee taken of both SUMMIT token and SUMMIT LP
    function unifiedDeposit(ExpeditionPoolInfo storage pool, UserInfo storage user, uint256 _summitAmount, uint256 _lpAmount, uint8 _totem, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256, uint256)
    {
        // If the user is interacting with this pool at the beginning of the txn
        bool interactingWithPoolInit = _userInteractingWithPool(pool, user, _userAdd);

        // Harvest winnings if any available
        harvestWinnings(pool, user, _userAdd);

        // Deposit funds if any amount passed in
        if (_summitAmount.add(_lpAmount) > 0) {

            if (!_isInternalTransfer) {
                cartographer.depositTokenManagement(_userAdd, summit, 0, _summitAmount, _lpAmount);
            }

            // Add deposit amount to running supply accumulators
            if (_summitAmount > 0) {
                pool.totemSummitSupply[_totem] += _summitAmount;
                pool.summitSupply += _summitAmount;
            }
            if (_lpAmount > 0) {
                pool.totemLpSupply[_totem] += _lpAmount;
                pool.lpSupply += _lpAmount;
            }
        }
        
        // Update / create users interaction with the expedition
        updateUserRoundInteraction(pool, user, _totem, _summitAmount, _lpAmount, true);

        // Update user's totem
        userTotem[_userAdd] = _totem;

        // If the user is interacting with this pool after the meat of the transaction completes
        bool interactingWithPoolFinal = _userInteractingWithPool(pool, user, _userAdd);

        // If the user's interacting status has changed, update it in the users active pools list
        if (interactingWithPoolInit != interactingWithPoolFinal)
            markIsUserInteractingWithPool(pool, interactingWithPoolFinal, _userAdd);
        
        // Return amount deposited in the expedition
        return (_summitAmount, _lpAmount);
    }


    /// @dev Withdraw staked funds from expedition
    /// @param _pid Expedition to withdraw from
    /// @param _summitAmount Amount of SUMMIT or SUMMIT LP token to withdraw
    /// @param _lpAmount Amount of SUMMIT LP to withdraw
    /// @param _userAdd User withdrawing
    /// @return True amount withdrawn of both SUMMIT token and SUMMIT LP
    function withdraw(uint16 _pid, uint256 _summitAmount, uint256 _lpAmount, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd) expeditionInteractionsAvailable()
        returns (uint256, uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Shared withdraw functionality
        return unifiedWithdraw(pool, user, _summitAmount, _lpAmount, _userAdd, false);
    }


    /// @dev Withdraw staked funds to elevate them to another elevation
    /// @param _pid Expedition to elevate funds from
    /// @param _amount Amount of SUMMIT token to elevate
    /// @param _token Token to be deposited, either SUMMIT or SUMMIT LP or validation of token would have failed already
    /// @param _userAdd User elevating
    /// @return Amount withdrawn to be elevated
    function elevateWithdraw(uint16 _pid, uint256 _amount, address _token, address _userAdd)
        external override
        nonReentrant onlyCartographer poolExists(_pid) validUserAdd(_userAdd) expeditionInteractionsAvailable()
        returns (uint256)
    {
        UserInfo storage user = userInfo[_pid][_userAdd];
        ExpeditionPoolInfo storage pool = expeditionPoolInfo[_pid];

        // Use {_token} address to determine whether to withdraw SUMMIT or SUMMIT LP
        uint256 summitAmount = _token == address(summit) ? _amount : 0;
        uint256 lpAmount = _token == address(summitLp) ? _amount : 0;

        // Perform Elevate withdraw with the diffed withdraw amounts
        (summitAmount, lpAmount) = unifiedWithdraw(pool, user, summitAmount, lpAmount, _userAdd, true);

        // Only one of SUMMIT or SUMMIT LP was deposited in the elevate flow: results can be summed and returned
        return summitAmount.add(lpAmount);
    }


    /// @dev Withdraw functionality shared between standardWithdraw and elevateWithdraw
    /// @param pool Pool to withdraw from
    /// @param user UserInfo of withdrawing user
    /// @param _summitAmount Amount of SUMMIT token to withdraw
    /// @param _summitAmount Amount of SUMMIT LP to withdraw
    /// @param _userAdd User address
    /// @param _isInternalTransfer Flag to switch off certain functionality for elevate withdraw
    /// @return Amount withdrawn of both SUMMIT token and SUMMIT LP
    function unifiedWithdraw(ExpeditionPoolInfo storage pool, UserInfo storage user, uint256 _summitAmount, uint256 _lpAmount, address _userAdd, bool _isInternalTransfer)
        internal
        returns (uint256, uint256)
    {
        // Validate amount attempting to withdraw
        require(_summitAmount.add(_lpAmount) > 0 && user.summitStaked >= _summitAmount && user.lpStaked >= _lpAmount, "Bad withdrawal");

        // If the user is interacting with this pool at the beginning of the txn
        bool interactingWithPoolInit = _userInteractingWithPool(pool, user, _userAdd);
        
        // Harvest winnings if any available
        harvestWinnings(pool, user, _userAdd);

        // Update / create round interaction
        uint8 totem = userTotem[_userAdd];
        updateUserRoundInteraction(pool, user, totem, _summitAmount, _lpAmount, false);
        
        // Transfer funds back to user
        if (!_isInternalTransfer) {
            cartographer.withdrawalTokenManagement(_userAdd, summit, 0, _summitAmount, _lpAmount);
        }

        // Subtract withdrawn amount from running supply totals
        if (_summitAmount > 0) {
            pool.totemSummitSupply[totem] = pool.totemSummitSupply[totem].sub(_summitAmount);
            pool.summitSupply = pool.summitSupply.sub(_summitAmount);
        }
        if (_lpAmount > 0) {
            pool.totemLpSupply[totem] = pool.totemLpSupply[totem].sub(_lpAmount);
            pool.lpSupply = pool.lpSupply.sub(_lpAmount);
        }

        // If the user is interacting with this pool after the meat of the transaction completes
        bool interactingWithPoolFinal = _userInteractingWithPool(pool, user, _userAdd);

        // If the user's interacting status has changed, update it in the users active pools list
        if (interactingWithPoolInit != interactingWithPoolFinal)
            markIsUserInteractingWithPool(pool, interactingWithPoolFinal, _userAdd);
        
        // Return amount withdrawn
        return (_summitAmount, _lpAmount);
    }
}

File 8 of 21 : ElevationHelper.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";


/*
---------------------------------------------------------------------------------------------
--   S U M M I T . D E F I
---------------------------------------------------------------------------------------------


Summit is highly experimental.
It has been crafted to bring a new flavor to the defi world.
We hope you enjoy the Summit.defi experience.
If you find any bugs in these contracts, please claim the bounty (see docs)


Created with love by Architect and the Summit team





---------------------------------------------------------------------------------------------
--   E L E V A T I O N   H E L P E R
---------------------------------------------------------------------------------------------


ElevationHelper.sol handles shared functionality between the elevations / expedition
It is also responsible for requesting and receiving the trusted seeds, as well as their decrypted versions after the future block is mined
Handles the allocation multiplier for each elevation
Handles the duration of each round


RANDOM NUMBER GENERATION
    . Webservice queries `nextSeedRoundAvailable` every 5 seconds
    . When true received
    . Webservice creates a random seed, seals it with the trustedSeeder address, and sends it to `receiveSealedSeed`
    . When the futureBlockNumber set in `receiveSealedSeed` is mined, `futureBlockMined` will return true
    . Webservice sends the original unsealed seed to `receiveUnsealedSeed`


*/

contract ElevationHelper is Ownable {
    using SafeMath for uint256;

    // ---------------------------------------
    // --   V A R I A B L E S
    // ---------------------------------------

    address public cartographer;                                            // Allows cartographer to act as secondary owner-ish
    address public trustedSeeder;                                           // Submits trusted sealed seed when round locks 60 before round end

    // Constants for elevation comparisons
    uint8 constant OASIS = 0;
    uint8 constant TWOTHOUSAND = 1;
    uint8 constant FIVETHOUSAND = 2;
    uint8 constant TENTHOUSAND = 3;
    uint8 constant EXPEDITION = 4;

    
    uint8[5] public allocMultiplier = [100, 110, 125, 150, 100];            // Alloc point multipliers for each elevation    
    uint8[5] public totemCount = [1, 2, 5, 10, 2];                          // Number of totems at each elevation

    
    uint256 constant baseRoundDuration = 3600;                              // Duration (seconds) of the smallest round chunk
    uint256[5] public durationMult = [0, 2, 4, 8, 24];                      // Number of round chunks for each elevation

    uint256[5] public unlockTimestamp;                                      // Time at which each elevation unlocks to the public
    uint256[5] public roundNumber;                                          // Current round of each elevation
    uint256[5] public roundEndTimestamp;                                    // Time at which each elevation's current round ends
    mapping(uint256 => uint8) public expeditionDeityDivider;                // Higher / lower integer for each expedition round

    mapping(uint8 => mapping(uint256 => uint256)) public totemWinsAccum;    // Accumulator of the total number of wins for each totem
    mapping(uint8 => mapping(uint256 => uint8)) public winningTotem;        // The specific winning totem for each elevation round


    uint256 constant referralDurationMult = 24 * 7;                         // Round chunk multiplier for the unclaimed referral rewards burn
    uint256 public referralRound;                                           // Incrementor of the referral round
    uint256 public referralBurnTimestamp;                                   // Time at which burning unclaimed referral rewards becomes available


    uint256 seedRoundEndTimestamp;                                          // Timestamp the first seed round ends
    uint256 seedRoundDurationMult = 2;
    uint256 seedRound = 0;                                                  // The sealed seed is generated at the top of every hour
    mapping(uint256 => bytes32) sealedSeed;                                 // Sealed seed for each seed round, provided by trusted seeder webservice                                              
    mapping(uint256 => bytes32) unsealedSeed;                               // Sealed seed for each seed round, provided by trusted seeder webservice                                              
    mapping(uint256 => uint256) futureBlockNumber;                          // Future block number for each seed round
    mapping(uint256 => bytes32) futureBlockHash;                            // Future block hash for each seed round






    // ---------------------------------------
    // --   E V E N T S
    // ---------------------------------------

    event SetTrustedSeederAdd(address indexed user, address indexed newAddress);
    event WinningTotemSelected(uint8 indexed elevation, uint256 indexed round, uint8 indexed totem);
    event DeityDividerSelected(uint256 indexed expeditionRound, uint8 indexed deityDivider);



    
    // ---------------------------------------
    // --   I N I T I A L I Z A T I O N
    // ---------------------------------------

    /// @dev Creates ElevationHelper contract with cartographer as owner of certain functionality
    /// @param _cartographer Address of main Cartographer contract
    constructor(address _cartographer)
        public
        onlyOwner
    {
        require(_cartographer != address(0), "Cartographer missing");
        cartographer = _cartographer;
    }

    /// @dev Turns on the Summit ecosystem across all contracts
    /// @param _enableTimestamp Timestamp at which Summit was enabled, used to set unlock points for each elevation
    function enable(uint256 _enableTimestamp)
        external
        onlyCartographer
    {
        // The next top of hour from the enable timestamp
        uint256 nextHourTimestamp = _enableTimestamp + (3600 - (_enableTimestamp % 3600));

        // Setting when each elevation of the ecosystem unlocks
        unlockTimestamp = [
            nextHourTimestamp,                          // Oasis - throwaway
            nextHourTimestamp,                          // Plains
            nextHourTimestamp.add(1 days),              // Mesa
            nextHourTimestamp.add(4 days),              // Summit
            nextHourTimestamp.add(7 days)               // Expedition
        ];

        // The first 'round' ends when the elevation unlocks
        roundEndTimestamp = unlockTimestamp;
        
        // Timestamp the first unclaimed referral rewards burn becomes available
        referralBurnTimestamp = nextHourTimestamp.add(7 days);    

        // Timestamp of the first seed round starting
        seedRoundEndTimestamp = nextHourTimestamp.sub(60 seconds);
    }


    /// @dev Update trusted seeder
    function setTrustedSeederAdd(address _trustedSeeder) external onlyCartographer {
        require(_trustedSeeder != address(0), "Trusted seeder missing");
        trustedSeeder = _trustedSeeder;

        emit SetTrustedSeederAdd(msg.sender, _trustedSeeder);
    }
    




    // ---------------------------------------
    // --   M O D I F I E R S
    // ---------------------------------------

    modifier onlyCartographer() {
        require(msg.sender == cartographer, "Only cartographer");
        _;
    }
    modifier onlyTrustedSeeder() {
        require(msg.sender == trustedSeeder, "Only trusted seeder");
        _;
    }
    modifier allElevations(uint8 _elevation) {
        require(_elevation <= EXPEDITION, "Bad elevation");
        _;
    }
    modifier elevationOrExpedition(uint8 _elevation) {
        require(_elevation >= TWOTHOUSAND && _elevation <= EXPEDITION, "Bad elevation");
        _;
    }
    





    // ---------------------------------------
    // --   U T I L S (inlined for brevity)
    // ---------------------------------------
    
    /// @dev Duration of elevation round in seconds
    /// @param _elevation Desired elevation
    function roundDurationSeconds(uint8 _elevation) public view returns (uint256) {
        return durationMult[_elevation] * baseRoundDuration;
    }

    /// @dev Current round of the expedition
    function currentExpeditionRound() public view returns (uint256) {
        return roundNumber[EXPEDITION];
    }

    /// @dev Deity divider (random offset which skews chances of each deity winning) of the current expedition round
    function currentDeityDivider() public view returns (uint8) {
        return expeditionDeityDivider[currentExpeditionRound()];
    }

    /// @dev Modifies a given alloc point with the multiplier of that elevation, used to set a single allocation for a token while each elevation is set automatically
    /// @param _allocPoint Base alloc point to modify
    /// @param _elevation Fetcher for the elevation multiplier
    function elevationModulatedAllocation(uint256 _allocPoint, uint8 _elevation) external view allElevations(_elevation) returns (uint256) {
        return _allocPoint.mul(allocMultiplier[_elevation]);
    }

    /// @dev Checks whether elevation is locked due to round ending in next 60 seconds
    /// @param _elevation Which elevation to check
    function endOfRoundLockoutActive(uint8 _elevation) external view returns (bool) {
        return block.timestamp >= roundEndTimestamp[_elevation] - 60;
    }

    /// @dev The next round available for a new pool to unlock at. Used to add pools but not start them until the next rollover
    /// @param _elevation Which elevation to check
    function nextRound(uint8 _elevation) external view returns (uint256) {
        return block.timestamp <= unlockTimestamp[_elevation] ? 1 : (roundNumber[_elevation] + 1);
    }

    /// @dev Whether a round has ended
    /// @param _elevation Which elevation to check
    function roundEnded(uint8 _elevation) internal view returns (bool) {
        return block.timestamp >= roundEndTimestamp[_elevation];
    }

    /// @dev Seconds remaining in round of elevation
    /// @param _elevation Which elevation to check time remaining of
    function timeRemainingInRound(uint8 _elevation) public view returns (uint256) {
        return roundEnded(_elevation) ? 0 : roundEndTimestamp[_elevation] - block.timestamp;
    }

    /// @dev Getter of fractional amount of round remaining
    /// @param _elevation Which elevation to check progress of
    /// @return Fraction raised to 1e12
    function fractionRoundRemaining(uint8 _elevation) external view returns (uint256) {
        return timeRemainingInRound(_elevation)
            .mul(1e12
            ).div(roundDurationSeconds(_elevation));
    }

    /// @dev Getter of fractional progress through round
    /// @param _elevation Which elevation to check progress of
    /// @return Fraction raised to 1e12
    function fractionRoundComplete(uint8 _elevation) external view returns (uint256) {
        return roundDurationSeconds(_elevation)
            .sub(timeRemainingInRound(_elevation))
            .mul(1e12)
            .div(roundDurationSeconds(_elevation));
    }

    /// @dev Start timestamp of current round
    /// @param _elevation Which elevation to check
    function currentRoundStartTime(uint8 _elevation) external view returns(uint256) {
        return roundEndTimestamp[_elevation].sub(roundDurationSeconds(_elevation));
    }

    /// @dev Time remaining until next available referral burn event
    function referralBurnTimeRemaining() external view returns (uint256) {
        return referralBurnTimestamp - block.timestamp;
    }

    /// @dev Validate unclaimed referral rewards available for burning
    function validateReferralBurnAvailable() external view {
        require(block.timestamp >= referralBurnTimestamp, "Referral burn not available");
    }


    


    // ------------------------------------------------------------------
    // --   R A N D O M N E S S   S E E D I N G
    // ------------------------------------------------------------------


    // Flow of seeding:
    // Webservice queries `nextSeedRoundAvailable` every 5 seconds
    // When true received
    // Webservice creates a random seed, seals it with the trustedSeeder address, and sends it to `receiveSealedSeed`
    // When the futureBlockNumber set in `receiveSealedSeed` is mined, `futureBlockMined` will return true
    // Webservice sends the original unsealed seed to `receiveUnsealedSeed`


    /// @dev Seed round locked
    function nextSeedRoundAvailable() public view returns (bool) {
        return block.timestamp >= seedRoundEndTimestamp;
    }


    /// @dev When an elevation reaches the lockout phase 60s before rollover, the sealedseed webserver will send a seed
    /// If the webserver goes down (99.99% uptime, 3 outages of 1H each over 3 years) the randomness is still secure, and is only vulnerable to a single round of withheld block attack
    /// @param _sealedSeed random.org backed sealed seed from the trusted address, run by an autonomous webserver
    function receiveSealedSeed(bytes32 _sealedSeed)
        public
        onlyTrustedSeeder
    {
        require(nextSeedRoundAvailable(), "Already sealed seeded");

        // Increment seed round and set next seed round end timestamp
        seedRound += 1;
        seedRoundEndTimestamp += (baseRoundDuration * seedRoundDurationMult);

        // Store new sealed seed for next round of round rollovers
        sealedSeed[seedRound] = _sealedSeed;
        futureBlockNumber[seedRound] = block.number + 1;
    }


    /// @dev Whether the future block has been mined, allowing the unencrypted seed to be received
    function futureBlockMined() public view returns (bool) {
        return sealedSeed[seedRound] != "" &&
            block.number > futureBlockNumber[seedRound] &&
            unsealedSeed[seedRound] == "";
    }


    /// @dev Receives the unencrypted seed after the future block has been mined
    /// @param _unsealedSeed Unencrypted seed
    function receiveUnsealedSeed(bytes32 _unsealedSeed)
        public
        onlyTrustedSeeder
    {
        require(unsealedSeed[seedRound] == "", "Already unsealed seeded");
        require(futureBlockMined(), "Future block not reached");
        require(keccak256(abi.encodePacked(_unsealedSeed, msg.sender)) == sealedSeed[seedRound], "Unsealed seed does not match");
        unsealedSeed[seedRound] = _unsealedSeed;
        futureBlockHash[seedRound] = blockhash(futureBlockNumber[seedRound]);
    }


    


    // ------------------------------------------------------------------
    // --   R O L L O V E R   E L E V A T I O N   R O U N D
    // ------------------------------------------------------------------


    /// @dev Validates that the selected elevation is able to be rolled over
    /// @param _elevation Which elevation is attempting to be rolled over
    function validateRolloverAvailable(uint8 _elevation)
        external view
    {
        // Elevation must be unlocked for round to rollover
        require(block.timestamp >= unlockTimestamp[_elevation], "Elevation locked");
        // Rollover only becomes available after the round has ended, if timestamp is before roundEnd, the round has already been rolled over and its end timestamp pushed into the future
        require(block.timestamp >= roundEndTimestamp[_elevation], "Round already rolled over");
    }


    /// @dev Uses the seed and future block number to generate a random number, which is then used to select the winning totem and if necessary the next deity divider
    /// @param _elevation Which elevation to select winner for
    function selectWinningTotem(uint8 _elevation)
        external
        onlyCartographer elevationOrExpedition(_elevation)
    {
        // No winning totem should be selected for round 0, which takes place when the elevation is locked
        if (roundNumber[_elevation] == 0) { return; }

        // Create the random number from the future block hash and newly unsealed seed
        uint256 rand = uint256(keccak256(abi.encode(roundNumber[_elevation], unsealedSeed[seedRound], futureBlockHash[seedRound])));
       
        // Uses the random number to select the winning totem
        uint8 winner = chooseWinningTotem(_elevation, rand);

        // Updates data with the winning totem
        markWinningTotem(_elevation, winner);

        // If necessary, uses the random number to generate the next deity divider for expeditions
        if (_elevation == EXPEDITION)
            setNextDeityDivider(rand);
    }


    /// @dev Final step in the rollover pipeline, incrementing the round numbers to bring current
    /// @param _elevation Which elevation is being updated
    function rolloverElevation(uint8 _elevation)
        external
        onlyCartographer
    {
        // Incrementing round number, does not need to be adjusted with overflown rounds
        roundNumber[_elevation] += 1;

        // Failsafe to cover multiple rounds needing to be rolled over if no user rolled them over previously (almost impossible, but just in case)
        uint256 overflownRounds = ((block.timestamp - roundEndTimestamp[_elevation]) / roundDurationSeconds(_elevation));
        
        // Brings current with any extra overflown rounds
        roundEndTimestamp[_elevation] = roundEndTimestamp[_elevation].add(roundDurationSeconds(_elevation) * (overflownRounds + 1));
    }


    /// @dev Simple modulo on generated random number to choose the winning totem (inlined for brevity)
    /// @param _elevation Which elevation the winner will be selected for
    /// @param _rand The generated random number to select with
    function chooseWinningTotem(uint8 _elevation, uint256 _rand) internal view returns (uint8) {
        if (_elevation == EXPEDITION)
            return (_rand % 100) < currentDeityDivider() ? 0 : 1;
        return uint8(_rand % totemCount[_elevation]);
    }


    /// @dev Stores selected winning totem (inlined for brevity)
    /// @param _elevation Elevation to store at
    /// @param _winner Selected winning totem
    function markWinningTotem(uint8 _elevation, uint8 _winner) internal {
        totemWinsAccum[_elevation][_winner] += 1;
        winningTotem[_elevation][roundNumber[_elevation]] = _winner;   

        emit WinningTotemSelected(_elevation, roundNumber[_elevation], _winner);
    }


    /// @dev Sets random deity divider (50 - 90) for next expedition round (inlined for brevity)
    /// @param _rand Same rand that chose winner
    function setNextDeityDivider(uint256 _rand) internal {
        // Number between 50 - 90 based on previous round winning number, helps to balance the divider between the deities
        uint256 expedSelector = (_rand % 100);
        uint8 divider = uint8(50 + expedSelector.mul(40).div(100));
        expeditionDeityDivider[currentExpeditionRound() + 1] = divider;

        emit DeityDividerSelected(currentExpeditionRound() + 1, divider);
    }
    


    // ------------------------------------------------------------------
    // --   R O L L O V E R   R E F E R R A L   B U R N
    // ------------------------------------------------------------------
    
    /// @dev Rollover referral burn round
    function rolloverReferralBurn() external  onlyCartographer {
        referralRound += 1;
        referralBurnTimestamp += baseRoundDuration * referralDurationMult;
    }


    
    
    // ------------------------------------------------------------------
    // --   F R O N T E N D
    // ------------------------------------------------------------------
    
    /// @dev Fetcher of historical data for past winning totems
    /// @param _elevation Which elevation to check historical winners of
    /// @return Array of 20 values, first 10 of which are win count accumulators for each totem, last 10 of which are winners of previous 10 rounds of play
    function historicalWinningTotems(uint8 _elevation) public view allElevations(_elevation) returns (uint256[20] memory) {

        // Early escape OASIS winners, as they don't exist
        if (_elevation == OASIS) {
            return [uint256(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        }

        uint256 round = roundNumber[_elevation];
        return [
            totemWinsAccum[_elevation][0],
            totemWinsAccum[_elevation][1],
            totemWinsAccum[_elevation][2],
            totemWinsAccum[_elevation][3],
            totemWinsAccum[_elevation][4],
            totemWinsAccum[_elevation][5],
            totemWinsAccum[_elevation][6],
            totemWinsAccum[_elevation][7],
            totemWinsAccum[_elevation][8],
            totemWinsAccum[_elevation][9],
            winningTotem[_elevation][round - 1],
            winningTotem[_elevation][round - 2],
            winningTotem[_elevation][round - 3],
            winningTotem[_elevation][round - 4],
            winningTotem[_elevation][round - 5],
            winningTotem[_elevation][round - 6],
            winningTotem[_elevation][round - 7],
            winningTotem[_elevation][round - 8],
            winningTotem[_elevation][round - 9],
            winningTotem[_elevation][round - 10]
        ];
    }
}

File 9 of 21 : Math.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }
}

File 10 of 21 : IBEP20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.4;

interface IBEP20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the token decimals.
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Returns the token symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the token name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the bep token owner.
     */
    function getOwner() external view returns (address);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address _owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 11 of 21 : SafeBEP20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "./IBEP20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title SafeBEP20
 * @dev Wrappers around BEP20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeBEP20 for IBEP20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeBEP20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IBEP20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IBEP20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IBEP20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IBEP20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeBEP20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IBEP20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IBEP20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeBEP20: decreased allowance below zero");
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IBEP20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeBEP20: low-level call failed");
        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeBEP20: BEP20 operation did not succeed");
        }
    }
}

File 12 of 21 : Ownable.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../utils/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

File 13 of 21 : Initializable.sol
// SPDX-License-Identifier: MIT

// solhint-disable-next-line compiler-version
pragma solidity >=0.4.24 <0.8.0;

import "../utils/Address.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 */
abstract contract Initializable {

    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

    /// @dev Returns true if and only if the function is running in the constructor
    function _isConstructor() private view returns (bool) {
        return !Address.isContract(address(this));
    }
}

File 14 of 21 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 15 of 21 : IPassthrough.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;


import "./libs/IBEP20.sol";



/// @dev Passthrough is an interface to send tokens to another contract and use the reward token in the Summit ecocystem
interface IPassthrough {
    function token() external view returns (IBEP20);
    function enact() external;
    function deposit(uint256, address, address) external returns (uint256);
    function withdraw(uint256, address, address) external returns (uint256);
    function retire(address, address) external;
}

File 16 of 21 : ILiquidityPair.sol
// SPDX-License-Identifier: MIT
pragma solidity = 0.6.12;

import "../libs/IBEP20.sol";

interface ILiquidityPair is IBEP20 {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112, uint112, uint32);
}

File 17 of 21 : ISubCart.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "./libs/IBEP20.sol";


interface ISubCart {
    function initialize(address, address, address) external;
    function enable(uint256) external;
    function add(uint16, uint8, bool, IBEP20, uint16) external;
    function addExpedition(uint16, bool, uint256, IBEP20, uint256, uint256) external;
    function set(uint16, bool, uint16) external;
    function massUpdatePools() external;

    function rollover(uint8) external;

    function rewards(uint16, address) external view returns (uint256, uint256, uint256, uint256);
    function hypotheticalRewards(uint16, address) external view returns (uint256, uint256);

    function switchTotem(uint8, uint8, address) external;
    function isTotemInUse(uint8, address) external view returns (bool);
    
    function crossCompound(uint16, uint16, uint8, address) external returns (uint256);
    function deposit(uint16, uint256, uint256, uint8, address) external returns (uint256, uint256);
    function harvestElevation(uint8, uint16, address) external returns (uint256);
    function elevateDeposit(uint16, uint256, address, uint8, address) external returns (uint256);
    function withdraw(uint16, uint256, uint256, address) external returns (uint256, uint256);
    function elevateWithdraw(uint16, uint256, address, address) external returns (uint256);
 
    function supply(uint16) external view returns (uint256);
    function token(uint16, bool) external view returns (IBEP20);
    function depositFee(uint16) external view returns (uint256);
    function isEarning(uint16) external view returns (bool);
    function selectedTotem(uint8, address) external view returns (uint8);
}

File 18 of 21 : Context.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

File 19 of 21 : BEP20.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.4.0;

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/GSN/Context.sol';
import './IBEP20.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';

/**
 * @dev Implementation of the {IBEP20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {BEP20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-BEP20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of BEP20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IBEP20-approve}.
 */
contract BEP20 is Context, IBEP20, Ownable {
    using SafeMath for uint256;

    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }

    /**
     * @dev Returns the bep token owner.
     */
    function getOwner() external override view returns (address) {
        return owner();
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public override view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public override view returns (string memory) {
        return _symbol;
    }

    /**
    * @dev Returns the number of decimals used to get its user representation.
    */
    function decimals() public override view returns (uint8) {
        return _decimals;
    }

    /**
     * @dev See {BEP20-totalSupply}.
     */
    function totalSupply() public override view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {BEP20-balanceOf}.
     */
    function balanceOf(address account) public override view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {BEP20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {BEP20-allowance}.
     */
    function allowance(address owner, address spender) public override view returns (uint256) {
        return _allowances[owner][spender];
    }
    
    /**
     * @dev See {BEP20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {BEP20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {BEP20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom (address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(amount, 'BEP20: transfer amount exceeds allowance')
        );
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {BEP20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {BEP20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, 'BEP20: decreased allowance below zero'));
        return true;
    }

    /**
     * @dev Creates `amount` tokens and assigns them to `msg.sender`, increasing
     * the total supply.
     *
     * Requirements
     *
     * - `msg.sender` must be the token owner
     */
    function mint(uint256 amount) public onlyOwner returns (bool) {
        _mint(_msgSender(), amount);
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer (address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), 'BEP20: transfer from the zero address');
        require(recipient != address(0), 'BEP20: transfer to the zero address');

        _balances[sender] = _balances[sender].sub(amount, 'BEP20: transfer amount exceeds balance');
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), 'BEP20: mint to the zero address');

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve (address owner, address spender, uint256 amount) internal {
        require(owner != address(0), 'BEP20: approve from the zero address');
        require(spender != address(0), 'BEP20: approve to the zero address');

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
}

File 20 of 21 : Context.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../utils/Context.sol";

File 21 of 21 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.2 <0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain`call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

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

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_cartographer","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrerAddress","type":"address"},{"indexed":true,"internalType":"address","name":"refereeAddress","type":"address"}],"name":"ReferralCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrerAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReferralRewardsRedeemed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"burner","type":"address"},{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UnclaimedReferralRewardsBurned","type":"event"},{"inputs":[{"internalType":"address","name":"referee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addReferralRewardsIfNecessary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"burner","type":"address"}],"name":"burnUnclaimedReferralRewardsAndRolloverRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cartographer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrerAddress","type":"address"}],"name":"createReferral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_summit","type":"address"}],"name":"enable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getPendingReferralRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReferralRewardsToBeBurned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReferralRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"pendingReferralRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"redeemReferralRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"referrerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"summit","outputs":[{"internalType":"contract SummitToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"totalReferralRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

6080604052600060025534801561001557600080fd5b50604051610e19380380610e198339818101604052602081101561003857600080fd5b50516001600160a01b038116610085576040805162461bcd60e51b815260206004820152600d60248201526c10d85c9d081c995c5d5a5c9959609a1b604482015290519081900360640190fd5b600080546001600160a01b039092166001600160a01b0319909216919091179055610d64806100b56000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c80637dffc3141161008c578063d8dcef3911610066578063d8dcef3914610206578063d9b8f6021461020e578063dcd33e8e14610234578063f958b61a14610260576100cf565b80637dffc314146101b25780639fbcb9cb146101d8578063d21cacdf146101e0576100cf565b806307bb27aa146100d45780630d42eb4b146101125780635bfa1b681461011a578063647aba30146101425780636d473295146101665780636e8ba9f61461018c575b600080fd5b610100600480360360408110156100ea57600080fd5b50803590602001356001600160a01b0316610268565b60408051918252519081900360200190f35b610100610285565b6101406004803603602081101561013057600080fd5b50356001600160a01b0316610301565b005b61014a6103fa565b604080516001600160a01b039092168252519081900360200190f35b6101406004803603602081101561017c57600080fd5b50356001600160a01b0316610409565b610100600480360360208110156101a257600080fd5b50356001600160a01b03166105fc565b610140600480360360208110156101c857600080fd5b50356001600160a01b0316610626565b61014a6107bf565b61014a600480360360208110156101f657600080fd5b50356001600160a01b03166107ce565b6101006107e9565b6101006004803603602081101561022457600080fd5b50356001600160a01b03166107ef565b6101406004803603604081101561024a57600080fd5b506001600160a01b038135169060200135610801565b6101406108f3565b600460209081526000928352604080842090915290825290205481565b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156102d057600080fd5b505afa1580156102e4573d6000803e3d6000fd5b505050506040513d60208110156102fa57600080fd5b5051905090565b6000546001600160a01b0316331461034a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b600180546001600160a01b0319166001600160a01b0383811691909117918290556040805163095ea7b360e01b815261dead600482015274446c3b15f9926687d2c40534fdb56400000000000060248201529051929091169163095ea7b3916044808201926020929091908290030181600087803b1580156103cb57600080fd5b505af11580156103df573d6000803e3d6000fd5b505050506040513d60208110156103f557600080fd5b505050565b6000546001600160a01b031681565b6001600160a01b03811633141561045d576040805162461bcd60e51b815260206004820152601360248201527221b0b73a103932b332b9103cb7bab939b2b63360691b604482015290519081900360640190fd5b336000908152600360205260409020546001600160a01b0316156104c0576040805162461bcd60e51b8152602060048201526015602482015274105b1c9958591e481899595b881c9959995c9c9959605a1b604482015290519081900360640190fd5b6001600160a01b038181166000908152600360205260409020541633141561052f576040805162461bcd60e51b815260206004820152601760248201527f4e6f207265636970726f63616c20726566657272616c73000000000000000000604482015290519081900360640190fd5b6001600160a01b038181166000908152600360205260408082205483168252902054163314156105a6576040805162461bcd60e51b815260206004820152601c60248201527f4e6f20332075736572206379636c6963616c20726566657272616c7300000000604482015290519081900360640190fd5b3360008181526003602052604080822080546001600160a01b0319166001600160a01b038616908117909155905190917f94db3a38c79f5f03e505529fe183e9fe2722c11f319bfea92ee2311f9886cdd691a350565b60025460009081526004602090815260408083206001600160a01b03949094168352929052205490565b6000546001600160a01b0316331461066f5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156106ba57600080fd5b505afa1580156106ce573d6000803e3d6000fd5b505050506040513d60208110156106e457600080fd5b505190508015610771576001546040805163a9059cbb60e01b815261dead60048201526024810184905290516001600160a01b039092169163a9059cbb916044808201926020929091908290030181600087803b15801561074457600080fd5b505af1158015610758573d6000803e3d6000fd5b505050506040513d602081101561076e57600080fd5b50505b60028054600181019091556040805183815290516001600160a01b038516917ffebab261bd91a9169258bd079eaf1923da01a656b9ab4a5f1aa2837046a9eefe919081900360200190a35050565b6001546001600160a01b031681565b6003602052600090815260409020546001600160a01b031681565b60025490565b60056020526000908152604090205481565b6000546001600160a01b0316331461084a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b6001600160a01b038281166000908152600360205260409020541661086e576108ef565b600061087b826064610abb565b6002805460009081526004602081815260408084206001600160a01b03808b168087526003855283872080548316885292855283872080548a019055965486529383528185208686528352818520805488019055549092168352600590528082208054850190559181522080549091019055505b5050565b6002546000908152600460209081526040808320338452909152902054610961576040805162461bcd60e51b815260206004820152601d60248201527f4e6f20726566657272616c207265776172647320746f2072656465656d000000604482015290519081900360640190fd5b60025460009081526004602081815260408084203385528252928390205460015484516370a0823160e01b81523094810194909452935190936001600160a01b0316926370a08231926024808301939192829003018186803b1580156109c657600080fd5b505afa1580156109da573d6000803e3d6000fd5b505050506040513d60208110156109f057600080fd5b5051811115610a5a57600080546040805163dc3a34b760e01b81526004810185905290516001600160a01b039092169263dc3a34b79260248084019382900301818387803b158015610a4157600080fd5b505af1158015610a55573d6000803e3d6000fd5b505050505b610a643382610b22565b60025460009081526004602090815260408083203380855290835281842093909355805184815290517ff81ba9609c6a894b93fc8521c7a1ea62c912609b7e1b5e4a7320998ba4797c32929181900390910190a250565b6000808211610b11576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381610b1a57fe5b049392505050565b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015610b6d57600080fd5b505afa158015610b81573d6000803e3d6000fd5b505050506040513d6020811015610b9757600080fd5b50519050600081831115610c2e576001546040805163a9059cbb60e01b81526001600160a01b038781166004830152602482018690529151919092169163a9059cbb9160448083019260209291908290030181600087803b158015610bfb57600080fd5b505af1158015610c0f573d6000803e3d6000fd5b505050506040513d6020811015610c2557600080fd5b50519050610cb3565b6001546040805163a9059cbb60e01b81526001600160a01b038781166004830152602482018790529151919092169163a9059cbb9160448083019260209291908290030181600087803b158015610c8457600080fd5b505af1158015610c98573d6000803e3d6000fd5b505050506040513d6020811015610cae57600080fd5b505190505b80610d05576040805162461bcd60e51b815260206004820152601a60248201527f5361666553756d6d69745472616e736665723a206661696c6564000000000000604482015290519081900360640190fd5b5050505056fe4f6e6c7920436172746f677261706865722063616e2063616c6c2066756e6374696f6ea264697066735822122067a801a90fb55754bb4c4eeb126d5a7db0cc8bd6539b8ed4adb9ed4410834e2164736f6c634300060c003300000000000000000000000046d303b6829adc7ac3217d92f71b1dbbe77ebba2

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c80637dffc3141161008c578063d8dcef3911610066578063d8dcef3914610206578063d9b8f6021461020e578063dcd33e8e14610234578063f958b61a14610260576100cf565b80637dffc314146101b25780639fbcb9cb146101d8578063d21cacdf146101e0576100cf565b806307bb27aa146100d45780630d42eb4b146101125780635bfa1b681461011a578063647aba30146101425780636d473295146101665780636e8ba9f61461018c575b600080fd5b610100600480360360408110156100ea57600080fd5b50803590602001356001600160a01b0316610268565b60408051918252519081900360200190f35b610100610285565b6101406004803603602081101561013057600080fd5b50356001600160a01b0316610301565b005b61014a6103fa565b604080516001600160a01b039092168252519081900360200190f35b6101406004803603602081101561017c57600080fd5b50356001600160a01b0316610409565b610100600480360360208110156101a257600080fd5b50356001600160a01b03166105fc565b610140600480360360208110156101c857600080fd5b50356001600160a01b0316610626565b61014a6107bf565b61014a600480360360208110156101f657600080fd5b50356001600160a01b03166107ce565b6101006107e9565b6101006004803603602081101561022457600080fd5b50356001600160a01b03166107ef565b6101406004803603604081101561024a57600080fd5b506001600160a01b038135169060200135610801565b6101406108f3565b600460209081526000928352604080842090915290825290205481565b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156102d057600080fd5b505afa1580156102e4573d6000803e3d6000fd5b505050506040513d60208110156102fa57600080fd5b5051905090565b6000546001600160a01b0316331461034a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b600180546001600160a01b0319166001600160a01b0383811691909117918290556040805163095ea7b360e01b815261dead600482015274446c3b15f9926687d2c40534fdb56400000000000060248201529051929091169163095ea7b3916044808201926020929091908290030181600087803b1580156103cb57600080fd5b505af11580156103df573d6000803e3d6000fd5b505050506040513d60208110156103f557600080fd5b505050565b6000546001600160a01b031681565b6001600160a01b03811633141561045d576040805162461bcd60e51b815260206004820152601360248201527221b0b73a103932b332b9103cb7bab939b2b63360691b604482015290519081900360640190fd5b336000908152600360205260409020546001600160a01b0316156104c0576040805162461bcd60e51b8152602060048201526015602482015274105b1c9958591e481899595b881c9959995c9c9959605a1b604482015290519081900360640190fd5b6001600160a01b038181166000908152600360205260409020541633141561052f576040805162461bcd60e51b815260206004820152601760248201527f4e6f207265636970726f63616c20726566657272616c73000000000000000000604482015290519081900360640190fd5b6001600160a01b038181166000908152600360205260408082205483168252902054163314156105a6576040805162461bcd60e51b815260206004820152601c60248201527f4e6f20332075736572206379636c6963616c20726566657272616c7300000000604482015290519081900360640190fd5b3360008181526003602052604080822080546001600160a01b0319166001600160a01b038616908117909155905190917f94db3a38c79f5f03e505529fe183e9fe2722c11f319bfea92ee2311f9886cdd691a350565b60025460009081526004602090815260408083206001600160a01b03949094168352929052205490565b6000546001600160a01b0316331461066f5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156106ba57600080fd5b505afa1580156106ce573d6000803e3d6000fd5b505050506040513d60208110156106e457600080fd5b505190508015610771576001546040805163a9059cbb60e01b815261dead60048201526024810184905290516001600160a01b039092169163a9059cbb916044808201926020929091908290030181600087803b15801561074457600080fd5b505af1158015610758573d6000803e3d6000fd5b505050506040513d602081101561076e57600080fd5b50505b60028054600181019091556040805183815290516001600160a01b038516917ffebab261bd91a9169258bd079eaf1923da01a656b9ab4a5f1aa2837046a9eefe919081900360200190a35050565b6001546001600160a01b031681565b6003602052600090815260409020546001600160a01b031681565b60025490565b60056020526000908152604090205481565b6000546001600160a01b0316331461084a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610d0c6023913960400191505060405180910390fd5b6001600160a01b038281166000908152600360205260409020541661086e576108ef565b600061087b826064610abb565b6002805460009081526004602081815260408084206001600160a01b03808b168087526003855283872080548316885292855283872080548a019055965486529383528185208686528352818520805488019055549092168352600590528082208054850190559181522080549091019055505b5050565b6002546000908152600460209081526040808320338452909152902054610961576040805162461bcd60e51b815260206004820152601d60248201527f4e6f20726566657272616c207265776172647320746f2072656465656d000000604482015290519081900360640190fd5b60025460009081526004602081815260408084203385528252928390205460015484516370a0823160e01b81523094810194909452935190936001600160a01b0316926370a08231926024808301939192829003018186803b1580156109c657600080fd5b505afa1580156109da573d6000803e3d6000fd5b505050506040513d60208110156109f057600080fd5b5051811115610a5a57600080546040805163dc3a34b760e01b81526004810185905290516001600160a01b039092169263dc3a34b79260248084019382900301818387803b158015610a4157600080fd5b505af1158015610a55573d6000803e3d6000fd5b505050505b610a643382610b22565b60025460009081526004602090815260408083203380855290835281842093909355805184815290517ff81ba9609c6a894b93fc8521c7a1ea62c912609b7e1b5e4a7320998ba4797c32929181900390910190a250565b6000808211610b11576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381610b1a57fe5b049392505050565b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015610b6d57600080fd5b505afa158015610b81573d6000803e3d6000fd5b505050506040513d6020811015610b9757600080fd5b50519050600081831115610c2e576001546040805163a9059cbb60e01b81526001600160a01b038781166004830152602482018690529151919092169163a9059cbb9160448083019260209291908290030181600087803b158015610bfb57600080fd5b505af1158015610c0f573d6000803e3d6000fd5b505050506040513d6020811015610c2557600080fd5b50519050610cb3565b6001546040805163a9059cbb60e01b81526001600160a01b038781166004830152602482018790529151919092169163a9059cbb9160448083019260209291908290030181600087803b158015610c8457600080fd5b505af1158015610c98573d6000803e3d6000fd5b505050506040513d6020811015610cae57600080fd5b505190505b80610d05576040805162461bcd60e51b815260206004820152601a60248201527f5361666553756d6d69745472616e736665723a206661696c6564000000000000604482015290519081900360640190fd5b5050505056fe4f6e6c7920436172746f677261706865722063616e2063616c6c2066756e6374696f6ea264697066735822122067a801a90fb55754bb4c4eeb126d5a7db0cc8bd6539b8ed4adb9ed4410834e2164736f6c634300060c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000046d303b6829adc7ac3217d92f71b1dbbe77ebba2

-----Decoded View---------------
Arg [0] : _cartographer (address): 0x46d303b6829aDc7AC3217D92f71B1DbbE77eBBA2

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 00000000000000000000000046d303b6829adc7ac3217d92f71b1dbbe77ebba2


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

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