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× 2× 46× 45× 45× 48× 46× 46× 47× 45× 45× 4× 4× | // 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; } |