ETH Price: $1,903.77 (-0.40%)

Transaction Decoder

Block:
18206865 at Sep-24-2023 04:32:11 PM +UTC
Transaction Fee:
0.00057159677674584 ETH $1.09
Gas Used:
84,930 Gas / 6.730210488 Gwei

Emitted Events:

Account State Difference:

  Address   Before After State Difference Code
1.843103194322782718 Eth1.843111687322782718 Eth0.000008493
0x99D8a9C4...Dff1e17F3
0xaEfEF1DA...c9D642A17
0.019540321668182238 Eth
Nonce: 353
0.018968724891436398 Eth
Nonce: 354
0.00057159677674584
0xbD2fBaf2...1165E6797

Execution Trace

mSpellStaking.withdraw( _amount=0 )
  • MagicInternetMoneyV1.balanceOf( 0xbD2fBaf2dc95bD78Cf1cD3c5235B33D1165E6797 ) => ( 142777388005744610354183 )
  • Spell.balanceOf( 0xbD2fBaf2dc95bD78Cf1cD3c5235B33D1165E6797 ) => ( 8368878340449622420662970458 )
  • MagicInternetMoneyV1.balanceOf( 0xbD2fBaf2dc95bD78Cf1cD3c5235B33D1165E6797 ) => ( 142777388005744610354183 )
  • MagicInternetMoneyV1.transfer( to=0xaEfEF1DAb8EC6005eEa3b804479Dd3Ac9D642A17, amount=9393598430884779133 ) => ( True )
  • Spell.transfer( to=0xaEfEF1DAb8EC6005eEa3b804479Dd3Ac9D642A17, amount=0 ) => ( True )
    File 1 of 3: mSpellStaking
    // SPDX-License-Identifier: MIT
    // Inspired by Stable Joe Staking which in turn is derived from the SushiSwap MasterChef contract
    pragma solidity 0.8.10;
    import "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
    import "../libraries/BoringOwnable.sol";
    /**
     * @title Magic Spell Staking
     * @author 0xMerlin
     */
    contract mSpellStaking is BoringOwnable {
        using SafeTransferLib for ERC20;
        /// @notice Info of each user
        struct UserInfo {
            uint128 amount;
            uint128 rewardDebt;
            uint128 lastAdded;
            /**
             * @notice We do some fancy math here. Basically, any point in time, the amount of JOEs
             * entitled to a user but is pending to be distributed is:
             *
             *   pending reward = (user.amount * accRewardPerShare) - user.rewardDebt[token]
             *
             * Whenever a user deposits or withdraws SPELL. Here's what happens:
             *   1. accRewardPerShare (and `lastRewardBalance`) gets updated
             *   2. User receives the pending reward sent to his/her address
             *   3. User's `amount` gets updated
             *   4. User's `rewardDebt[token]` gets updated
             */
        }
        ERC20 public immutable spell;
        /// @notice Array of tokens that users can claim
        ERC20 public immutable mim;
        /// @notice Last reward balance of `token`
        uint256 public lastRewardBalance;
        /// @notice amount of time that the position is locked for.
        uint256 private constant LOCK_TIME = 24 hours;
        bool public toggleLockup;
        /// @notice Accumulated `token` rewards per share, scaled to `ACC_REWARD_PER_SHARE_PRECISION`
        uint256 public accRewardPerShare;
        /// @notice The precision of `accRewardPerShare`
        uint256 public constant ACC_REWARD_PER_SHARE_PRECISION = 1e24;
        /// @dev Info of each user that stakes SPELL
        mapping(address => UserInfo) public userInfo;
        /// @notice Emitted when a user deposits SPELL
        event Deposit(address indexed user, uint256 amount);
        /// @notice Emitted when a user withdraws SPELL
        event Withdraw(address indexed user, uint256 amount);
        /// @notice Emitted when a user claims reward
        event ClaimReward(address indexed user, uint256 amount);
        /// @notice Emitted when a user emergency withdraws its SPELL
        event EmergencyWithdraw(address indexed user, uint256 amount);
        /**
         * @notice Initialize a new mSpellStaking contract
         * @dev This contract needs to receive an ERC20 `_rewardToken` in order to distribute them
         * (with MoneyMaker in our case)
         * @param _mim The address of the MIM token
         * @param _spell The address of the SPELL token
         */
        constructor(
            ERC20 _mim,
            ERC20 _spell
        ) {
            require(address(_mim) != address(0), "mSpellStaking: reward token can't be address(0)");
            require(address(_spell) != address(0), "mSpellStaking: spell can't be address(0)");
            spell = _spell;
            toggleLockup = true;
            mim = _mim;
        }
        /**
         * @notice Deposit SPELL for reward token allocation
         * @param _amount The amount of SPELL to deposit
         */
        function deposit(uint256 _amount) external {
            UserInfo storage user = userInfo[msg.sender];
            uint256 _previousAmount = user.amount;
            uint256 _newAmount = user.amount + _amount;
            user.amount = uint128(_newAmount);
            user.lastAdded = uint128(block.timestamp);
            updateReward();
            uint256 _previousRewardDebt = user.rewardDebt;
            user.rewardDebt = uint128(_newAmount * accRewardPerShare / ACC_REWARD_PER_SHARE_PRECISION);
            if (_previousAmount != 0) {
                uint256 _pending = _previousAmount * accRewardPerShare / ACC_REWARD_PER_SHARE_PRECISION - _previousRewardDebt;
                if (_pending != 0) {
                    safeTokenTransfer(mim, msg.sender, _pending);
                    emit ClaimReward(msg.sender, _pending);
                }
            }
            spell.safeTransferFrom(msg.sender, address(this), _amount);
            emit Deposit(msg.sender, _amount);
        }
        /**
         * @notice View function to see pending reward token on frontend
         * @param _user The address of the user
         * @return `_user`'s pending reward token
         */
        function pendingReward(address _user) external view returns (uint256) {
            UserInfo storage user = userInfo[_user];
            uint256 _totalSpell = spell.balanceOf(address(this));
            uint256 _accRewardTokenPerShare = accRewardPerShare;
            uint256 _rewardBalance = mim.balanceOf(address(this));
            if (_rewardBalance != lastRewardBalance && _totalSpell != 0) {
                uint256 _accruedReward = _rewardBalance - lastRewardBalance;
                _accRewardTokenPerShare = _accRewardTokenPerShare + _accruedReward * ACC_REWARD_PER_SHARE_PRECISION / _totalSpell;
            }
            return user.amount * _accRewardTokenPerShare / ACC_REWARD_PER_SHARE_PRECISION - user.rewardDebt;
        }
        /**
         * @notice Withdraw SPELL and harvest the rewards
         * @param _amount The amount of SPELL to withdraw
         */
        function withdraw(uint256 _amount) external {
            UserInfo storage user = userInfo[msg.sender];
            require(!toggleLockup || user.lastAdded + LOCK_TIME < block.timestamp, "mSpell: Wait for LockUp");
            uint256 _previousAmount = user.amount;
            uint256 _newAmount = user.amount - _amount;
            user.amount = uint128(_newAmount);
            updateReward();
            uint256 _pending = _previousAmount * accRewardPerShare / ACC_REWARD_PER_SHARE_PRECISION - user.rewardDebt;
            user.rewardDebt = uint128(_newAmount * accRewardPerShare / ACC_REWARD_PER_SHARE_PRECISION);
            if (_pending != 0) {
                safeTokenTransfer(mim, msg.sender, _pending);
                emit ClaimReward(msg.sender, _pending);
            }
            spell.safeTransfer(msg.sender, _amount);
            emit Withdraw(msg.sender, _amount);
        }
        /**
         * @notice Withdraw without caring about rewards. EMERGENCY ONLY
         */
        function emergencyWithdraw() external {
            UserInfo storage user = userInfo[msg.sender];
            require(!toggleLockup || user.lastAdded + LOCK_TIME < block.timestamp, "mSpell: Wait for LockUp");
            uint256 _amount = user.amount;
            user.amount = 0;
            user.rewardDebt = 0;
            spell.safeTransfer(msg.sender, _amount);
            emit EmergencyWithdraw(msg.sender, _amount);
        }
        /**
         * @notice Update reward variables
         * @dev Needs to be called before any deposit or withdrawal
         */
        function updateReward() public {
            uint256 _rewardBalance = mim.balanceOf(address(this));
            uint256 _totalSpell = spell.balanceOf(address(this));
            // Did mSpellStaking receive any token
            if (_rewardBalance == lastRewardBalance || _totalSpell == 0) {
                return;
            }
            uint256 _accruedReward = _rewardBalance - lastRewardBalance;
            accRewardPerShare = accRewardPerShare + _accruedReward * ACC_REWARD_PER_SHARE_PRECISION / _totalSpell;
            lastRewardBalance = _rewardBalance;
        }
        /**
         * @notice Safe token transfer function, just in case if rounding error
         * causes pool to not have enough reward tokens
         * @param _token The address of then token to transfer
         * @param _to The address that will receive `_amount` `rewardToken`
         * @param _amount The amount to send to `_to`
         */
        function safeTokenTransfer(
            ERC20 _token,
            address _to,
            uint256 _amount
        ) internal {
            uint256 _rewardBalance = _token.balanceOf(address(this));
            if (_amount > _rewardBalance) {
                lastRewardBalance = lastRewardBalance - _rewardBalance;
                _token.safeTransfer(_to, _rewardBalance);
            } else {
                lastRewardBalance = lastRewardBalance - _amount;
                _token.safeTransfer(_to, _amount);
            }
        }
        /**
         * @notice Allows to enable and disable the lockup
         * @param status The new lockup status
         */
         function toggleLockUp(bool status) external onlyOwner {
            toggleLockup = status;
         }
    }
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity >=0.8.0;
    import {ERC20} from "../tokens/ERC20.sol";
    /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
    /// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
    /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
    library SafeTransferLib {
        /*///////////////////////////////////////////////////////////////
                                ETH OPERATIONS
        //////////////////////////////////////////////////////////////*/
        function safeTransferETH(address to, uint256 amount) internal {
            bool callStatus;
            assembly {
                // Transfer the ETH and store if it succeeded or not.
                callStatus := call(gas(), to, amount, 0, 0, 0, 0)
            }
            require(callStatus, "ETH_TRANSFER_FAILED");
        }
        /*///////////////////////////////////////////////////////////////
                               ERC20 OPERATIONS
        //////////////////////////////////////////////////////////////*/
        function safeTransferFrom(
            ERC20 token,
            address from,
            address to,
            uint256 amount
        ) internal {
            bool callStatus;
            assembly {
                // Get a pointer to some free memory.
                let freeMemoryPointer := mload(0x40)
                // Write the abi-encoded calldata to memory piece by piece:
                mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
                mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument.
                mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
                mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
                // Call the token and store if it succeeded or not.
                // We use 100 because the calldata length is 4 + 32 * 3.
                callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)
            }
            require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FROM_FAILED");
        }
        function safeTransfer(
            ERC20 token,
            address to,
            uint256 amount
        ) internal {
            bool callStatus;
            assembly {
                // Get a pointer to some free memory.
                let freeMemoryPointer := mload(0x40)
                // Write the abi-encoded calldata to memory piece by piece:
                mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
                mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
                mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
                // Call the token and store if it succeeded or not.
                // We use 68 because the calldata length is 4 + 32 * 2.
                callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
            }
            require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FAILED");
        }
        function safeApprove(
            ERC20 token,
            address to,
            uint256 amount
        ) internal {
            bool callStatus;
            assembly {
                // Get a pointer to some free memory.
                let freeMemoryPointer := mload(0x40)
                // Write the abi-encoded calldata to memory piece by piece:
                mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
                mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
                mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
                // Call the token and store if it succeeded or not.
                // We use 68 because the calldata length is 4 + 32 * 2.
                callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
            }
            require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED");
        }
        /*///////////////////////////////////////////////////////////////
                             INTERNAL HELPER LOGIC
        //////////////////////////////////////////////////////////////*/
        function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) {
            assembly {
                // Get how many bytes the call returned.
                let returnDataSize := returndatasize()
                // If the call reverted:
                if iszero(callStatus) {
                    // Copy the revert message into memory.
                    returndatacopy(0, 0, returnDataSize)
                    // Revert with the same message.
                    revert(0, returnDataSize)
                }
                switch returnDataSize
                case 32 {
                    // Copy the return data into memory.
                    returndatacopy(0, 0, returnDataSize)
                    // Set success to whether it returned true.
                    success := iszero(iszero(mload(0)))
                }
                case 0 {
                    // There was no return data.
                    success := 1
                }
                default {
                    // It returned some malformed input.
                    success := 0
                }
            }
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    // Audit on 5-Jan-2021 by Keno and BoringCrypto
    // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
    // Edited by BoringCrypto
    contract BoringOwnableData {
        address public owner;
        address public pendingOwner;
    }
    contract BoringOwnable is BoringOwnableData {
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /// @notice `owner` defaults to msg.sender on construction.
        constructor() {
            owner = msg.sender;
            emit OwnershipTransferred(address(0), msg.sender);
        }
        /// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
        /// Can only be invoked by the current `owner`.
        /// @param newOwner Address of the new owner.
        /// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
        /// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
        function transferOwnership(
            address newOwner,
            bool direct,
            bool renounce
        ) public onlyOwner {
            if (direct) {
                // Checks
                require(newOwner != address(0) || renounce, "Ownable: zero address");
                // Effects
                emit OwnershipTransferred(owner, newOwner);
                owner = newOwner;
                pendingOwner = address(0);
            } else {
                // Effects
                pendingOwner = newOwner;
            }
        }
        /// @notice Needs to be called by `pendingOwner` to claim ownership.
        function claimOwnership() public {
            address _pendingOwner = pendingOwner;
            // Checks
            require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");
            // Effects
            emit OwnershipTransferred(owner, _pendingOwner);
            owner = _pendingOwner;
            pendingOwner = address(0);
        }
        /// @notice Only allows the `owner` to execute the function.
        modifier onlyOwner() {
            require(msg.sender == owner, "Ownable: caller is not the owner");
            _;
        }
    }// SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity >=0.8.0;
    /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
    /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
    abstract contract ERC20 {
        /*///////////////////////////////////////////////////////////////
                                      EVENTS
        //////////////////////////////////////////////////////////////*/
        event Transfer(address indexed from, address indexed to, uint256 amount);
        event Approval(address indexed owner, address indexed spender, uint256 amount);
        /*///////////////////////////////////////////////////////////////
                                 METADATA STORAGE
        //////////////////////////////////////////////////////////////*/
        string public name;
        string public symbol;
        uint8 public immutable decimals;
        /*///////////////////////////////////////////////////////////////
                                  ERC20 STORAGE
        //////////////////////////////////////////////////////////////*/
        uint256 public totalSupply;
        mapping(address => uint256) public balanceOf;
        mapping(address => mapping(address => uint256)) public allowance;
        /*///////////////////////////////////////////////////////////////
                               EIP-2612 STORAGE
        //////////////////////////////////////////////////////////////*/
        bytes32 public constant PERMIT_TYPEHASH =
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        uint256 internal immutable INITIAL_CHAIN_ID;
        bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
        mapping(address => uint256) public nonces;
        /*///////////////////////////////////////////////////////////////
                                   CONSTRUCTOR
        //////////////////////////////////////////////////////////////*/
        constructor(
            string memory _name,
            string memory _symbol,
            uint8 _decimals
        ) {
            name = _name;
            symbol = _symbol;
            decimals = _decimals;
            INITIAL_CHAIN_ID = block.chainid;
            INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
        }
        /*///////////////////////////////////////////////////////////////
                                  ERC20 LOGIC
        //////////////////////////////////////////////////////////////*/
        function approve(address spender, uint256 amount) public virtual returns (bool) {
            allowance[msg.sender][spender] = amount;
            emit Approval(msg.sender, spender, amount);
            return true;
        }
        function transfer(address to, uint256 amount) public virtual returns (bool) {
            balanceOf[msg.sender] -= amount;
            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }
            emit Transfer(msg.sender, to, amount);
            return true;
        }
        function transferFrom(
            address from,
            address to,
            uint256 amount
        ) public virtual returns (bool) {
            if (allowance[from][msg.sender] != type(uint256).max) {
                allowance[from][msg.sender] -= amount;
            }
            balanceOf[from] -= amount;
            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }
            emit Transfer(from, to, amount);
            return true;
        }
        /*///////////////////////////////////////////////////////////////
                                  EIP-2612 LOGIC
        //////////////////////////////////////////////////////////////*/
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) public virtual {
            require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
            // Unchecked because the only math done is incrementing
            // the owner's nonce which cannot realistically overflow.
            unchecked {
                bytes32 digest = keccak256(
                    abi.encodePacked(
                        "\\x19\\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                    )
                );
                address recoveredAddress = ecrecover(digest, v, r, s);
                require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_PERMIT_SIGNATURE");
                allowance[recoveredAddress][spender] = value;
            }
            emit Approval(owner, spender, value);
        }
        function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
            return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
        }
        function computeDomainSeparator() internal view virtual returns (bytes32) {
            return
                keccak256(
                    abi.encode(
                        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                        keccak256(bytes(name)),
                        keccak256(bytes("1")),
                        block.chainid,
                        address(this)
                    )
                );
        }
        /*///////////////////////////////////////////////////////////////
                           INTERNAL MINT/BURN LOGIC
        //////////////////////////////////////////////////////////////*/
        function _mint(address to, uint256 amount) internal virtual {
            totalSupply += amount;
            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }
            emit Transfer(address(0), to, amount);
        }
        function _burn(address from, uint256 amount) internal virtual {
            balanceOf[from] -= amount;
            // Cannot underflow because a user's balance
            // will never be larger than the total supply.
            unchecked {
                totalSupply -= amount;
            }
            emit Transfer(from, address(0), amount);
        }
    }
    

    File 2 of 3: MagicInternetMoneyV1
    // SPDX-License-Identifier: MIXED
    
    // File @boringcrypto/boring-solidity/contracts/libraries/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    /// @notice A library for performing overflow-/underflow-safe math,
    /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math).
    library BoringMath {
        function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require((c = a + b) >= b, "BoringMath: Add Overflow");
        }
    
        function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require((c = a - b) <= a, "BoringMath: Underflow");
        }
    
        function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow");
        }
    
        function to128(uint256 a) internal pure returns (uint128 c) {
            require(a <= uint128(-1), "BoringMath: uint128 Overflow");
            c = uint128(a);
        }
    
        function to64(uint256 a) internal pure returns (uint64 c) {
            require(a <= uint64(-1), "BoringMath: uint64 Overflow");
            c = uint64(a);
        }
    
        function to32(uint256 a) internal pure returns (uint32 c) {
            require(a <= uint32(-1), "BoringMath: uint32 Overflow");
            c = uint32(a);
        }
    }
    
    /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint128.
    library BoringMath128 {
        function add(uint128 a, uint128 b) internal pure returns (uint128 c) {
            require((c = a + b) >= b, "BoringMath: Add Overflow");
        }
    
        function sub(uint128 a, uint128 b) internal pure returns (uint128 c) {
            require((c = a - b) <= a, "BoringMath: Underflow");
        }
    }
    
    /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint64.
    library BoringMath64 {
        function add(uint64 a, uint64 b) internal pure returns (uint64 c) {
            require((c = a + b) >= b, "BoringMath: Add Overflow");
        }
    
        function sub(uint64 a, uint64 b) internal pure returns (uint64 c) {
            require((c = a - b) <= a, "BoringMath: Underflow");
        }
    }
    
    /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint32.
    library BoringMath32 {
        function add(uint32 a, uint32 b) internal pure returns (uint32 c) {
            require((c = a + b) >= b, "BoringMath: Add Overflow");
        }
    
        function sub(uint32 a, uint32 b) internal pure returns (uint32 c) {
            require((c = a - b) <= a, "BoringMath: Underflow");
        }
    }
    
    // File @boringcrypto/boring-solidity/contracts/interfaces/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    interface IERC20 {
        function totalSupply() external view returns (uint256);
    
        function balanceOf(address account) external view returns (uint256);
    
        function allowance(address owner, address spender) external view returns (uint256);
    
        function approve(address spender, uint256 amount) external returns (bool);
    
        event Transfer(address indexed from, address indexed to, uint256 value);
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /// @notice EIP 2612
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    }
    
    // File @boringcrypto/boring-solidity/contracts/[email protected]
    // License-Identifier: MIT
    // Based on code and smartness by Ross Campbell and Keno
    // Uses immutable to store the domain separator to reduce gas usage
    // If the chain id changes due to a fork, the forked chain will calculate on the fly.
    pragma solidity 0.6.12;
    
    // solhint-disable no-inline-assembly
    
    contract Domain {
        bytes32 private constant DOMAIN_SEPARATOR_SIGNATURE_HASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
        // See https://eips.ethereum.org/EIPS/eip-191
        string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = "\x19\x01";
    
        // solhint-disable var-name-mixedcase
        bytes32 private immutable _DOMAIN_SEPARATOR;
        uint256 private immutable DOMAIN_SEPARATOR_CHAIN_ID;
    
        /// @dev Calculate the DOMAIN_SEPARATOR
        function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
            return keccak256(abi.encode(DOMAIN_SEPARATOR_SIGNATURE_HASH, chainId, address(this)));
        }
    
        constructor() public {
            uint256 chainId;
            assembly {
                chainId := chainid()
            }
            _DOMAIN_SEPARATOR = _calculateDomainSeparator(DOMAIN_SEPARATOR_CHAIN_ID = chainId);
        }
    
        /// @dev Return the DOMAIN_SEPARATOR
        // It's named internal to allow making it public from the contract that uses it by creating a simple view function
        // with the desired public name, such as DOMAIN_SEPARATOR or domainSeparator.
        // solhint-disable-next-line func-name-mixedcase
        function _domainSeparator() internal view returns (bytes32) {
            uint256 chainId;
            assembly {
                chainId := chainid()
            }
            return chainId == DOMAIN_SEPARATOR_CHAIN_ID ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(chainId);
        }
    
        function _getDigest(bytes32 dataHash) internal view returns (bytes32 digest) {
            digest = keccak256(abi.encodePacked(EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA, _domainSeparator(), dataHash));
        }
    }
    
    // File @boringcrypto/boring-solidity/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    // solhint-disable no-inline-assembly
    // solhint-disable not-rely-on-time
    
    // Data part taken out for building of contracts that receive delegate calls
    contract ERC20Data {
        /// @notice owner > balance mapping.
        mapping(address => uint256) public balanceOf;
        /// @notice owner > spender > allowance mapping.
        mapping(address => mapping(address => uint256)) public allowance;
        /// @notice owner > nonce mapping. Used in `permit`.
        mapping(address => uint256) public nonces;
    }
    
    abstract contract ERC20 is IERC20, Domain {
        /// @notice owner > balance mapping.
        mapping(address => uint256) public override balanceOf;
        /// @notice owner > spender > allowance mapping.
        mapping(address => mapping(address => uint256)) public override allowance;
        /// @notice owner > nonce mapping. Used in `permit`.
        mapping(address => uint256) public nonces;
    
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
    
        /// @notice Transfers `amount` tokens from `msg.sender` to `to`.
        /// @param to The address to move the tokens.
        /// @param amount of the tokens to move.
        /// @return (bool) Returns True if succeeded.
        function transfer(address to, uint256 amount) public returns (bool) {
            // If `amount` is 0, or `msg.sender` is `to` nothing happens
            if (amount != 0 || msg.sender == to) {
                uint256 srcBalance = balanceOf[msg.sender];
                require(srcBalance >= amount, "ERC20: balance too low");
                if (msg.sender != to) {
                    require(to != address(0), "ERC20: no zero address"); // Moved down so low balance calls safe some gas
    
                    balanceOf[msg.sender] = srcBalance - amount; // Underflow is checked
                    balanceOf[to] += amount;
                }
            }
            emit Transfer(msg.sender, to, amount);
            return true;
        }
    
        /// @notice Transfers `amount` tokens from `from` to `to`. Caller needs approval for `from`.
        /// @param from Address to draw tokens from.
        /// @param to The address to move the tokens.
        /// @param amount The token amount to move.
        /// @return (bool) Returns True if succeeded.
        function transferFrom(
            address from,
            address to,
            uint256 amount
        ) public returns (bool) {
            // If `amount` is 0, or `from` is `to` nothing happens
            if (amount != 0) {
                uint256 srcBalance = balanceOf[from];
                require(srcBalance >= amount, "ERC20: balance too low");
    
                if (from != to) {
                    uint256 spenderAllowance = allowance[from][msg.sender];
                    // If allowance is infinite, don't decrease it to save on gas (breaks with EIP-20).
                    if (spenderAllowance != type(uint256).max) {
                        require(spenderAllowance >= amount, "ERC20: allowance too low");
                        allowance[from][msg.sender] = spenderAllowance - amount; // Underflow is checked
                    }
                    require(to != address(0), "ERC20: no zero address"); // Moved down so other failed calls safe some gas
    
                    balanceOf[from] = srcBalance - amount; // Underflow is checked
                    balanceOf[to] += amount;
                }
            }
            emit Transfer(from, to, amount);
            return true;
        }
    
        /// @notice Approves `amount` from sender to be spend by `spender`.
        /// @param spender Address of the party that can draw from msg.sender's account.
        /// @param amount The maximum collective amount that `spender` can draw.
        /// @return (bool) Returns True if approved.
        function approve(address spender, uint256 amount) public override returns (bool) {
            allowance[msg.sender][spender] = amount;
            emit Approval(msg.sender, spender, amount);
            return true;
        }
    
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32) {
            return _domainSeparator();
        }
    
        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        bytes32 private constant PERMIT_SIGNATURE_HASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    
        /// @notice Approves `value` from `owner_` to be spend by `spender`.
        /// @param owner_ Address of the owner.
        /// @param spender The address of the spender that gets approved to draw from `owner_`.
        /// @param value The maximum collective amount that `spender` can draw.
        /// @param deadline This permit must be redeemed before this deadline (UTC timestamp in seconds).
        function permit(
            address owner_,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external override {
            require(owner_ != address(0), "ERC20: Owner cannot be 0");
            require(block.timestamp < deadline, "ERC20: Expired");
            require(
                ecrecover(_getDigest(keccak256(abi.encode(PERMIT_SIGNATURE_HASH, owner_, spender, value, nonces[owner_]++, deadline))), v, r, s) ==
                    owner_,
                "ERC20: Invalid Signature"
            );
            allowance[owner_][spender] = value;
            emit Approval(owner_, spender, value);
        }
    }
    
    contract ERC20WithSupply is IERC20, ERC20 {
        uint256 public override totalSupply;
    
        function _mint(address user, uint256 amount) private {
            uint256 newTotalSupply = totalSupply + amount;
            require(newTotalSupply >= totalSupply, "Mint overflow");
            totalSupply = newTotalSupply;
            balanceOf[user] += amount;
        }
    
        function _burn(address user, uint256 amount) private {
            require(balanceOf[user] >= amount, "Burn too much");
            totalSupply -= amount;
            balanceOf[user] -= amount;
        }
    }
    
    // File @boringcrypto/boring-solidity/contracts/libraries/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    struct Rebase {
        uint128 elastic;
        uint128 base;
    }
    
    /// @notice A rebasing library using overflow-/underflow-safe math.
    library RebaseLibrary {
        using BoringMath for uint256;
        using BoringMath128 for uint128;
    
        /// @notice Calculates the base value in relationship to `elastic` and `total`.
        function toBase(
            Rebase memory total,
            uint256 elastic,
            bool roundUp
        ) internal pure returns (uint256 base) {
            if (total.elastic == 0) {
                base = elastic;
            } else {
                base = elastic.mul(total.base) / total.elastic;
                if (roundUp && base.mul(total.elastic) / total.base < elastic) {
                    base = base.add(1);
                }
            }
        }
    
        /// @notice Calculates the elastic value in relationship to `base` and `total`.
        function toElastic(
            Rebase memory total,
            uint256 base,
            bool roundUp
        ) internal pure returns (uint256 elastic) {
            if (total.base == 0) {
                elastic = base;
            } else {
                elastic = base.mul(total.elastic) / total.base;
                if (roundUp && elastic.mul(total.base) / total.elastic < base) {
                    elastic = elastic.add(1);
                }
            }
        }
    
        /// @notice Add `elastic` to `total` and doubles `total.base`.
        /// @return (Rebase) The new total.
        /// @return base in relationship to `elastic`.
        function add(
            Rebase memory total,
            uint256 elastic,
            bool roundUp
        ) internal pure returns (Rebase memory, uint256 base) {
            base = toBase(total, elastic, roundUp);
            total.elastic = total.elastic.add(elastic.to128());
            total.base = total.base.add(base.to128());
            return (total, base);
        }
    
        /// @notice Sub `base` from `total` and update `total.elastic`.
        /// @return (Rebase) The new total.
        /// @return elastic in relationship to `base`.
        function sub(
            Rebase memory total,
            uint256 base,
            bool roundUp
        ) internal pure returns (Rebase memory, uint256 elastic) {
            elastic = toElastic(total, base, roundUp);
            total.elastic = total.elastic.sub(elastic.to128());
            total.base = total.base.sub(base.to128());
            return (total, elastic);
        }
    
        /// @notice Add `elastic` and `base` to `total`.
        function add(
            Rebase memory total,
            uint256 elastic,
            uint256 base
        ) internal pure returns (Rebase memory) {
            total.elastic = total.elastic.add(elastic.to128());
            total.base = total.base.add(base.to128());
            return total;
        }
    
        /// @notice Subtract `elastic` and `base` to `total`.
        function sub(
            Rebase memory total,
            uint256 elastic,
            uint256 base
        ) internal pure returns (Rebase memory) {
            total.elastic = total.elastic.sub(elastic.to128());
            total.base = total.base.sub(base.to128());
            return total;
        }
    
        /// @notice Add `elastic` to `total` and update storage.
        /// @return newElastic Returns updated `elastic`.
        function addElastic(Rebase storage total, uint256 elastic) internal returns (uint256 newElastic) {
            newElastic = total.elastic = total.elastic.add(elastic.to128());
        }
    
        /// @notice Subtract `elastic` from `total` and update storage.
        /// @return newElastic Returns updated `elastic`.
        function subElastic(Rebase storage total, uint256 elastic) internal returns (uint256 newElastic) {
            newElastic = total.elastic = total.elastic.sub(elastic.to128());
        }
    }
    
    // File @sushiswap/bentobox-sdk/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    interface IBatchFlashBorrower {
        function onBatchFlashLoan(
            address sender,
            IERC20[] calldata tokens,
            uint256[] calldata amounts,
            uint256[] calldata fees,
            bytes calldata data
        ) external;
    }
    
    // File @sushiswap/bentobox-sdk/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    interface IFlashBorrower {
        function onFlashLoan(
            address sender,
            IERC20 token,
            uint256 amount,
            uint256 fee,
            bytes calldata data
        ) external;
    }
    
    // File @sushiswap/bentobox-sdk/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    interface IStrategy {
        // Send the assets to the Strategy and call skim to invest them
        function skim(uint256 amount) external;
    
        // Harvest any profits made converted to the asset and pass them to the caller
        function harvest(uint256 balance, address sender) external returns (int256 amountAdded);
    
        // Withdraw assets. The returned amount can differ from the requested amount due to rounding.
        // The actualAmount should be very close to the amount. The difference should NOT be used to report a loss. That's what harvest is for.
        function withdraw(uint256 amount) external returns (uint256 actualAmount);
    
        // Withdraw all assets in the safest way possible. This shouldn't fail.
        function exit(uint256 balance) external returns (int256 amountAdded);
    }
    
    // File @sushiswap/bentobox-sdk/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    
    interface IBentoBoxV1 {
        event LogDeploy(address indexed masterContract, bytes data, address indexed cloneAddress);
        event LogDeposit(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 share);
        event LogFlashLoan(address indexed borrower, address indexed token, uint256 amount, uint256 feeAmount, address indexed receiver);
        event LogRegisterProtocol(address indexed protocol);
        event LogSetMasterContractApproval(address indexed masterContract, address indexed user, bool approved);
        event LogStrategyDivest(address indexed token, uint256 amount);
        event LogStrategyInvest(address indexed token, uint256 amount);
        event LogStrategyLoss(address indexed token, uint256 amount);
        event LogStrategyProfit(address indexed token, uint256 amount);
        event LogStrategyQueued(address indexed token, address indexed strategy);
        event LogStrategySet(address indexed token, address indexed strategy);
        event LogStrategyTargetPercentage(address indexed token, uint256 targetPercentage);
        event LogTransfer(address indexed token, address indexed from, address indexed to, uint256 share);
        event LogWhiteListMasterContract(address indexed masterContract, bool approved);
        event LogWithdraw(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 share);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        function balanceOf(IERC20, address) external view returns (uint256);
    
        function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results);
    
        function batchFlashLoan(
            IBatchFlashBorrower borrower,
            address[] calldata receivers,
            IERC20[] calldata tokens,
            uint256[] calldata amounts,
            bytes calldata data
        ) external;
    
        function claimOwnership() external;
    
        function deploy(
            address masterContract,
            bytes calldata data,
            bool useCreate2
        ) external payable;
    
        function deposit(
            IERC20 token_,
            address from,
            address to,
            uint256 amount,
            uint256 share
        ) external payable returns (uint256 amountOut, uint256 shareOut);
    
        function flashLoan(
            IFlashBorrower borrower,
            address receiver,
            IERC20 token,
            uint256 amount,
            bytes calldata data
        ) external;
    
        function harvest(
            IERC20 token,
            bool balance,
            uint256 maxChangeAmount
        ) external;
    
        function masterContractApproved(address, address) external view returns (bool);
    
        function masterContractOf(address) external view returns (address);
    
        function nonces(address) external view returns (uint256);
    
        function owner() external view returns (address);
    
        function pendingOwner() external view returns (address);
    
        function pendingStrategy(IERC20) external view returns (IStrategy);
    
        function permitToken(
            IERC20 token,
            address from,
            address to,
            uint256 amount,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    
        function registerProtocol() external;
    
        function setMasterContractApproval(
            address user,
            address masterContract,
            bool approved,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    
        function setStrategy(IERC20 token, IStrategy newStrategy) external;
    
        function setStrategyTargetPercentage(IERC20 token, uint64 targetPercentage_) external;
    
        function strategy(IERC20) external view returns (IStrategy);
    
        function strategyData(IERC20)
            external
            view
            returns (
                uint64 strategyStartDate,
                uint64 targetPercentage,
                uint128 balance
            );
    
        function toAmount(
            IERC20 token,
            uint256 share,
            bool roundUp
        ) external view returns (uint256 amount);
    
        function toShare(
            IERC20 token,
            uint256 amount,
            bool roundUp
        ) external view returns (uint256 share);
    
        function totals(IERC20) external view returns (Rebase memory totals_);
    
        function transfer(
            IERC20 token,
            address from,
            address to,
            uint256 share
        ) external;
    
        function transferMultiple(
            IERC20 token,
            address from,
            address[] calldata tos,
            uint256[] calldata shares
        ) external;
    
        function transferOwnership(
            address newOwner,
            bool direct,
            bool renounce
        ) external;
    
        function whitelistMasterContract(address masterContract, bool approved) external;
    
        function whitelistedMasterContracts(address) external view returns (bool);
    
        function withdraw(
            IERC20 token_,
            address from,
            address to,
            uint256 amount,
            uint256 share
        ) external returns (uint256 amountOut, uint256 shareOut);
    }
    
    // File @boringcrypto/boring-solidity/contracts/[email protected]
    // License-Identifier: MIT
    pragma solidity 0.6.12;
    
    // Audit on 5-Jan-2021 by Keno and BoringCrypto
    // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
    // Edited by BoringCrypto
    
    contract BoringOwnableData {
        address public owner;
        address public pendingOwner;
    }
    
    contract BoringOwnable is BoringOwnableData {
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /// @notice `owner` defaults to msg.sender on construction.
        constructor() public {
            owner = msg.sender;
            emit OwnershipTransferred(address(0), msg.sender);
        }
    
        /// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
        /// Can only be invoked by the current `owner`.
        /// @param newOwner Address of the new owner.
        /// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
        /// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
        function transferOwnership(
            address newOwner,
            bool direct,
            bool renounce
        ) public onlyOwner {
            if (direct) {
                // Checks
                require(newOwner != address(0) || renounce, "Ownable: zero address");
    
                // Effects
                emit OwnershipTransferred(owner, newOwner);
                owner = newOwner;
                pendingOwner = address(0);
            } else {
                // Effects
                pendingOwner = newOwner;
            }
        }
    
        /// @notice Needs to be called by `pendingOwner` to claim ownership.
        function claimOwnership() public {
            address _pendingOwner = pendingOwner;
    
            // Checks
            require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");
    
            // Effects
            emit OwnershipTransferred(owner, _pendingOwner);
            owner = _pendingOwner;
            pendingOwner = address(0);
        }
    
        /// @notice Only allows the `owner` to execute the function.
        modifier onlyOwner() {
            require(msg.sender == owner, "Ownable: caller is not the owner");
            _;
        }
    }
    
    // File contracts/MagicInternetMoney.sol
    // License-Identifier: MIT
    
    // Magic Internet Money
    
    // ███╗   ███╗██╗███╗   ███╗
    // ████╗ ████║██║████╗ ████║
    // ██╔████╔██║██║██╔████╔██║
    // ██║╚██╔╝██║██║██║╚██╔╝██║
    // ██║ ╚═╝ ██║██║██║ ╚═╝ ██║
    // ╚═╝     ╚═╝╚═╝╚═╝     ╚═╝
    
    // BoringCrypto, 0xMerlin
    
    pragma solidity 0.6.12;
    
    /// @title Cauldron
    /// @dev This contract allows contract calls to any contract (except BentoBox)
    /// from arbitrary callers thus, don't trust calls from this contract in any circumstances.
    contract MagicInternetMoneyV1 is ERC20, BoringOwnable {
        using BoringMath for uint256;
        // ERC20 'variables'
        string public constant symbol = "MIM";
        string public constant name = "Magic Internet Money";
        uint8 public constant decimals = 18;
        uint256 public override totalSupply;
    
        struct Minting {
            uint128 time;
            uint128 amount;
        }
    
        Minting public lastMint;
        uint256 private constant MINTING_PERIOD = 24 hours;
        uint256 private constant MINTING_INCREASE = 15000;
        uint256 private constant MINTING_PRECISION = 1e5;
    
        function mint(address to, uint256 amount) public onlyOwner {
            require(to != address(0), "MIM: no mint to zero address");
    
            // Limits the amount minted per period to a convergence function, with the period duration restarting on every mint
            uint256 totalMintedAmount = uint256(lastMint.time < block.timestamp - MINTING_PERIOD ? 0 : lastMint.amount).add(amount);
            require(totalSupply == 0 || totalSupply.mul(MINTING_INCREASE) / MINTING_PRECISION >= totalMintedAmount);
    
            lastMint.time = block.timestamp.to128();
            lastMint.amount = totalMintedAmount.to128();
    
            totalSupply = totalSupply + amount;
            balanceOf[to] += amount;
            emit Transfer(address(0), to, amount);
        }
    
        function mintToBentoBox(
            address clone,
            uint256 amount,
            IBentoBoxV1 bentoBox
        ) public onlyOwner {
            mint(address(bentoBox), amount);
            bentoBox.deposit(IERC20(address(this)), address(bentoBox), clone, amount, 0);
        }
    
        function burn(uint256 amount) public {
            require(amount <= balanceOf[msg.sender], "MIM: not enough");
    
            balanceOf[msg.sender] -= amount;
            totalSupply -= amount;
            emit Transfer(msg.sender, address(0), amount);
        }
    }

    File 3 of 3: Spell
    // SPDX-License-Identifier: MIT
    
    
    //   .d8888b.                    888 888 
    //  d88P  Y88b                   888 888 
    //  Y88b.                        888 888 
    //   "Y888b.   88888b.   .d88b.  888 888 
    //      "Y88b. 888 "88b d8P  Y8b 888 888 
    //        "888 888  888 88888888 888 888 
    //  Y88b  d88P 888 d88P Y8b.     888 888 
    //   "Y8888P"  88888P"   "Y8888  888 888 
    //             888                       
    //             888                       
    //             888                       
    
    // Special thanks to:
    // @BoringCrypto for his great libraries @ https://github.com/boringcrypto/BoringSolidity
    
    pragma solidity 0.6.12;
    
    // Contract: BoringOwnable
    // Audit on 5-Jan-2021 by Keno and BoringCrypto
    // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
    // Edited by BoringCrypto
    
    contract BoringOwnableData {
        address public owner;
        address public pendingOwner;
    }
    
    contract BoringOwnable is BoringOwnableData {
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /// @notice `owner` defaults to msg.sender on construction.
        constructor() public {
            owner = msg.sender;
            emit OwnershipTransferred(address(0), msg.sender);
        }
    
        /// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
        /// Can only be invoked by the current `owner`.
        /// @param newOwner Address of the new owner.
        /// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
        /// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
        function transferOwnership(
            address newOwner,
            bool direct,
            bool renounce
        ) public onlyOwner {
            if (direct) {
                // Checks
                require(newOwner != address(0) || renounce, "Ownable: zero address");
    
                // Effects
                emit OwnershipTransferred(owner, newOwner);
                owner = newOwner;
                pendingOwner = address(0);
            } else {
                // Effects
                pendingOwner = newOwner;
            }
        }
    
        /// @notice Needs to be called by `pendingOwner` to claim ownership.
        function claimOwnership() public {
            address _pendingOwner = pendingOwner;
    
            // Checks
            require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");
    
            // Effects
            emit OwnershipTransferred(owner, _pendingOwner);
            owner = _pendingOwner;
            pendingOwner = address(0);
        }
    
        /// @notice Only allows the `owner` to execute the function.
        modifier onlyOwner() {
            require(msg.sender == owner, "Ownable: caller is not the owner");
            _;
        }
    }
    
    contract Domain {
        bytes32 private constant DOMAIN_SEPARATOR_SIGNATURE_HASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
        // See https://eips.ethereum.org/EIPS/eip-191
        string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = "\x19\x01";
    
        // solhint-disable var-name-mixedcase
        bytes32 private immutable _DOMAIN_SEPARATOR;
        uint256 private immutable DOMAIN_SEPARATOR_CHAIN_ID;    
    
        /// @dev Calculate the DOMAIN_SEPARATOR
        function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
            return keccak256(
                abi.encode(
                    DOMAIN_SEPARATOR_SIGNATURE_HASH,
                    chainId,
                    address(this)
                )
            );
        }
    
        constructor() public {
            uint256 chainId; assembly {chainId := chainid()}
            _DOMAIN_SEPARATOR = _calculateDomainSeparator(DOMAIN_SEPARATOR_CHAIN_ID = chainId);
        }
    
        /// @dev Return the DOMAIN_SEPARATOR
        // It's named internal to allow making it public from the contract that uses it by creating a simple view function
        // with the desired public name, such as DOMAIN_SEPARATOR or domainSeparator.
        // solhint-disable-next-line func-name-mixedcase
        function _domainSeparator() internal view returns (bytes32) {
            uint256 chainId; assembly {chainId := chainid()}
            return chainId == DOMAIN_SEPARATOR_CHAIN_ID ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(chainId);
        }
    
        function _getDigest(bytes32 dataHash) internal view returns (bytes32 digest) {
            digest =
                keccak256(
                    abi.encodePacked(
                        EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA,
                        _domainSeparator(),
                        dataHash
                    )
                );
        }
    }
    
    interface IERC20 {
        function totalSupply() external view returns (uint256);
    
        function balanceOf(address account) external view returns (uint256);
    
        function allowance(address owner, address spender) external view returns (uint256);
    
        function approve(address spender, uint256 amount) external returns (bool);
    
        event Transfer(address indexed from, address indexed to, uint256 value);
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /// @notice EIP 2612
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    }
    
    // Data part taken out for building of contracts that receive delegate calls
    contract ERC20Data {
        /// @notice owner > balance mapping.
        mapping(address => uint256) public balanceOf;
        /// @notice owner > spender > allowance mapping.
        mapping(address => mapping(address => uint256)) public allowance;
        /// @notice owner > nonce mapping. Used in `permit`.
        mapping(address => uint256) public nonces;
    }
    
    abstract contract ERC20 is IERC20, Domain {
        /// @notice owner > balance mapping.
        mapping(address => uint256) public override balanceOf;
        /// @notice owner > spender > allowance mapping.
        mapping(address => mapping(address => uint256)) public override allowance;
        /// @notice owner > nonce mapping. Used in `permit`.
        mapping(address => uint256) public nonces;
        
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
    
        /// @notice Transfers `amount` tokens from `msg.sender` to `to`.
        /// @param to The address to move the tokens.
        /// @param amount of the tokens to move.
        /// @return (bool) Returns True if succeeded.
        function transfer(address to, uint256 amount) public returns (bool) {
            // If `amount` is 0, or `msg.sender` is `to` nothing happens
            if (amount != 0 || msg.sender == to) {
                uint256 srcBalance = balanceOf[msg.sender];
                require(srcBalance >= amount, "ERC20: balance too low");
                if (msg.sender != to) {
                    require(to != address(0), "ERC20: no zero address"); // Moved down so low balance calls safe some gas
    
                    balanceOf[msg.sender] = srcBalance - amount; // Underflow is checked
                    balanceOf[to] += amount;
                }
            }
            emit Transfer(msg.sender, to, amount);
            return true;
        }
    
        /// @notice Transfers `amount` tokens from `from` to `to`. Caller needs approval for `from`.
        /// @param from Address to draw tokens from.
        /// @param to The address to move the tokens.
        /// @param amount The token amount to move.
        /// @return (bool) Returns True if succeeded.
        function transferFrom(
            address from,
            address to,
            uint256 amount
        ) public returns (bool) {
            // If `amount` is 0, or `from` is `to` nothing happens
            if (amount != 0) {
                uint256 srcBalance = balanceOf[from];
                require(srcBalance >= amount, "ERC20: balance too low");
    
                if (from != to) {
                    uint256 spenderAllowance = allowance[from][msg.sender];
                    // If allowance is infinite, don't decrease it to save on gas (breaks with EIP-20).
                    if (spenderAllowance != type(uint256).max) {
                        require(spenderAllowance >= amount, "ERC20: allowance too low");
                        allowance[from][msg.sender] = spenderAllowance - amount; // Underflow is checked
                    }
                    require(to != address(0), "ERC20: no zero address"); // Moved down so other failed calls safe some gas
    
                    balanceOf[from] = srcBalance - amount; // Underflow is checked
                    balanceOf[to] += amount;
                }
            }
            emit Transfer(from, to, amount);
            return true;
        }
    
        /// @notice Approves `amount` from sender to be spend by `spender`.
        /// @param spender Address of the party that can draw from msg.sender's account.
        /// @param amount The maximum collective amount that `spender` can draw.
        /// @return (bool) Returns True if approved.
        function approve(address spender, uint256 amount) public override returns (bool) {
            allowance[msg.sender][spender] = amount;
            emit Approval(msg.sender, spender, amount);
            return true;
        }
    
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32) {
            return _domainSeparator();
        }
    
        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        bytes32 private constant PERMIT_SIGNATURE_HASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    
        /// @notice Approves `value` from `owner_` to be spend by `spender`.
        /// @param owner_ Address of the owner.
        /// @param spender The address of the spender that gets approved to draw from `owner_`.
        /// @param value The maximum collective amount that `spender` can draw.
        /// @param deadline This permit must be redeemed before this deadline (UTC timestamp in seconds).
        function permit(
            address owner_,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external override {
            require(owner_ != address(0), "ERC20: Owner cannot be 0");
            require(block.timestamp < deadline, "ERC20: Expired");
            require(
                ecrecover(_getDigest(keccak256(abi.encode(PERMIT_SIGNATURE_HASH, owner_, spender, value, nonces[owner_]++, deadline))), v, r, s) ==
                    owner_,
                "ERC20: Invalid Signature"
            );
            allowance[owner_][spender] = value;
            emit Approval(owner_, spender, value);
        }
    }
    
    // Contract: BoringMath
    /// @notice A library for performing overflow-/underflow-safe math,
    /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math).
    library BoringMath {
        function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require((c = a + b) >= b, "BoringMath: Add Overflow");
        }
    
        function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require((c = a - b) <= a, "BoringMath: Underflow");
        }
    
        function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
            require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow");
        }
    }
    
    /// @title Spell
    /// @author 0xMerlin
    /// @dev This contract spreads Magic.
    contract Spell is ERC20, BoringOwnable {
        using BoringMath for uint256;
        // ERC20 'variables'
        string public constant symbol = "SPELL";
        string public constant name = "Spell Token";
        uint8 public constant decimals = 18;
        uint256 public override totalSupply;
        uint256 public constant MAX_SUPPLY = 420 * 1e27;
    
        function mint(address to, uint256 amount) public onlyOwner {
            require(to != address(0), "SPELL: no mint to zero address");
            require(MAX_SUPPLY >= totalSupply.add(amount), "SPELL: Don't go over MAX");
    
            totalSupply = totalSupply + amount;
            balanceOf[to] += amount;
            emit Transfer(address(0), to, amount);
        }
    }