ETH Price: $1,976.65 (-5.10%)

Transaction Decoder

Block:
12367755 at May-04-2021 12:30:29 PM +UTC
Transaction Fee:
0.002254604 ETH $4.46
Gas Used:
51,241 Gas / 44 Gwei

Emitted Events:

386 OwnedUpgradeabilityProxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x000000000000000000000000c3a2735456c975f4a1694175d1f204a8de0fbf09, 0x0000000000000000000000008fd832757f58f71bac53196270a4a55c8e1a29d9, 0000000000000000000000000000000000000000000000efbe631dcea09a4bce )

Account State Difference:

  Address   Before After State Difference Code
(Crazy Pool)
49.152180203487037537 Eth49.154434807487037537 Eth0.002254604
0xa1e72267...E470e4149
0xC3A27354...8DE0Fbf09
0.387305386440826942 Eth
Nonce: 10
0.385050782440826942 Eth
Nonce: 11
0.002254604

Execution Trace

OwnedUpgradeabilityProxy.095ea7b3( )
  • TrueFiPool.approve( spender=0x8FD832757F58F71BAC53196270A4a55c8E1a29D9, amount=4422490675279778433998 ) => ( True )
    File 1 of 2: OwnedUpgradeabilityProxy
    /*                                                                           
        .'''''''''''..     ..''''''''''''''''..       ..'''''''''''''''..       
        .;;;;;;;;;;;'.   .';;;;;;;;;;;;;;;;;;,.     .,;;;;;;;;;;;;;;;;;,.       
        .;;;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;,.    .,;;;;;;;;;;;;;;;;;;,.       
        .;;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;;,.   .;;;;;;;;;;;;;;;;;;;;,.       
        ';;;;;;;;'.  .';;;;;;;;;;;;;;;;;;;;;;,. .';;;;;;;;;;;;;;;;;;;;;,.       
        ';;;;;,..   .';;;;;;;;;;;;;;;;;;;;;;;,..';;;;;;;;;;;;;;;;;;;;;;,.       
        ......     .';;;;;;;;;;;;;,'''''''''''.,;;;;;;;;;;;;;,'''''''''..       
                  .,;;;;;;;;;;;;;.           .,;;;;;;;;;;;;;.                   
                 .,;;;;;;;;;;;;,.           .,;;;;;;;;;;;;,.                    
                .,;;;;;;;;;;;;,.           .,;;;;;;;;;;;;,.                     
               .,;;;;;;;;;;;;,.           .;;;;;;;;;;;;;,.     .....            
              .;;;;;;;;;;;;;'.         ..';;;;;;;;;;;;;'.    .',;;;;,'.         
            .';;;;;;;;;;;;;'.         .';;;;;;;;;;;;;;'.   .';;;;;;;;;;.        
           .';;;;;;;;;;;;;'.         .';;;;;;;;;;;;;;'.    .;;;;;;;;;;;,.       
          .,;;;;;;;;;;;;;'...........,;;;;;;;;;;;;;;.      .;;;;;;;;;;;,.       
         .,;;;;;;;;;;;;,..,;;;;;;;;;;;;;;;;;;;;;;;,.       ..;;;;;;;;;,.        
        .,;;;;;;;;;;;;,. .,;;;;;;;;;;;;;;;;;;;;;;,.          .',;;;,,..         
       .,;;;;;;;;;;;;,.  .,;;;;;;;;;;;;;;;;;;;;;,.              ....            
        ..',;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;;,.                               
           ..',;;;;'.    .,;;;;;;;;;;;;;;;;;;;'.                                
              ...'..     .';;;;;;;;;;;;;;,,,'.                                  
                           ...............                                      
    */
    
    // https://github.com/trusttoken/smart-contracts
    // SPDX-License-Identifier: MIT
    
    // File: contracts/proxy/OwnedUpgradeabilityProxy.sol
    pragma solidity 0.6.10;
    
    /**
     * @title OwnedUpgradeabilityProxy
     * @dev This contract combines an upgradeability proxy with basic authorization control functionalities
     */
    contract OwnedUpgradeabilityProxy {
        /**
         * @dev Event to show ownership has been transferred
         * @param previousOwner representing the address of the previous owner
         * @param newOwner representing the address of the new owner
         */
        event ProxyOwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Event to show ownership transfer is pending
         * @param currentOwner representing the address of the current owner
         * @param pendingOwner representing the address of the pending owner
         */
        event NewPendingOwner(address currentOwner, address pendingOwner);
    
        // Storage position of the owner and pendingOwner of the contract
        bytes32 private constant proxyOwnerPosition = 0x6279e8199720cf3557ecd8b58d667c8edc486bd1cf3ad59ea9ebdfcae0d0dfac; //keccak256("trueUSD.proxy.owner");
        bytes32 private constant pendingProxyOwnerPosition = 0x8ddbac328deee8d986ec3a7b933a196f96986cb4ee030d86cc56431c728b83f4; //keccak256("trueUSD.pending.proxy.owner");
    
        /**
         * @dev the constructor sets the original owner of the contract to the sender account.
         */
        constructor() public {
            _setUpgradeabilityOwner(msg.sender);
        }
    
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyProxyOwner() {
            require(msg.sender == proxyOwner(), "only Proxy Owner");
            _;
        }
    
        /**
         * @dev Throws if called by any account other than the pending owner.
         */
        modifier onlyPendingProxyOwner() {
            require(msg.sender == pendingProxyOwner(), "only pending Proxy Owner");
            _;
        }
    
        /**
         * @dev Tells the address of the owner
         * @return owner the address of the owner
         */
        function proxyOwner() public view returns (address owner) {
            bytes32 position = proxyOwnerPosition;
            assembly {
                owner := sload(position)
            }
        }
    
        /**
         * @dev Tells the address of the owner
         * @return pendingOwner the address of the pending owner
         */
        function pendingProxyOwner() public view returns (address pendingOwner) {
            bytes32 position = pendingProxyOwnerPosition;
            assembly {
                pendingOwner := sload(position)
            }
        }
    
        /**
         * @dev Sets the address of the owner
         */
        function _setUpgradeabilityOwner(address newProxyOwner) internal {
            bytes32 position = proxyOwnerPosition;
            assembly {
                sstore(position, newProxyOwner)
            }
        }
    
        /**
         * @dev Sets the address of the owner
         */
        function _setPendingUpgradeabilityOwner(address newPendingProxyOwner) internal {
            bytes32 position = pendingProxyOwnerPosition;
            assembly {
                sstore(position, newPendingProxyOwner)
            }
        }
    
        /**
         * @dev Allows the current owner to transfer control of the contract to a newOwner.
         *changes the pending owner to newOwner. But doesn't actually transfer
         * @param newOwner The address to transfer ownership to.
         */
        function transferProxyOwnership(address newOwner) external onlyProxyOwner {
            require(newOwner != address(0));
            _setPendingUpgradeabilityOwner(newOwner);
            emit NewPendingOwner(proxyOwner(), newOwner);
        }
    
        /**
         * @dev Allows the pendingOwner to claim ownership of the proxy
         */
        function claimProxyOwnership() external onlyPendingProxyOwner {
            emit ProxyOwnershipTransferred(proxyOwner(), pendingProxyOwner());
            _setUpgradeabilityOwner(pendingProxyOwner());
            _setPendingUpgradeabilityOwner(address(0));
        }
    
        /**
         * @dev Allows the proxy owner to upgrade the current version of the proxy.
         * @param implementation representing the address of the new implementation to be set.
         */
        function upgradeTo(address implementation) public virtual onlyProxyOwner {
            address currentImplementation;
            bytes32 position = implementationPosition;
            assembly {
                currentImplementation := sload(position)
            }
            require(currentImplementation != implementation);
            assembly {
                sstore(position, implementation)
            }
            emit Upgraded(implementation);
        }
    
        /**
         * @dev This event will be emitted every time the implementation gets upgraded
         * @param implementation representing the address of the upgraded implementation
         */
        event Upgraded(address indexed implementation);
    
        // Storage position of the address of the current implementation
        bytes32 private constant implementationPosition = 0x6e41e0fbe643dfdb6043698bf865aada82dc46b953f754a3468eaa272a362dc7; //keccak256("trueUSD.proxy.implementation");
    
        function implementation() public view returns (address impl) {
            bytes32 position = implementationPosition;
            assembly {
                impl := sload(position)
            }
        }
    
        /**
         * @dev Fallback functions allowing to perform a delegatecall to the given implementation.
         * This function will return whatever the implementation call returns
         */
        fallback() external payable {
            proxyCall();
        }
    
        receive() external payable {
            proxyCall();
        }
    
        function proxyCall() internal {
            bytes32 position = implementationPosition;
    
            assembly {
                let ptr := mload(0x40)
                calldatacopy(ptr, returndatasize(), calldatasize())
                let result := delegatecall(gas(), sload(position), ptr, calldatasize(), returndatasize(), returndatasize())
                returndatacopy(ptr, 0, returndatasize())
    
                switch result
                    case 0 {
                        revert(ptr, returndatasize())
                    }
                    default {
                        return(ptr, returndatasize())
                    }
            }
        }
    }

    File 2 of 2: TrueFiPool
    /*
        .'''''''''''..     ..''''''''''''''''..       ..'''''''''''''''..
        .;;;;;;;;;;;'.   .';;;;;;;;;;;;;;;;;;,.     .,;;;;;;;;;;;;;;;;;,.
        .;;;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;,.    .,;;;;;;;;;;;;;;;;;;,.
        .;;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;;,.   .;;;;;;;;;;;;;;;;;;;;,.
        ';;;;;;;;'.  .';;;;;;;;;;;;;;;;;;;;;;,. .';;;;;;;;;;;;;;;;;;;;;,.
        ';;;;;,..   .';;;;;;;;;;;;;;;;;;;;;;;,..';;;;;;;;;;;;;;;;;;;;;;,.
        ......     .';;;;;;;;;;;;;,'''''''''''.,;;;;;;;;;;;;;,'''''''''..
                  .,;;;;;;;;;;;;;.           .,;;;;;;;;;;;;;.
                 .,;;;;;;;;;;;;,.           .,;;;;;;;;;;;;,.
                .,;;;;;;;;;;;;,.           .,;;;;;;;;;;;;,.
               .,;;;;;;;;;;;;,.           .;;;;;;;;;;;;;,.     .....
              .;;;;;;;;;;;;;'.         ..';;;;;;;;;;;;;'.    .',;;;;,'.
            .';;;;;;;;;;;;;'.         .';;;;;;;;;;;;;;'.   .';;;;;;;;;;.
           .';;;;;;;;;;;;;'.         .';;;;;;;;;;;;;;'.    .;;;;;;;;;;;,.
          .,;;;;;;;;;;;;;'...........,;;;;;;;;;;;;;;.      .;;;;;;;;;;;,.
         .,;;;;;;;;;;;;,..,;;;;;;;;;;;;;;;;;;;;;;;,.       ..;;;;;;;;;,.
        .,;;;;;;;;;;;;,. .,;;;;;;;;;;;;;;;;;;;;;;,.          .',;;;,,..
       .,;;;;;;;;;;;;,.  .,;;;;;;;;;;;;;;;;;;;;;,.              ....
        ..',;;;;;;;;,.   .,;;;;;;;;;;;;;;;;;;;;,.
           ..',;;;;'.    .,;;;;;;;;;;;;;;;;;;;'.
              ...'..     .';;;;;;;;;;;;;;,,,'.
                           ...............
    */
    
    // https://github.com/trusttoken/smart-contracts
    // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
    
    // SPDX-License-Identifier: MIT
    
    // pragma solidity ^0.6.0;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `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);
    }
    
    
    // Dependency file: @openzeppelin/contracts/utils/ReentrancyGuard.sol
    
    
    // pragma solidity ^0.6.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].
     */
    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;
        }
    }
    
    
    // Dependency file: @openzeppelin/contracts/math/SafeMath.sol
    
    
    // pragma solidity ^0.6.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, 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) {
            return sub(a, b, "SafeMath: subtraction overflow");
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * 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);
            uint256 c = a - b;
    
            return c;
        }
    
        /**
         * @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) {
            // 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 0;
            }
    
            uint256 c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
    
            return c;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts 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) {
            return div(a, b, "SafeMath: division by zero");
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
            require(b > 0, errorMessage);
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
            return c;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts 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) {
            return mod(a, b, "SafeMath: modulo by zero");
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    
    
    // Dependency file: @openzeppelin/contracts/utils/Address.sol
    
    
    // pragma solidity ^0.6.2;
    
    /**
     * @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 in 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");
            return _functionCallWithValue(target, data, value, errorMessage);
        }
    
        function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
            require(isContract(target), "Address: call to non-contract");
    
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
            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);
                }
            }
        }
    }
    
    
    // Dependency file: @openzeppelin/contracts/GSN/Context.sol
    
    
    // pragma solidity ^0.6.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;
        }
    }
    
    
    // Dependency file: contracts/truefi/common/Initializable.sol
    
    // Copied from https://github.com/OpenZeppelin/openzeppelin-contracts-ethereum-package/blob/v3.0.0/contracts/Initializable.sol
    
    // pragma solidity 0.6.10;
    
    /**
     * @title Initializable
     *
     * @dev Helper contract to support initializer functions. To use it, replace
     * the constructor with a function that has the `initializer` modifier.
     * WARNING: Unlike constructors, initializer functions must be manually
     * invoked. This applies both to deploying an Initializable contract, as well
     * as extending an Initializable contract via inheritance.
     * WARNING: When used with inheritance, manual care must be taken to not invoke
     * a parent initializer twice, or ensure that all initializers are idempotent,
     * because this is not dealt with automatically as with constructors.
     */
    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 use in the initializer function of a contract.
         */
        modifier initializer() {
            require(initializing || isConstructor() || !initialized, "Contract instance has already been 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) {
            // extcodesize checks the size of the code stored in an address, and
            // address returns the current address. Since the code is still not
            // deployed when running a constructor, any checks on its code size will
            // yield zero, making it an effective way to detect if a contract is
            // under construction or not.
            address self = address(this);
            uint256 cs;
            assembly {
                cs := extcodesize(self)
            }
            return cs == 0;
        }
    
        // Reserved storage space to allow for layout changes in the future.
        uint256[50] private ______gap;
    }
    
    
    // Dependency file: contracts/truefi/common/UpgradeableERC20.sol
    
    // pragma solidity 0.6.10;
    
    // import {Address} from "@openzeppelin/contracts/utils/Address.sol";
    // import {Context} from "@openzeppelin/contracts/GSN/Context.sol";
    // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    // import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
    
    // import {Initializable} from "contracts/truefi/common/Initializable.sol";
    
    /**
     * @dev Implementation of the {IERC20} 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 {ERC20PresetMinterPauser}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-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 ERC20 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 {IERC20-approve}.
     */
    contract ERC20 is Initializable, Context, IERC20 {
        using SafeMath for uint256;
        using Address for address;
    
        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.
         */
        function __ERC20_initialize(string memory name, string memory symbol) internal initializer {
            _name = name;
            _symbol = symbol;
            _decimals = 18;
        }
    
        /**
         * @dev Returns the name of the token.
         */
        function name() public view returns (string memory) {
            return _name;
        }
    
        /**
         * @dev Returns the symbol of the token, usually a shorter version of the
         * name.
         */
        function symbol() public view returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev Returns the number of decimals used to get its user representation.
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
         *
         * Tokens usually opt for a value of 18, imitating the relationship between
         * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
         * called.
         *
         * NOTE: This information is only used for _display_ purposes: it in
         * no way affects any of the arithmetic of the contract, including
         * {IERC20-balanceOf} and {IERC20-transfer}.
         */
        function decimals() public view returns (uint8) {
            return _decimals;
        }
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public override view returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public override view returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-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 virtual override returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public virtual override view returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20};
         *
         * 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 virtual override returns (bool) {
            _transfer(sender, recipient, amount);
            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: 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 {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public virtual 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 {IERC20-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 virtual returns (bool) {
            _approve(
                _msgSender(),
                spender,
                _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")
            );
            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 virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(sender, recipient, amount);
    
            _balances[sender] = _balances[sender].sub(amount, "ERC20: 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 virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply = _totalSupply.add(amount);
            _balances[account] = _balances[account].add(amount);
            emit Transfer(address(0), account, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
            _totalSupply = _totalSupply.sub(amount);
            emit Transfer(account, address(0), 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 virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Sets {decimals} to a value other than the default one of 18.
         *
         * WARNING: This function should only be called from the constructor. Most
         * applications that interact with token contracts will not expect
         * {decimals} to ever change, and may work incorrectly if it does.
         */
        function _setupDecimals(uint8 decimals_) internal {
            _decimals = decimals_;
        }
    
        /**
         * @dev Hook that is called before any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * will be to transferred to `to`.
         * - when `from` is zero, `amount` tokens will be minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 amount
        ) internal virtual {}
    
        function updateNameAndSymbol(string memory __name, string memory __symbol) internal {
            _name = __name;
            _symbol = __symbol;
        }
    }
    
    
    // Dependency file: contracts/truefi/common/UpgradeableOwnable.sol
    
    // pragma solidity 0.6.10;
    
    // import {Context} from "@openzeppelin/contracts/GSN/Context.sol";
    
    // import {Initializable} from "contracts/truefi/common/Initializable.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.
     */
    contract Ownable is Initializable, Context {
        address private _owner;
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        function initialize() internal initializer {
            address msgSender = _msgSender();
            _owner = msgSender;
            emit OwnershipTransferred(address(0), msgSender);
        }
    
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view 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;
        }
    }
    
    
    // Dependency file: contracts/truefi/interface/IYToken.sol
    
    // pragma solidity 0.6.10;
    
    // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    
    interface IYToken is IERC20 {
        function getPricePerFullShare() external view returns (uint256);
    }
    
    
    // Dependency file: contracts/truefi/interface/ICurve.sol
    
    // pragma solidity 0.6.10;
    
    // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    
    // import {IYToken} from "contracts/truefi/interface/IYToken.sol";
    
    interface ICurve {
        function calc_token_amount(uint256[4] memory amounts, bool deposit) external view returns (uint256);
    
        function get_virtual_price() external view returns (uint256);
    }
    
    interface ICurveGauge {
        function balanceOf(address depositor) external view returns (uint256);
    
        function minter() external returns (ICurveMinter);
    
        function deposit(uint256 amount) external;
    
        function withdraw(uint256 amount) external;
    }
    
    interface ICurveMinter {
        function mint(address gauge) external;
    
        function token() external view returns (IERC20);
    }
    
    interface ICurvePool {
        function add_liquidity(uint256[4] memory amounts, uint256 min_mint_amount) external;
    
        function remove_liquidity_one_coin(
            uint256 _token_amount,
            int128 i,
            uint256 min_amount,
            bool donate_dust
        ) external;
    
        function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256);
    
        function token() external view returns (IERC20);
    
        function curve() external view returns (ICurve);
    
        function coins(int128 id) external view returns (IYToken);
    }
    
    
    // Dependency file: contracts/truefi/interface/ITrueFiPool.sol
    
    // pragma solidity 0.6.10;
    
    // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    
    /**
     * TruePool is an ERC20 which represents a share of a pool
     *
     * This contract can be used to wrap opportunities to be compatible
     * with TrueFi and allow users to directly opt-in through the TUSD contract
     *
     * Each TruePool is also a staking opportunity for TRU
     */
    interface ITrueFiPool is IERC20 {
        /// @dev pool token (TUSD)
        function currencyToken() external view returns (IERC20);
    
        /// @dev stake token (TRU)
        function stakeToken() external view returns (IERC20);
    
        /**
         * @dev join pool
         * 1. Transfer TUSD from sender
         * 2. Mint pool tokens based on value to sender
         */
        function join(uint256 amount) external;
    
        /**
         * @dev exit pool
         * 1. Transfer pool tokens from sender
         * 2. Burn pool tokens
         * 3. Transfer value of pool tokens in TUSD to sender
         */
        function exit(uint256 amount) external;
    
        /**
         * @dev borrow from pool
         * 1. Transfer TUSD to sender
         * 2. Only lending pool should be allowed to call this
         */
        function borrow(uint256 amount, uint256 fee) external;
    
        /**
         * @dev join pool
         * 1. Transfer TUSD from sender
         * 2. Only lending pool should be allowed to call this
         */
        function repay(uint256 amount) external;
    }
    
    
    // Dependency file: contracts/truefi/interface/ITrueLender.sol
    
    // pragma solidity 0.6.10;
    
    interface ITrueLender {
        function value() external view returns (uint256);
    
        function distribute(
            address recipient,
            uint256 numerator,
            uint256 denominator
        ) external;
    }
    
    
    // Dependency file: contracts/truefi/interface/IUniswapRouter.sol
    
    // pragma solidity 0.6.10;
    
    interface IUniswapRouter {
        function swapExactTokensForTokens(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external returns (uint256[] memory amounts);
    }
    
    
    // Dependency file: contracts/truefi/Log.sol
    
    /*
     * ABDK Math 64.64 Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
     * Author: Mikhail Vladimirov <mikhail.vladimirov@gmail.com>
     */
    // pragma solidity 0.6.10;
    
    /**
     * Smart contract library of mathematical functions operating with signed
     * 64.64-bit fixed point numbers.  Signed 64.64-bit fixed point number is
     * basically a simple fraction whose numerator is signed 128-bit integer and
     * denominator is 2^64.  As long as denominator is always the same, there is no
     * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
     * represented by int128 type holding only the numerator.
     */
    library ABDKMath64x64 {
        /**
         * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
         * number.  Revert on overflow.
         *
         * @param x unsigned 256-bit integer number
         * @return signed 64.64-bit fixed point number
         */
        function fromUInt(uint256 x) internal pure returns (int128) {
            require(x <= 0x7FFFFFFFFFFFFFFF);
            return int128(x << 64);
        }
    
        /**
         * Calculate binary logarithm of x.  Revert if x <= 0.
         *
         * @param x signed 64.64-bit fixed point number
         * @return signed 64.64-bit fixed point number
         */
        function log_2(int128 x) internal pure returns (int128) {
            require(x > 0);
    
            int256 msb = 0;
            int256 xc = x;
            if (xc >= 0x10000000000000000) {
                xc >>= 64;
                msb += 64;
            }
            if (xc >= 0x100000000) {
                xc >>= 32;
                msb += 32;
            }
            if (xc >= 0x10000) {
                xc >>= 16;
                msb += 16;
            }
            if (xc >= 0x100) {
                xc >>= 8;
                msb += 8;
            }
            if (xc >= 0x10) {
                xc >>= 4;
                msb += 4;
            }
            if (xc >= 0x4) {
                xc >>= 2;
                msb += 2;
            }
            if (xc >= 0x2) msb += 1; // No need to shift xc anymore
    
            int256 result = (msb - 64) << 64;
            uint256 ux = uint256(x) << uint256(127 - msb);
            for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {
                ux *= ux;
                uint256 b = ux >> 255;
                ux >>= 127 + b;
                result += bit * int256(b);
            }
    
            return int128(result);
        }
    
        /**
         * Calculate natural logarithm of x.  Revert if x <= 0.
         *
         * @param x signed 64.64-bit fixed point number
         * @return signed 64.64-bit fixed point number
         */
        function ln(int128 x) internal pure returns (int128) {
            require(x > 0);
    
            return int128((uint256(log_2(x)) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF) >> 128);
        }
    }
    
    
    // Dependency file: contracts/truefi/interface/ITruPriceOracle.sol
    
    // pragma solidity 0.6.10;
    
    interface ITruPriceOracle {
        function usdToTru(uint256 amount) external view returns (uint256);
    
        function truToUsd(uint256 amount) external view returns (uint256);
    }
    
    
    // Root file: contracts/truefi/TrueFiPool.sol
    
    pragma solidity 0.6.10;
    
    // import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    // import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
    // import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
    
    // import {ERC20} from "contracts/truefi/common/UpgradeableERC20.sol";
    // import {Ownable} from "contracts/truefi/common/UpgradeableOwnable.sol";
    // import {ICurveGauge, ICurveMinter, ICurvePool} from "contracts/truefi/interface/ICurve.sol";
    // import {ITrueFiPool} from "contracts/truefi/interface/ITrueFiPool.sol";
    // import {ITrueLender} from "contracts/truefi/interface/ITrueLender.sol";
    // import {IUniswapRouter} from "contracts/truefi/interface/IUniswapRouter.sol";
    // import {ABDKMath64x64} from "contracts/truefi/Log.sol";
    // import {ITruPriceOracle} from "contracts/truefi/interface/ITruPriceOracle.sol";
    
    /**
     * @title TrueFi Pool
     * @dev Lending pool which uses curve.fi to store idle funds
     * Earn high interest rates on currency deposits through uncollateralized loans
     *
     * Funds deposited in this pool are not fully liquid. Luqidity
     * Exiting the pool has 2 options:
     * - withdraw a basket of LoanTokens backing the pool
     * - take an exit penallty depending on pool liquidity
     * After exiting, an account will need to wait for LoanTokens to expire and burn them
     * It is recommended to perform a zap or swap tokens on Uniswap for increased liquidity
     *
     * Funds are managed through an external function to save gas on deposits
     */
    contract TrueFiPool is ITrueFiPool, ERC20, ReentrancyGuard, Ownable {
        using SafeMath for uint256;
    
        // ================ WARNING ==================
        // ===== THIS CONTRACT IS INITIALIZABLE ======
        // === STORAGE VARIABLES ARE DECLARED BELOW ==
        // REMOVAL OR REORDER OF VARIABLES WILL RESULT
        // ========= IN STORAGE CORRUPTION ===========
    
        ICurvePool public _curvePool;
        ICurveGauge public _curveGauge;
        IERC20 public _currencyToken;
        ITrueLender public _lender;
        ICurveMinter public _minter;
        IUniswapRouter public _uniRouter;
    
        // fee for deposits
        uint256 public joiningFee;
        // track claimable fees
        uint256 public claimableFees;
    
        mapping(address => uint256) latestJoinBlock;
    
        IERC20 public _stakeToken;
    
        // cache values during sync for gas optimization
        bool private inSync;
        uint256 private yTokenValueCache;
        uint256 private loansValueCache;
    
        // TRU price oracle
        ITruPriceOracle public _oracle;
    
        // fund manager can call functions to help manage pool funds
        // fund manager can be set to 0 or governance
        address public fundsManager;
    
        // allow pausing of deposits
        bool public isJoiningPaused;
    
        // ======= STORAGE DECLARATION END ============
    
        // curve.fi data
        uint8 constant N_TOKENS = 4;
        uint8 constant TUSD_INDEX = 3;
    
        /**
         * @dev Emitted when stake token address
         * @param token New stake token address
         */
        event StakeTokenChanged(IERC20 token);
    
        /**
         * @dev Emitted oracle was changed
         * @param newOracle New oracle address
         */
        event OracleChanged(ITruPriceOracle newOracle);
    
        /**
         * @dev Emitted when funds manager is changed
         * @param newManager New manager address
         */
        event FundsManagerChanged(address newManager);
    
        /**
         * @dev Emitted when fee is changed
         * @param newFee New fee
         */
        event JoiningFeeChanged(uint256 newFee);
    
        /**
         * @dev Emitted when someone joins the pool
         * @param staker Account staking
         * @param deposited Amount deposited
         * @param minted Amount of pool tokens minted
         */
        event Joined(address indexed staker, uint256 deposited, uint256 minted);
    
        /**
         * @dev Emitted when someone exits the pool
         * @param staker Account exiting
         * @param amount Amount unstaking
         */
        event Exited(address indexed staker, uint256 amount);
    
        /**
         * @dev Emitted when funds are flushed into curve.fi
         * @param currencyAmount Amount of tokens deposited
         */
        event Flushed(uint256 currencyAmount);
    
        /**
         * @dev Emitted when funds are pulled from curve.fi
         * @param yAmount Amount of pool tokens
         */
        event Pulled(uint256 yAmount);
    
        /**
         * @dev Emitted when funds are borrowed from pool
         * @param borrower Borrower address
         * @param amount Amount of funds borrowed from pool
         * @param fee Fees collected from this transaction
         */
        event Borrow(address borrower, uint256 amount, uint256 fee);
    
        /**
         * @dev Emitted when borrower repays the pool
         * @param payer Address of borrower
         * @param amount Amount repaid
         */
        event Repaid(address indexed payer, uint256 amount);
    
        /**
         * @dev Emitted when fees are collected
         * @param beneficiary Account to receive fees
         * @param amount Amount of fees collected
         */
        event Collected(address indexed beneficiary, uint256 amount);
    
        /**
         * @dev Emitted when joining is paused or unpaused
         * @param isJoiningPaused New pausing status
         */
        event JoiningPauseStatusChanged(bool isJoiningPaused);
    
        /**
         * @dev Initialize pool
         * @param __curvePool curve pool address
         * @param __curveGauge curve gauge address
         * @param __currencyToken curve pool underlying token
         * @param __lender TrueLender address
         * @param __uniRouter Uniswap router
         */
        function initialize(
            ICurvePool __curvePool,
            ICurveGauge __curveGauge,
            IERC20 __currencyToken,
            ITrueLender __lender,
            IUniswapRouter __uniRouter,
            IERC20 __stakeToken,
            ITruPriceOracle __oracle
        ) public initializer {
            ERC20.__ERC20_initialize("TrueFi LP", "TFI-LP");
            Ownable.initialize();
    
            _curvePool = __curvePool;
            _curveGauge = __curveGauge;
            _currencyToken = __currencyToken;
            _lender = __lender;
            _minter = _curveGauge.minter();
            _uniRouter = __uniRouter;
            _stakeToken = __stakeToken;
            _oracle = __oracle;
    
            joiningFee = 25;
        }
    
        /**
         * @dev only lender can perform borrowing or repaying
         */
        modifier onlyLender() {
            require(msg.sender == address(_lender), "TrueFiPool: Caller is not the lender");
            _;
        }
    
        /**
         * @dev pool can only be joined when it's unpaused
         */
        modifier joiningNotPaused() {
            require(!isJoiningPaused, "TrueFiPool: Joining the pool is paused");
            _;
        }
    
        /**
         * @dev only lender can perform borrowing or repaying
         */
        modifier onlyOwnerOrManager() {
            require(msg.sender == owner() || msg.sender == fundsManager, "TrueFiPool: Caller is neither owner nor funds manager");
            _;
        }
    
        /**
         * Sync values to avoid making expensive calls multiple times
         * Will set inSync to true, allowing getter functions to return cached values
         * Wipes cached values to save gas
         */
        modifier sync() {
            // sync
            yTokenValueCache = yTokenValue();
            loansValueCache = loansValue();
            inSync = true;
            _;
            // wipe
            inSync = false;
            yTokenValueCache = 0;
            loansValueCache = 0;
        }
    
        /**
         * @dev get currency token address
         * @return currency token address
         */
        function currencyToken() public override view returns (IERC20) {
            return _currencyToken;
        }
    
        /**
         * @dev get stake token address
         * @return stake token address
         */
        function stakeToken() public override view returns (IERC20) {
            return _stakeToken;
        }
    
        /**
         * @dev set stake token address
         * @param token stake token address
         */
        function setStakeToken(IERC20 token) public onlyOwner {
            _stakeToken = token;
            emit StakeTokenChanged(token);
        }
    
        /**
         * @dev set funds manager address
         */
        function setFundsManager(address newFundsManager) public onlyOwner {
            fundsManager = newFundsManager;
            emit FundsManagerChanged(newFundsManager);
        }
    
        /**
         * @dev set oracle token address
         * @param newOracle new oracle address
         */
        function setOracle(ITruPriceOracle newOracle) public onlyOwner {
            _oracle = newOracle;
            emit OracleChanged(newOracle);
        }
    
        /**
         * @dev Allow pausing of deposits in case of emergency
         * @param status New deposit status
         */
        function changeJoiningPauseStatus(bool status) external onlyOwnerOrManager {
            isJoiningPaused = status;
            emit JoiningPauseStatusChanged(status);
        }
    
        /**
         * @dev Get total balance of stake tokens
         * @return Balance of stake tokens in this contract
         */
        function stakeTokenBalance() public view returns (uint256) {
            return _stakeToken.balanceOf(address(this));
        }
    
        /**
         * @dev Get total balance of curve.fi pool tokens
         * @return Balance of y pool tokens in this contract
         */
        function yTokenBalance() public view returns (uint256) {
            return _curvePool.token().balanceOf(address(this)).add(_curveGauge.balanceOf(address(this)));
        }
    
        /**
         * @dev Virtual value of yCRV tokens in the pool
         * Will return sync value if inSync
         * @return yTokenValue in USD.
         */
        function yTokenValue() public view returns (uint256) {
            if (inSync) {
                return yTokenValueCache;
            }
            return yTokenBalance().mul(_curvePool.curve().get_virtual_price()).div(1 ether);
        }
    
        /**
         * @dev Price of TRU in USD
         * @return Oracle price of TRU in USD
         */
        function truValue() public view returns (uint256) {
            uint256 balance = stakeTokenBalance();
            if (balance == 0) {
                return 0;
            }
            return _oracle.truToUsd(balance);
        }
    
        /**
         * @dev Virtual value of liquid assets in the pool
         * @return Virtual liquid value of pool assets
         */
        function liquidValue() public view returns (uint256) {
            return currencyBalance().add(yTokenValue());
        }
    
        /**
         * @dev Calculate pool value in TUSD
         * "virtual price" of entire pool - LoanTokens, TUSD, curve y pool tokens
         * @return pool value in USD
         */
        function poolValue() public view returns (uint256) {
            // this assumes defaulted loans are worth their full value
            return liquidValue().add(loansValue());
        }
    
        /**
         * @dev Virtual value of loan assets in the pool
         * Will return cached value if inSync
         * @return Value of loans in pool
         */
        function loansValue() public view returns (uint256) {
            if (inSync) {
                return loansValueCache;
            }
            return _lender.value();
        }
    
        /**
         * @dev ensure enough curve.fi pool tokens are available
         * Check if current available amount of TUSD is enough and
         * withdraw remainder from gauge
         * @param neededAmount amount required
         */
        function ensureEnoughTokensAreAvailable(uint256 neededAmount) internal {
            uint256 currentlyAvailableAmount = _curvePool.token().balanceOf(address(this));
            if (currentlyAvailableAmount < neededAmount) {
                _curveGauge.withdraw(neededAmount.sub(currentlyAvailableAmount));
            }
        }
    
        /**
         * @dev set pool join fee
         * @param fee new fee
         */
        function setJoiningFee(uint256 fee) external onlyOwner {
            require(fee <= 10000, "TrueFiPool: Fee cannot exceed transaction value");
            joiningFee = fee;
            emit JoiningFeeChanged(fee);
        }
    
        /**
         * @dev sets all token allowances used to 0
         */
        function resetApprovals() external onlyOwner {
            _currencyToken.approve(address(_curvePool), 0);
            _curvePool.token().approve(address(_curvePool), 0);
            _curvePool.token().approve(address(_curveGauge), 0);
        }
    
        /**
         * @dev Join the pool by depositing currency tokens
         * @param amount amount of currency token to deposit
         */
        function join(uint256 amount) external override joiningNotPaused {
            uint256 fee = amount.mul(joiningFee).div(10000);
            uint256 mintedAmount = mint(amount.sub(fee));
            claimableFees = claimableFees.add(fee);
    
            latestJoinBlock[tx.origin] = block.number;
            require(_currencyToken.transferFrom(msg.sender, address(this), amount));
    
            emit Joined(msg.sender, amount, mintedAmount);
        }
    
        // prettier-ignore
        /**
         * @dev Exit pool
         * This function will withdraw a basket of currencies backing the pool value
         * @param amount amount of pool tokens to redeem for underlying tokens
         */
        function exit(uint256 amount) external override nonReentrant {
            require(block.number != latestJoinBlock[tx.origin], "TrueFiPool: Cannot join and exit in same block");
            require(amount <= balanceOf(msg.sender), "TrueFiPool: insufficient funds");
    
            uint256 _totalSupply = totalSupply();
    
            // get share of currency tokens kept in the pool
            uint256 currencyAmountToTransfer = amount.mul(
                currencyBalance()).div(_totalSupply);
    
            // calculate amount of curve.fi pool tokens
            uint256 curveLiquidityAmountToTransfer = amount.mul(
                yTokenBalance()).div(_totalSupply);
    
            // calculate amount of stake tokens
            uint256 stakeTokenAmountToTransfer = amount.mul(
                stakeTokenBalance()).div(_totalSupply);
    
            // burn tokens sent
            _burn(msg.sender, amount);
    
            // withdraw basket of loan tokens
            _lender.distribute(msg.sender, amount, _totalSupply);
    
            // if currency remaining, transfer
            if (currencyAmountToTransfer > 0) {
                require(_currencyToken.transfer(msg.sender, currencyAmountToTransfer));
            }
            // if curve tokens remaining, transfer
            if (curveLiquidityAmountToTransfer > 0) {
                ensureEnoughTokensAreAvailable(curveLiquidityAmountToTransfer);
                require(_curvePool.token().transfer(msg.sender, curveLiquidityAmountToTransfer));
            }
    
            // if stake token remaining, transfer
            if (stakeTokenAmountToTransfer > 0) {
                require(_stakeToken.transfer(msg.sender, stakeTokenAmountToTransfer));
            }
    
            emit Exited(msg.sender, amount);
        }
    
        /**
         * @dev Exit pool only with liquid tokens
         * This function will withdraw TUSD but with a small penalty
         * Uses the sync() modifier to reduce gas costs of using curve
         * @param amount amount of pool tokens to redeem for underlying tokens
         */
        function liquidExit(uint256 amount) external nonReentrant sync {
            require(block.number != latestJoinBlock[tx.origin], "TrueFiPool: Cannot join and exit in same block");
            require(amount <= balanceOf(msg.sender), "TrueFiPool: Insufficient funds");
    
            uint256 amountToWithdraw = poolValue().mul(amount).div(totalSupply());
            amountToWithdraw = amountToWithdraw.mul(liquidExitPenalty(amountToWithdraw)).div(10000);
            require(amountToWithdraw <= liquidValue(), "TrueFiPool: Not enough liquidity in pool");
    
            // burn tokens sent
            _burn(msg.sender, amount);
    
            if (amountToWithdraw > currencyBalance()) {
                removeLiquidityFromCurve(amountToWithdraw.sub(currencyBalance()));
                require(amountToWithdraw <= currencyBalance(), "TrueFiPool: Not enough funds were withdrawn from Curve");
            }
    
            require(_currencyToken.transfer(msg.sender, amountToWithdraw));
    
            emit Exited(msg.sender, amountToWithdraw);
        }
    
        /**
         * @dev Penalty (in % * 100) applied if liquid exit is performed with this amount
         * returns 10000 if no penalty
         */
        function liquidExitPenalty(uint256 amount) public view returns (uint256) {
            uint256 lv = liquidValue();
            uint256 pv = poolValue();
            if (amount == pv) {
                return 10000;
            }
            uint256 liquidRatioBefore = lv.mul(10000).div(pv);
            uint256 liquidRatioAfter = lv.sub(amount).mul(10000).div(pv.sub(amount));
            return uint256(10000).sub(averageExitPenalty(liquidRatioAfter, liquidRatioBefore));
        }
    
        /**
         * @dev Calculates integral of 5/(x+50)dx times 10000
         */
        function integrateAtPoint(uint256 x) public pure returns (uint256) {
            return uint256(ABDKMath64x64.ln(ABDKMath64x64.fromUInt(x.add(50)))).mul(50000).div(2**64);
        }
    
        /**
         * @dev Calculates average penalty on interval [from; to]
         * @return average exit penalty
         */
        function averageExitPenalty(uint256 from, uint256 to) public pure returns (uint256) {
            require(from <= to, "TrueFiPool: To precedes from");
            if (from == 10000) {
                // When all liquid, dont penalize
                return 0;
            }
            if (from == to) {
                return uint256(50000).div(from.add(50));
            }
            return integrateAtPoint(to).sub(integrateAtPoint(from)).div(to.sub(from));
        }
    
        /**
         * @dev Deposit idle funds into curve.fi pool and stake in gauge
         * Called by owner to help manage funds in pool and save on gas for deposits
         * @param currencyAmount Amount of funds to deposit into curve
         * @param minMintAmount Minimum amount to mint
         */
        function flush(uint256 currencyAmount, uint256 minMintAmount) external onlyOwnerOrManager {
            require(currencyAmount <= currencyBalance(), "TrueFiPool: Insufficient currency balance");
    
            uint256[N_TOKENS] memory amounts = [0, 0, 0, currencyAmount];
    
            // add TUSD to curve
            _currencyToken.approve(address(_curvePool), currencyAmount);
            _curvePool.add_liquidity(amounts, minMintAmount);
    
            // stake yCurve tokens in gauge
            uint256 yBalance = _curvePool.token().balanceOf(address(this));
            _curvePool.token().approve(address(_curveGauge), yBalance);
            _curveGauge.deposit(yBalance);
    
            emit Flushed(currencyAmount);
        }
    
        /**
         * @dev Remove liquidity from curve
         * @param yAmount amount of curve pool tokens
         * @param minCurrencyAmount minimum amount of tokens to withdraw
         */
        function pull(uint256 yAmount, uint256 minCurrencyAmount) external onlyOwnerOrManager {
            require(yAmount <= yTokenBalance(), "TrueFiPool: Insufficient Curve liquidity balance");
    
            // unstake in gauge
            ensureEnoughTokensAreAvailable(yAmount);
    
            // remove TUSD from curve
            _curvePool.token().approve(address(_curvePool), yAmount);
            _curvePool.remove_liquidity_one_coin(yAmount, TUSD_INDEX, minCurrencyAmount, false);
    
            emit Pulled(yAmount);
        }
    
        // prettier-ignore
        /**
         * @dev Remove liquidity from curve if necessary and transfer to lender
         * @param amount amount for lender to withdraw
         */
        function borrow(uint256 amount, uint256 fee) external override nonReentrant onlyLender {
            // if there is not enough TUSD, withdraw from curve
            if (amount > currencyBalance()) {
                removeLiquidityFromCurve(amount.sub(currencyBalance()));
                require(amount <= currencyBalance(), "TrueFiPool: Not enough funds in pool to cover borrow");
            }
    
            mint(fee);
            require(_currencyToken.transfer(msg.sender, amount.sub(fee)));
    
            emit Borrow(msg.sender, amount, fee);
        }
    
        function removeLiquidityFromCurve(uint256 amountToWithdraw) internal {
            // get rough estimate of how much yCRV we should sell
            uint256 roughCurveTokenAmount = calcTokenAmount(amountToWithdraw).mul(1005).div(1000);
            require(roughCurveTokenAmount <= yTokenBalance(), "TrueFiPool: Not enough Curve liquidity tokens in pool to cover borrow");
            // pull tokens from gauge
            ensureEnoughTokensAreAvailable(roughCurveTokenAmount);
            // remove TUSD from curve
            _curvePool.token().approve(address(_curvePool), roughCurveTokenAmount);
            uint256 minAmount = roughCurveTokenAmount.mul(_curvePool.curve().get_virtual_price()).mul(999).div(1000).div(1 ether);
            _curvePool.remove_liquidity_one_coin(roughCurveTokenAmount, TUSD_INDEX, minAmount, false);
        }
    
        /**
         * @dev repay debt by transferring tokens to the contract
         * @param currencyAmount amount to repay
         */
        function repay(uint256 currencyAmount) external override onlyLender {
            require(_currencyToken.transferFrom(msg.sender, address(this), currencyAmount));
            emit Repaid(msg.sender, currencyAmount);
        }
    
        /**
         * @dev Collect CRV tokens minted by staking at gauge
         */
        function collectCrv() external onlyOwnerOrManager {
            _minter.mint(address(_curveGauge));
        }
    
        /**
         * @dev Sell collected CRV on Uniswap
         * - Selling CRV is managed by the contract owner
         * - Calculations can be made off-chain and called based on market conditions
         * - Need to pass path of exact pairs to go through while executing exchange
         * For example, CRV -> WETH -> TUSD
         *
         * @param amountIn see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         * @param amountOutMin see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         * @param path see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         */
        function sellCrv(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path
        ) public onlyOwnerOrManager {
            _minter.token().approve(address(_uniRouter), amountIn);
            _uniRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), block.timestamp + 1 hours);
        }
    
        /**
         * @dev Sell collected TRU on Uniswap
         * - Selling TRU is managed by the contract owner
         * - Calculations can be made off-chain and called based on market conditions
         * - Need to pass path of exact pairs to go through while executing exchange
         * For example, CRV -> WETH -> TUSD
         *
         * @param amountIn see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         * @param amountOutMin see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         * @param path see https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
         */
        function sellStakeToken(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path
        ) public onlyOwnerOrManager {
            _stakeToken.approve(address(_uniRouter), amountIn);
            _uniRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), block.timestamp + 1 hours);
        }
    
        /**
         * @dev Claim fees from the pool
         * @param beneficiary account to send funds to
         */
        function collectFees(address beneficiary) external onlyOwnerOrManager {
            uint256 amount = claimableFees;
            claimableFees = 0;
    
            if (amount > 0) {
                require(_currencyToken.transfer(beneficiary, amount));
            }
    
            emit Collected(beneficiary, amount);
        }
    
        /**
         * @notice Expected amount of minted Curve.fi yDAI/yUSDC/yUSDT/yTUSD tokens.
         * Can be used to control slippage
         * Called in flush() function
         * @param currencyAmount amount to calculate for
         * @return expected amount minted given currency amount
         */
        function calcTokenAmount(uint256 currencyAmount) public view returns (uint256) {
            // prettier-ignore
            uint256 yTokenAmount = currencyAmount.mul(1e18).div(
                _curvePool.coins(TUSD_INDEX).getPricePerFullShare());
            uint256[N_TOKENS] memory yAmounts = [0, 0, 0, yTokenAmount];
            return _curvePool.curve().calc_token_amount(yAmounts, true);
        }
    
        /**
         * @dev Converts the value of a single yCRV into an underlying asset
         * @param yAmount amount of curve pool tokens to calculate for
         * @return Value of one y pool token
         */
        function calcWithdrawOneCoin(uint256 yAmount) public view returns (uint256) {
            return _curvePool.calc_withdraw_one_coin(yAmount, TUSD_INDEX);
        }
    
        /**
         * @dev Currency token balance
         * @return Currency token balance
         */
        function currencyBalance() internal view returns (uint256) {
            return _currencyToken.balanceOf(address(this)).sub(claimableFees);
        }
    
        /**
         * @param depositedAmount Amount of currency deposited
         * @return amount minted from this transaction
         */
        function mint(uint256 depositedAmount) internal returns (uint256) {
            uint256 mintedAmount = depositedAmount;
            if (mintedAmount == 0) {
                return mintedAmount;
            }
    
            // first staker mints same amount deposited
            if (totalSupply() > 0) {
                mintedAmount = totalSupply().mul(depositedAmount).div(poolValue());
            }
            // mint pool tokens
            _mint(msg.sender, mintedAmount);
    
            return mintedAmount;
        }
    
        /**
         * @dev Update name and symbol of this contract
         */
        function updateNameAndSymbol() public {
            super.updateNameAndSymbol("TrueFi TrueUSD", "tfTUSD");
        }
    }