all files / p1/ Broker.sol

95.45% Statements 21/22
94.12% Branches 32/34
100% Functions 7/7
93.94% Lines 31/33
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168                                                                                                              46×   46× 46× 46×   46× 45× 44×                               280×   277× 277×               276× 276×       276×   276× 14× 14×       276×           275× 242×                               46×   45× 45×         48×         46× 46×         47×       45× 45×                            
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
 
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "../interfaces/IBroker.sol";
import "../interfaces/IMain.sol";
import "../interfaces/ITrade.sol";
import "../libraries/Fixed.sol";
import "./mixins/Component.sol";
import "../plugins/trading/GnosisTrade.sol";
 
// Gnosis: uint96 ~= 7e28
uint256 constant GNOSIS_MAX_TOKENS = 7e28;
 
/// A simple core contract that deploys disposable trading contracts for Traders
contract BrokerP1 is ComponentP1, IBroker {
    using EnumerableSet for EnumerableSet.AddressSet;
    using FixLib for uint192;
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using Clones for address;
 
    uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week
 
    IBackingManager private backingManager;
    IRevenueTrader private rsrTrader;
    IRevenueTrader private rTokenTrader;
 
    // The trade contract to clone on openTrade(). Governance parameter.
    ITrade public tradeImplementation;
 
    // The Gnosis contract to init each trade with. Governance parameter.
    IGnosis public gnosis;
 
    // {s} the length of an auction. Governance parameter.
    uint48 public auctionLength;
 
    // Whether trading is disabled.
    // Initially false. Settable by OWNER. A trade clone can set it to true via reportViolation()
    bool public disabled;
 
    // The set of ITrade (clone) addresses this contract has created
    mapping(address => bool) private trades;
 
    // ==== Invariant ====
    // (trades[addr] == true) iff this contract has created an ITrade clone at addr
 
    // effects: initial parameters are set
    function init(
        IMain main_,
        IGnosis gnosis_,
        ITrade tradeImplementation_,
        uint48 auctionLength_
    ) external initializer {
        __Component_init(main_);
 
        backingManager = main_.backingManager();
        rsrTrader = main_.rsrTrader();
        rTokenTrader = main_.rTokenTrader();
 
        setGnosis(gnosis_);
        setTradeImplementation(tradeImplementation_);
        setAuctionLength(auctionLength_);
    }
 
    /// Handle a trade request by deploying a customized disposable trading contract
    /// @dev Requires setting an allowance in advance
    /// @custom:interaction CEI
    // checks:
    //   not disabled, paused, or frozen
    //   caller is a system Trader
    // effects:
    //   Deploys a new trade clone, `trade`
    //   trades'[trade] = true
    // actions:
    //   Transfers req.sellAmount of req.sell.erc20 from caller to `trade`
    //   Calls trade.init() with appropriate parameters
    function openTrade(TradeRequest memory req) external notPausedOrFrozen returns (ITrade) {
        require(!disabled, "broker disabled");
 
        address caller = _msgSender();
        require(
            caller == address(backingManager) ||
                caller == address(rsrTrader) ||
                caller == address(rTokenTrader),
            "only traders"
        );
 
        // In the future we'll have more sophisticated choice logic here, probably by trade size
        GnosisTrade trade = GnosisTrade(address(tradeImplementation).clone());
        trades[address(trade)] = true;
 
        // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that
        // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion
        uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount;
 
        if (maxQty > GNOSIS_MAX_TOKENS) {
            req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL);
            req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR);
        }
 
        // == Interactions ==
        IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom(
            caller,
            address(trade),
            req.sellAmount
        );
 
        trade.init(this, caller, gnosis, auctionLength, req);
        return trade;
    }
 
    /// Disable the broker until re-enabled by governance
    /// @custom:protected
    // checks: not paused, not frozen, caller is a Trade this contract cloned
    // effects: disabled' = true
    function reportViolation() external notPausedOrFrozen {
        Irequire(trades[_msgSender()], "unrecognized trade contract");
        emit DisabledSet(disabled, true);
        disabled = true;
    }
 
    // === Setters ===
 
    /// @custom:governance
    function setGnosis(IGnosis newGnosis) public Egovernance {
        require(address(newGnosis) != address(0), "invalid Gnosis address");
 
        emit GnosisSet(gnosis, newGnosis);
        gnosis = newGnosis;
    }
 
    /// @custom:governance
    function setTradeImplementation(ITrade newTradeImplementation) public governance {
        require(
            address(newTradeImplementation) != address(0),
            "invalid Trade Implementation address"
        );
 
        emit TradeImplementationSet(tradeImplementation, newTradeImplementation);
        tradeImplementation = newTradeImplementation;
    }
 
    /// @custom:governance
    function setAuctionLength(uint48 newAuctionLength) public governance {
        require(
            newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH,
            "invalid auctionLength"
        );
        emit AuctionLengthSet(auctionLength, newAuctionLength);
        auctionLength = newAuctionLength;
    }
 
    /// @custom:governance
    function setDisabled(bool disabled_) external governance {
        emit DisabledSet(disabled, disabled_);
        disabled = disabled_;
    }
 
    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[44] private __gap;
}