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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | 27× 27× 28× 28× 4× 2× 80× 75× 13× 13× 12× 12× 9× 9× 9× 21× 21× 7× 7× 6× 20× 20× 36× 36× 36× 36× 36× 35× 34× 34× 33× 33× 33× 26× 26× 26× 7× 10× 3× 3× 3× 3× 10× 2× 2× 2× 2× 2× 2× 3× 12× 12× 12× 11× 11× 11× 6× 6× 6× 3× 3× 3× 3× 3× 3× 3× 27× 12× 14× 14× 3× 3× 3× 3× 2× 2× 2× 2× 2× 1× 2× 2× 2× 2× 1× 2× 1× 1× 2× 1× 1× 2× 1× 1× 1× 1× 1× | // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../BaseLogic.sol"; import "../IRiskManager.sol"; import "../PToken.sol"; import "../Interfaces.sol"; import "../Utils.sol"; /// @notice Definition of callback method that deferLiquidityCheck will invoke on your contract interface IDeferredLiquidityCheck { function onDeferredLiquidityCheck(bytes memory data) external; } /// @notice Batch executions, liquidity check deferrals, and interfaces to fetch prices and account liquidity contract Exec is BaseLogic { constructor(bytes32 moduleGitCommit_) BaseLogic(MODULEID__EXEC, moduleGitCommit_) {} /// @notice Single item in a batch request struct EulerBatchItem { bool allowError; address proxyAddr; bytes data; } /// @notice Single item in a batch response struct EulerBatchItemResponse { bool success; bytes result; } // Accessors /// @notice Compute aggregate liquidity for an account /// @param account User address /// @return status Aggregate liquidity (sum of all entered assets) function liquidity(address account) external staticDelegate returns (IRiskManager.LiquidityStatus memory status) { bytes memory result = callInternalModule(MODULEID__RISK_MANAGER, abi.encodeWithSelector(IRiskManager.computeLiquidity.selector, account)); (status) = abi.decode(result, (IRiskManager.LiquidityStatus)); } /// @notice Compute detailed liquidity for an account, broken down by asset /// @param account User address /// @return assets List of user's entered assets and each asset's corresponding liquidity function detailedLiquidity(address account) public staticDelegate returns (IRiskManager.AssetLiquidity[] memory assets) { bytes memory result = callInternalModule(MODULEID__RISK_MANAGER, abi.encodeWithSelector(IRiskManager.computeAssetLiquidities.selector, account)); (assets) = abi.decode(result, (IRiskManager.AssetLiquidity[])); } /// @notice Retrieve Euler's view of an asset's price /// @param underlying Token address /// @return twap Time-weighted average price /// @return twapPeriod TWAP duration, either the twapWindow value in AssetConfig, or less if that duration not available function getPrice(address underlying) external staticDelegate returns (uint twap, uint twapPeriod) { bytes memory result = callInternalModule(MODULEID__RISK_MANAGER, abi.encodeWithSelector(IRiskManager.getPrice.selector, underlying)); (twap, twapPeriod) = abi.decode(result, (uint, uint)); } /// @notice Retrieve Euler's view of an asset's price, as well as the current marginal price on uniswap /// @param underlying Token address /// @return twap Time-weighted average price /// @return twapPeriod TWAP duration, either the twapWindow value in AssetConfig, or less if that duration not available /// @return currPrice The current marginal price on uniswap3 (informational: not used anywhere in the Euler protocol) function getPriceFull(address underlying) external staticDelegate returns (uint twap, uint twapPeriod, uint currPrice) { bytes memory result = callInternalModule(MODULEID__RISK_MANAGER, abi.encodeWithSelector(IRiskManager.getPriceFull.selector, underlying)); (twap, twapPeriod, currPrice) = abi.decode(result, (uint, uint, uint)); } // Custom execution methods /// @notice Defer liquidity checking for an account, to perform rebalancing, flash loans, etc. msg.sender must implement IDeferredLiquidityCheck /// @param account The account to defer liquidity for. Usually address(this), although not always /// @param data Passed through to the onDeferredLiquidityCheck() callback, so contracts don't need to store transient data in storage function deferLiquidityCheck(address account, bytes memory data) external reentrantOK { address msgSender = unpackTrailingParamMsgSender(); require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/defer/reentrancy"); accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN; IDeferredLiquidityCheck(msgSender).onDeferredLiquidityCheck(data); uint8 status = accountLookup[account].deferLiquidityStatus; accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE; Eif (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account); } /// @notice Execute several operations in a single transaction /// @param items List of operations to execute /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for /// @return List of operation results function batchDispatch(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks) public reentrantOK returns (EulerBatchItemResponse[] memory) { address msgSender = unpackTrailingParamMsgSender(); for (uint i = 0; i < deferLiquidityChecks.length; ++i) { address account = deferLiquidityChecks[i]; require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/batch/reentrancy"); accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN; } EulerBatchItemResponse[] memory response = new EulerBatchItemResponse[](items.length); for (uint i = 0; i < items.length; ++i) { EulerBatchItem calldata item = items[i]; address proxyAddr = item.proxyAddr; uint32 moduleId = trustedSenders[proxyAddr].moduleId; address moduleImpl = trustedSenders[proxyAddr].moduleImpl; require(moduleId != 0, "e/batch/unknown-proxy-addr"); require(moduleId <= MAX_EXTERNAL_MODULEID, "e/batch/call-to-internal-module"); if (moduleImpl == address(0)) moduleImpl = moduleLookup[moduleId]; require(moduleImpl != address(0), "e/batch/module-not-installed"); bytes memory inputWrapped = abi.encodePacked(item.data, uint160(msgSender), uint160(proxyAddr)); (bool success, bytes memory result) = moduleImpl.delegatecall(inputWrapped); if (success || item.allowError) { EulerBatchItemResponse memory r = response[i]; r.success = success; r.result = result; } else { revertBytes(result); } } for (uint i = 0; i < deferLiquidityChecks.length; ++i) { address account = deferLiquidityChecks[i]; uint8 status = accountLookup[account].deferLiquidityStatus; accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE; Eif (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account); } return response; } /// @notice Results of a batchDispatch, but with extra information struct EulerBatchExtra { EulerBatchItemResponse[] responses; uint gasUsed; IRiskManager.AssetLiquidity[][] liquidities; } /// @notice Call batchDispatch, but return extra information. Only intended to be used with callStatic. /// @param items List of operations to execute /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for /// @param queryLiquidity List of user accounts to return detailed liquidity information for /// @return output Structure with extra information function batchDispatchExtra(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks, address[] calldata queryLiquidity) external reentrantOK returns (EulerBatchExtra memory output) { { uint origGasLeft = gasleft(); output.responses = batchDispatch(items, deferLiquidityChecks); output.gasUsed = origGasLeft - gasleft(); } output.liquidities = new IRiskManager.AssetLiquidity[][](queryLiquidity.length); for (uint i = 0; i < queryLiquidity.length; ++i) { output.liquidities[i] = detailedLiquidity(queryLiquidity[i]); } } // Average liquidity tracking /// @notice Enable average liquidity tracking for your account. Operations will cost more gas, but you may get additional benefits when performing liquidations /// @param subAccountId subAccountId 0 for primary, 1-255 for a sub-account. /// @param delegate An address of another account that you would allow to use the benefits of your account's average liquidity (use the null address if you don't care about this). The other address must also reciprocally delegate to your account. /// @param onlyDelegate Set this flag to skip tracking average liquidity and only set the delegate. function trackAverageLiquidity(uint subAccountId, address delegate, bool onlyDelegate) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); address account = getSubAccount(msgSender, subAccountId); require(account != delegate, "e/track-liquidity/self-delegation"); emit DelegateAverageLiquidity(account, delegate); accountLookup[account].averageLiquidityDelegate = delegate; if (onlyDelegate) return; emit TrackAverageLiquidity(account); accountLookup[account].lastAverageLiquidityUpdate = uint40(block.timestamp); accountLookup[account].averageLiquidity = 0; } /// @notice Disable average liquidity tracking for your account and remove delegate /// @param subAccountId subAccountId 0 for primary, 1-255 for a sub-account function unTrackAverageLiquidity(uint subAccountId) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); address account = getSubAccount(msgSender, subAccountId); emit UnTrackAverageLiquidity(account); emit DelegateAverageLiquidity(account, address(0)); accountLookup[account].lastAverageLiquidityUpdate = 0; accountLookup[account].averageLiquidity = 0; accountLookup[account].averageLiquidityDelegate = address(0); } /// @notice Retrieve the average liquidity for an account /// @param account User account (xor in subAccountId, if applicable) /// @return The average liquidity, in terms of the reference asset, and post risk-adjustment function getAverageLiquidity(address account) external nonReentrant returns (uint) { return getUpdatedAverageLiquidity(account); } /// @notice Retrieve the average liquidity for an account or a delegate account, if set /// @param account User account (xor in subAccountId, if applicable) /// @return The average liquidity, in terms of the reference asset, and post risk-adjustment function getAverageLiquidityWithDelegate(address account) external nonReentrant returns (uint) { return getUpdatedAverageLiquidityWithDelegate(account); } /// @notice Retrieve the account which delegates average liquidity for an account, if set /// @param account User account (xor in subAccountId, if applicable) /// @return The average liquidity delegate account function getAverageLiquidityDelegateAccount(address account) external view returns (address) { address delegate = accountLookup[account].averageLiquidityDelegate; return accountLookup[delegate].averageLiquidityDelegate == account ? delegate : address(0); } // PToken wrapping/unwrapping /// @notice Transfer underlying tokens from sender's wallet into the pToken wrapper. Allowance should be set for the euler address. /// @param underlying Token address /// @param amount The amount to wrap in underlying units function pTokenWrap(address underlying, uint amount) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); emit PTokenWrap(underlying, msgSender, amount); address pTokenAddr = reversePTokenLookup[underlying]; require(pTokenAddr != address(0), "e/exec/ptoken-not-found"); { uint origBalance = IERC20(underlying).balanceOf(pTokenAddr); Utils.safeTransferFrom(underlying, msgSender, pTokenAddr, amount); uint newBalance = IERC20(underlying).balanceOf(pTokenAddr); require(newBalance == origBalance + amount, "e/exec/ptoken-transfer-mismatch"); } PToken(pTokenAddr).claimSurplus(msgSender); } /// @notice Transfer underlying tokens from the pToken wrapper to the sender's wallet. /// @param underlying Token address /// @param amount The amount to unwrap in underlying units function pTokenUnWrap(address underlying, uint amount) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); emit PTokenUnWrap(underlying, msgSender, amount); address pTokenAddr = reversePTokenLookup[underlying]; require(pTokenAddr != address(0), "e/exec/ptoken-not-found"); PToken(pTokenAddr).forceUnwrap(msgSender, amount); } /// @notice Apply EIP2612 signed permit on a target token from sender to euler contract /// @param token Token address /// @param value Allowance value /// @param deadline Permit expiry timestamp /// @param v secp256k1 signature v /// @param r secp256k1 signature r /// @param s secp256k1 signature s function usePermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external nonReentrant { require(underlyingLookup[token].eTokenAddress != address(0), "e/exec/market-not-activated"); address msgSender = unpackTrailingParamMsgSender(); IERC20Permit(token).permit(msgSender, address(this), value, deadline, v, r, s); } /// @notice Apply DAI like (allowed) signed permit on a target token from sender to euler contract /// @param token Token address /// @param nonce Sender nonce /// @param expiry Permit expiry timestamp /// @param allowed If true, set unlimited allowance, otherwise set zero allowance /// @param v secp256k1 signature v /// @param r secp256k1 signature r /// @param s secp256k1 signature s function usePermitAllowed(address token, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external nonReentrant { require(underlyingLookup[token].eTokenAddress != address(0), "e/exec/market-not-activated"); address msgSender = unpackTrailingParamMsgSender(); IERC20Permit(token).permit(msgSender, address(this), nonce, expiry, allowed, v, r, s); } /// @notice Apply allowance to tokens expecting the signature packed in a single bytes param /// @param token Token address /// @param value Allowance value /// @param deadline Permit expiry timestamp /// @param signature secp256k1 signature encoded as rsv function usePermitPacked(address token, uint256 value, uint256 deadline, bytes calldata signature) external nonReentrant { require(underlyingLookup[token].eTokenAddress != address(0), "e/exec/market-not-activated"); address msgSender = unpackTrailingParamMsgSender(); IERC20Permit(token).permit(msgSender, address(this), value, deadline, signature); } /// @notice Execute a staticcall to an arbitrary address with an arbitrary payload. /// @param contractAddress Address of the contract to call /// @param payload Encoded call payload /// @return result Encoded return data /// @dev Intended to be used in static-called batches, to e.g. provide detailed information about the impacts of the simulated operation. function doStaticCall(address contractAddress, bytes memory payload) external view returns (bytes memory) { (bool success, bytes memory result) = contractAddress.staticcall(payload); Iif (!success) revertBytes(result); assembly { return(add(32, result), mload(result)) } } } |